From 2cbe804742d15db94d97a9b66fa3d70f85b3d932 Mon Sep 17 00:00:00 2001 From: leofelix077 Date: Thu, 9 Apr 2026 11:26:49 -0300 Subject: [PATCH 1/4] add v1 of freighter extension best practices skill --- .claude/skills/freighter-best-practices | 1 + CLAUDE.md | 169 ++++++ docs/skills/freighter-best-practices/SKILL.md | 41 ++ .../references/anti-patterns.md | 164 ++++++ .../references/architecture.md | 184 +++++++ .../references/code-style.md | 485 ++++++++++++++++++ .../references/dependencies.md | 116 +++++ .../references/error-handling.md | 100 ++++ .../references/git-workflow.md | 76 +++ .../references/i18n.md | 83 +++ .../references/messaging.md | 169 ++++++ .../references/performance.md | 241 +++++++++ .../references/security.md | 112 ++++ .../references/testing.md | 91 ++++ llms.txt | 34 ++ 15 files changed, 2066 insertions(+) create mode 120000 .claude/skills/freighter-best-practices create mode 100644 CLAUDE.md create mode 100644 docs/skills/freighter-best-practices/SKILL.md create mode 100644 docs/skills/freighter-best-practices/references/anti-patterns.md create mode 100644 docs/skills/freighter-best-practices/references/architecture.md create mode 100644 docs/skills/freighter-best-practices/references/code-style.md create mode 100644 docs/skills/freighter-best-practices/references/dependencies.md create mode 100644 docs/skills/freighter-best-practices/references/error-handling.md create mode 100644 docs/skills/freighter-best-practices/references/git-workflow.md create mode 100644 docs/skills/freighter-best-practices/references/i18n.md create mode 100644 docs/skills/freighter-best-practices/references/messaging.md create mode 100644 docs/skills/freighter-best-practices/references/performance.md create mode 100644 docs/skills/freighter-best-practices/references/security.md create mode 100644 docs/skills/freighter-best-practices/references/testing.md create mode 100644 llms.txt diff --git a/.claude/skills/freighter-best-practices b/.claude/skills/freighter-best-practices new file mode 120000 index 0000000000..c81db6d3f4 --- /dev/null +++ b/.claude/skills/freighter-best-practices @@ -0,0 +1 @@ +../../../docs/skills/freighter-best-practices \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..0ee4fdf7cb --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,169 @@ +# Freighter — AI Agent Context + +> Non-custodial Stellar wallet browser extension. Monorepo with extension, +> client SDK, shared utilities, and docs site. + +## Quick Reference + +| Item | Value | +| ----------------- | --------------------------------------------------------------- | +| Language | TypeScript, React | +| Node | >= 22 (.nvmrc) | +| Package Manager | Yarn 4.10.0 (workspaces) | +| State Management | Redux (extension) | +| Testing | Jest (unit), Playwright (e2e) | +| Linting | ESLint flat config + Prettier | +| CI | GitHub Actions (10 workflows) | +| Docs | Docusaurus at docs.freighter.app | +| Default Branch | `master` | +| Branch Convention | `type/description` (feature/, fix/, chore/, refactor/, bugfix/) | + +## Build & Test Commands + +```bash +yarn setup # Install + allow scripts +yarn start # Dev mode (all workspaces) +yarn start:extension # Extension only +yarn start:freighter-api # SDK only +yarn start:docs # Docs only +yarn build:extension # Dev build +yarn build:extension:experimental # Experimental features enabled +yarn build:extension:production # Minified production build +yarn build:extension:translations # Auto-generate translation keys +yarn test # Jest watch mode +yarn test:ci # Jest CI mode +yarn test:e2e # Playwright e2e +``` + +## Repository Structure + +``` +freighter/ +├── extension/ # Browser extension +│ ├── src/popup/ # React UI (user-facing) +│ ├── src/background/ # Service worker (signing, storage, messages) +│ ├── src/contentScript/ # Content script (dApp <-> extension bridge) +│ ├── e2e-tests/ # Playwright tests +│ └── public/ # Static assets, manifest +├── @stellar/freighter-api/ # npm SDK for dApp integration +├── @shared/ # Shared code (api, constants, helpers) +│ ├── api/ +│ ├── constants/ +│ └── helpers/ +├── docs/ # Docusaurus documentation site +├── config/ # Shared build configs (Jest, Babel, Webpack) +└── .github/ + ├── workflows/ # 10 CI/CD workflows + └── agents/ # Playwright test AI agents (generator, healer, planner) +``` + +## Architecture + +The extension has three runtime contexts that communicate via message passing: + +1. **Popup** (`extension/src/popup/`) — React app. UI layer. Dispatches actions + to background via `chrome.runtime.sendMessage`. +2. **Background** (`extension/src/background/`) — Service worker. Holds + encrypted keys, signs transactions, manages storage. Processes messages from + popup and content script. +3. **Content Script** (`extension/src/contentScript/`) — Injected into web + pages. Bridges `window.postMessage` from `@stellar/freighter-api` to + `chrome.runtime.sendMessage` for the background. + +### Build Variants + +| Variant | Command | Features | +| ------------ | ----------------------------------- | -------------------------------------------- | +| Standard | `yarn build:extension` | Everyday development, connects to dev server | +| Experimental | `yarn build:extension:experimental` | Feature-flagged features enabled | +| Production | `yarn build:extension:production` | Minified, security guardrails, no dev server | + +### Dev Server URLs + +| URL | Purpose | +| ----------------------------------- | ----------------------------------------- | +| `localhost:9000` | Extension popup | +| Extension popup `/debug` route | Blockaid debug panel (dev mode only) | +| `localhost:9000/#/integration-test` | Integration test helper (clears app data) | +| `localhost:3000` | Documentation site | + +### Hot Reload + +- **Popup changes** hot reload automatically via dev server at `localhost:9000` +- **Background/content script changes** require `yarn build:extension` + reload + extension in browser + +### Environment Variables + +Only two required for local dev — create `extension/.env`: + +```env +INDEXER_URL=https://freighter-backend-prd.stellar.org/api/v1 +INDEXER_V2_URL=https://freighter-backend-v2.stellar.org/api/v1 +``` + +## Key Conventions + +- **Navigation:** Use `navigateTo` helper + `ROUTES` enum, never hardcoded + paths. See `extension/STYLEGUIDE.MD`. +- **Translations:** All user-facing strings wrapped in `t()` from + `react-i18next`. Run `yarn build:extension:translations` to generate keys. See + `extension/LOCALIZATION.MD`. +- **Imports:** External packages first, then internal. ESLint enforces ordering. +- **Formatting:** Double quotes, 2-space indent, trailing commas, 80-char width, + semicolons. Config in `.prettierrc.yaml`. +- **Branch naming:** `type/description` — `feature/`, `fix/`, `chore/`, + `refactor/`, `bugfix/`. +- **Commit messages:** Imperative action verb + description. PR number appended + on merge. + +### Pre-commit Hooks + +Husky runs on every commit: + +1. `pretty-quick --staged` — Prettier on staged files +2. `lint-staged` — ESLint fix on staged `.ts`/`.tsx` +3. Translation build — auto-generates `extension/src/popup/locales/` files + +If using nvm, create `~/.huskyrc` to ensure correct Node version. + +## Security-Sensitive Areas + +These areas require careful review — do not modify without understanding the +security implications: + +- `extension/src/background/` — handles private keys, signing, encrypted storage +- `extension/src/contentScript/` — attack surface between dApps and extension + (postMessage) +- `extension/public/static/manifest/` — extension permissions and CSP +- `@shared/constants/` — network endpoints, key derivation parameters +- Any code touching `chrome.storage` or key material + +## Known Complexity + +- **Message passing:** Popup <-> background <-> content script communication is + async and uses typed message enums. Follow existing patterns. +- **Build variants:** Standard, experimental, and production builds have + different feature flags and security settings. +- **Blockaid integration:** Transaction scanning is wired through multiple + layers. Test with the debug panel at `localhost:9000/#/debug`. +- **Soroswap:** Asset swap integration via `popup/helpers/sorobanSwap.ts` — + three key methods (getSoroswapTokens, soroswapGetBestPath, + buildAndSimulateSoroswapTx). +- **Store submissions:** Separate CI workflows for Chrome Web Store, Firefox + AMO, Safari, and npm publishing. +- **AI agents:** Three Playwright test agents in `.github/agents/` (generator, + healer, planner) for test automation. + +## Key Documentation + +| Topic | Location | +| --------------------------- | ------------------------------------------ | +| Extension dev guide | `extension/README.md` | +| Hardware wallet integration | `extension/INTEGRATING_HARDWARE_WALLET.MD` | +| Soroswap integration | `extension/INTEGRATING_SOROSWAP.MD` | +| Localization | `extension/LOCALIZATION.MD` | +| Style guide | `extension/STYLEGUIDE.MD` | +| E2E testing | `extension/e2e-tests/README.md` | +| Changelog | `extension/CHANGELOG.MD` | +| API playgrounds | docs.freighter.app | diff --git a/docs/skills/freighter-best-practices/SKILL.md b/docs/skills/freighter-best-practices/SKILL.md new file mode 100644 index 0000000000..9fc58888a0 --- /dev/null +++ b/docs/skills/freighter-best-practices/SKILL.md @@ -0,0 +1,41 @@ +--- +name: freighter-best-practices +description: + Comprehensive best practices for the Freighter browser extension. Covers code + style, architecture, security, testing, performance, error handling, i18n, + messaging, git workflow, dependencies, and anti-patterns. Use when writing new + features, reviewing PRs, adding message handlers, working with Redux, + modifying content scripts, handling keys, building transactions, or any + development work in the freighter extension repo. Triggers on any task + touching the freighter codebase. +--- + +# Freighter Best Practices + +Freighter is a non-custodial Stellar wallet shipped as a browser extension +(Manifest V3) for Chrome, Firefox, and Safari. The repository is a Yarn +workspaces monorepo with four main workspaces: + +1. **extension/** -- the browser extension itself (popup, background service + worker, content scripts) +2. **@stellar/freighter-api/** -- the npm SDK that dApps use to interact with + Freighter +3. **@shared/** -- shared packages (api, constants, helpers) consumed by + multiple workspaces +4. **docs/** -- documentation and skills + +## Reference Guide + +| Concern | File | When to Read | +| -------------------- | ---------------------------- | -------------------------------------------------------- | +| Code Style | references/code-style.md | Writing or reviewing any code | +| Architecture | references/architecture.md | Adding features, understanding the codebase | +| Security | references/security.md | Touching keys, messages, storage, or dApp interactions | +| Testing | references/testing.md | Writing or fixing tests | +| Performance | references/performance.md | Optimizing renders, bundle size, or load times | +| Error Handling | references/error-handling.md | Adding error states, catch blocks, or user-facing errors | +| Internationalization | references/i18n.md | Adding or modifying user-facing strings | +| Messaging | references/messaging.md | Adding popup<>background<>content script communication | +| Git & PR Workflow | references/git-workflow.md | Branching, committing, opening PRs, CI | +| Dependencies | references/dependencies.md | Adding, updating, or auditing packages | +| Anti-Patterns | references/anti-patterns.md | Code review, avoiding common mistakes | diff --git a/docs/skills/freighter-best-practices/references/anti-patterns.md b/docs/skills/freighter-best-practices/references/anti-patterns.md new file mode 100644 index 0000000000..2980eebcf5 --- /dev/null +++ b/docs/skills/freighter-best-practices/references/anti-patterns.md @@ -0,0 +1,164 @@ +# Anti-Patterns -- Freighter Extension + +Common mistakes to watch for during code review and development. + +## 1. Suppressing exhaustive-deps + +```typescript +// eslint-disable-next-line react-hooks/exhaustive-deps +``` + +Found ~50 times in the codebase. This indicates tight coupling between hooks and +external state, or an intentional override of the dependency array. + +**For new code:** fix the dependency array instead of suppressing the warning. +Refactor the hook to accept stable references, or use `useCallback`/`useMemo` to +stabilize dependencies. + +**If truly needed:** add a comment explaining WHY the suppression is necessary. +A bare `eslint-disable` without explanation is not acceptable. + +## 2. Non-null Assertions on Optional Data + +```typescript +// WRONG: crashes silently if icons[asset] is undefined +const icon = icons[asset]!; +``` + +Always handle the missing case explicitly: + +```typescript +// CORRECT +const icon = icons[asset]; +if (!icon) { + return ; +} +``` + +## 3. Type Assertion Workarounds + +```typescript +// WRONG: forced cast indicates incomplete types +const payload = action?.payload as typeof action.payload & { extra?: string }; +``` + +This pattern works around incomplete type definitions. Fix the types at the +source instead of casting at the usage site. Update the action creator's return +type, the thunk's generic parameters, or the slice's state type. + +## 4. Module-Level State in Background + +```typescript +// WRONG: this variable will be lost when the MV3 worker restarts +let cachedData = {}; + +export const handler = () => { + cachedData.foo = "bar"; // lost on next restart +}; +``` + +MV3 service workers are ephemeral. Chrome can terminate and restart them at any +time. Any variable declared outside a function will be reset. + +**Use `chrome.storage.session`** for ephemeral data or +**`chrome.storage.local`** for persistent data. + +## 5. Direct chrome.runtime.sendMessage from Popup + +```typescript +// WRONG: bypasses the shared API layer +chrome.runtime.sendMessage({ type: SERVICE_TYPES.GET_DATA }); +``` + +Always use the `sendMessageToBackground()` wrapper from `@shared/api/internal`: + +```typescript +// CORRECT +import { sendMessageToBackground } from "@shared/api/internal"; +await sendMessageToBackground({ type: SERVICE_TYPES.GET_DATA }); +``` + +The wrapper provides consistent typing, response parsing, and error handling. + +## 6. Hardcoded URL Paths in Navigation + +```typescript +// WRONG +navigate("/account/send"); +``` + +Use the `ROUTES` enum and `navigateTo()` helper: + +```typescript +// CORRECT +navigateTo(ROUTES.sendPayment); +``` + +## 7. Inline Object/Function Creation in JSX Props + +```typescript +// WRONG: new object/function on every render, defeats memo() + handleSelect(asset)} +/> +``` + +Extract to `useMemo` and `useCallback`: + +```typescript +// CORRECT +const filter = useMemo(() => ({ type: "native" }), []); +const onSelect = useCallback((asset) => handleSelect(asset), [handleSelect]); + +``` + +## 8. Non-Serializable Objects in Redux Store + +```typescript +// WRONG: Stellar SDK objects contain methods and circular references +state.transaction = new Transaction(xdr); +``` + +Store serializable representations only: + +```typescript +// CORRECT: store the XDR string, reconstruct the object when needed +state.transactionXdr = transaction.toXDR(); +``` + +## 9. Silent Error Swallowing + +```typescript +// WRONG: error is completely lost +try { + await riskyOperation(); +} catch (e) { + // do nothing +} +``` + +At minimum, report to Sentry: + +```typescript +// CORRECT +try { + await riskyOperation(); +} catch (error) { + captureException(error); +} +``` + +## 10. TODO Comments Without Tracking + +```typescript +// TODO: refactor this later +``` + +If leaving a TODO, create a GitHub issue and reference it: + +```typescript +// TODO(#1234): refactor balance calculation to use BigNumber +``` + +This ensures TODOs are tracked and not forgotten. diff --git a/docs/skills/freighter-best-practices/references/architecture.md b/docs/skills/freighter-best-practices/references/architecture.md new file mode 100644 index 0000000000..1eddf9f135 --- /dev/null +++ b/docs/skills/freighter-best-practices/references/architecture.md @@ -0,0 +1,184 @@ +# Architecture -- Freighter Extension + +## Three Runtime Contexts + +Freighter runs code in three isolated contexts, each with different lifetimes +and capabilities: + +### Popup (`extension/src/popup/`) + +- A React single-page application rendered when the user clicks the extension + icon +- **Lifetime:** created when opened, destroyed when closed -- no persistent + state in memory +- Communicates with the background via `chrome.runtime.sendMessage` (wrapped by + `sendMessageToBackground()`) +- Served from `localhost:9000` during development (hot reload) + +### Background Service Worker (`extension/src/background/`) + +- A Manifest V3 service worker that handles all privileged operations (key + management, signing, storage) +- **Lifetime:** ephemeral -- Chrome may terminate it at any time after + inactivity. All state must be persisted to `chrome.storage` +- Listens for messages from the popup and content scripts via + `chrome.runtime.onMessage` +- Uses `chrome.storage.session` for decrypted keys (memory-only, cleared on + session end) and `chrome.storage.local` for the encrypted vault + +### Content Script (`extension/src/contentScript/`) + +- Injected into every tab at `document_start` +- **Lifetime:** per tab, lives as long as the tab +- Bridges dApp requests from the page to the background: listens for + `window.postMessage` from the page, forwards valid messages via + `chrome.runtime.sendMessage` +- Filters messages by source (`EXTERNAL_MSG_REQUEST`) and only forwards valid + `EXTERNAL_SERVICE_TYPES` + +## Monorepo Workspaces + +``` +freighter/ + extension/ -- The browser extension (popup, background, content script) + @stellar/freighter-api/ -- npm SDK for dApp integration + @shared/ -- Shared packages + api/ -- API helpers (internal messaging, external endpoints) + constants/ -- Shared constants and enums + helpers/ -- Utility functions + docs/ -- Documentation and skills + config/ -- Shared build configuration +``` + +## State Management Patterns + +The codebase uses two patterns depending on the scope of the state: + +### Redux Toolkit Duck Pattern (Cross-Cutting Global State) + +For state shared across multiple components or that must persist across +navigation (account data, settings, cache): + +- **`createAsyncThunk`** for all async operations (API calls, background + messages) +- **`createSlice`** for reducers with immer-based immutable updates +- **`createSelector`** (reselect) for memoized derived state -- never compute + inline in components +- **`ActionStatus` enum** tracks async lifecycle: `IDLE`, `PENDING`, `ERROR`, + `SUCCESS` + +```typescript +// Example duck structure +export const fetchAccountBalances = createAsyncThunk( + "accountBalances/fetch", + async (publicKey: string, { rejectWithValue }) => { + try { + const balances = await getBalances(publicKey); + return balances; + } catch (error) { + if (error instanceof Error) { + return rejectWithValue({ errorMessage: error.message }); + } + } + }, +); + +const accountBalancesSlice = createSlice({ + name: "accountBalances", + initialState, + reducers: { + /* sync reducers */ + }, + extraReducers: (builder) => { + builder + .addCase(fetchAccountBalances.pending, (state) => { + state.status = ActionStatus.PENDING; + }) + .addCase(fetchAccountBalances.fulfilled, (state, action) => { + state.status = ActionStatus.SUCCESS; + state.balances = action.payload; + }) + .addCase(fetchAccountBalances.rejected, (state, action) => { + state.status = ActionStatus.ERROR; + state.error = action.payload?.errorMessage; + }); + }, +}); +``` + +### useReducer + RequestState Pattern (View-Level Local State) + +For state scoped to a single view that doesn't need to persist (data-fetching +hooks for specific screens): + +- **`useReducer`** with the shared `reducer`/`initialState` from + `helpers/request.ts` +- **`RequestState` enum** from `constants/request` tracks: `IDLE`, `LOADING`, + `SUCCESS`, `ERROR` +- Composes existing hooks (e.g., `useGetCollectibles`, `useGetBalances`) for + data fetching +- Used by 8+ views: Discover, AddCollectibles, GrantAccess, SignMessage, + RecoverAccount, etc. + +```typescript +// Example: view-level data hook +import { initialState, reducer } from "helpers/request"; +import { RequestState } from "constants/request"; + +const useGetScreenData = () => { + const [state, dispatch] = useReducer(reducer, initialState); + + useEffect(() => { + dispatch({ type: "FETCH_DATA_START" }); + fetchData() + .then((data) => dispatch({ type: "FETCH_DATA_SUCCESS", payload: data })) + .catch(() => dispatch({ type: "FETCH_DATA_ERROR" })); + }, []); + + return state; +}; +``` + +**When to use which:** If the data is needed by multiple screens or must survive +navigation, use Redux. If it's a single screen's fetch-and-display lifecycle, +use `useReducer`. + +## Background Store Hydration + +The background service worker persists the Redux store to survive ephemeral +restarts: + +1. Store is saved to `chrome.storage.session` on every change via + `store.subscribe(() => saveStore(store.getState()))` +2. On startup, the store is hydrated from `chrome.storage.session` using + `REDUX_STORE_KEY` +3. Firefox fallback: uses `chrome.storage.local` where session storage is + unavailable + +## Component Patterns + +- **Functional components only** -- no class components except `ErrorBoundary` +- **View/hook separation** -- complex components split into a view (JSX) and a + hook (logic) +- **`memo()`** for component memoization. `lodash/isEqual` is available in the + codebase for deep comparisons where needed + +## Cache Duck + +A centralized cache duck manages network data: + +- Keyed by `[network][publicKey]` for per-account, per-network isolation +- 3-minute expiration enforced by `isCacheValid()` helper +- Prevents redundant API calls when switching between accounts or networks + +## Custom Middleware + +- **`metricsMiddleware`** -- dispatches analytics events for tracked actions +- **`activePublicKeyMiddleware`** -- detects account mismatch between popup and + background state + +## Navigation + +- All routes defined in `ROUTES` enum +- Navigation uses `navigateTo()` helper function +- Never hardcode route paths as strings -- always reference the enum diff --git a/docs/skills/freighter-best-practices/references/code-style.md b/docs/skills/freighter-best-practices/references/code-style.md new file mode 100644 index 0000000000..997d58340f --- /dev/null +++ b/docs/skills/freighter-best-practices/references/code-style.md @@ -0,0 +1,485 @@ +# Code Style -- Freighter Extension + +## Prettier Configuration + +The project uses Prettier (`.prettierrc.yaml`) with the following settings: + +- **Quotes:** double quotes (`singleQuote: false`) +- **Indentation:** 2 spaces (`tabWidth: 2`) +- **Print width:** 80 characters +- **Trailing commas:** all (`trailingComma: "all"`) +- **Semicolons:** always (`semi: true`) +- **Arrow parens:** always (`arrowParens: "always"` — `(x) => x`, never + `x => x`) +- **Bracket spacing:** true (`{ foo }` not `{foo}`) +- **Prose wrap:** always + +## ESLint Configuration + +Flat config (`eslint.config.js`) with: + +- **Parser:** `@typescript-eslint/parser` +- **Plugins:** React, React Hooks, JSX A11y, Import, TypeScript ESLint +- **Key rules:** + - `semi` -- error (semicolons required) + - `react-hooks/rules-of-hooks` -- error + - `react-hooks/exhaustive-deps` -- warn + - `react/prop-types` -- off (TypeScript replaces prop-types) + - `import/no-unresolved` -- warn + +## Import Ordering + +External packages first, then internal modules. Convention only — no ESLint +`import/order` rule enforces it: + +```typescript +// 1. External packages (react, redux, i18next, design system) +import React, { useState } from "react"; +import { useDispatch } from "react-redux"; +import { useTranslation } from "react-i18next"; +import { Button } from "@stellar/design-system"; + +// 2. Internal @shared/* packages +import { sendMessageToBackground } from "@shared/api/internal"; +import { SERVICE_TYPES } from "@shared/constants/services"; + +// 3. Internal popup/* modules +import { ROUTES } from "popup/constants/routes"; +import { navigateTo } from "popup/helpers/navigate"; +``` + +Note: `@shared/*` and `@stellar/*` are INTERNAL packages despite the `@` prefix. +Group them with internal imports. + +## Enum Conventions + +### Enum Naming (Two Patterns) + +The codebase uses two naming patterns for enums. Follow the established +convention: + +| Pattern | When | Examples | +| ------------------------ | ------------------------------------------------ | ------------------------------------------------------------------------------------------------- | +| **SCREAMING_SNAKE_CASE** | Constants, service types, routes, network enums | `SERVICE_TYPES`, `EXTERNAL_SERVICE_TYPES`, `ROUTES`, `NETWORKS`, `OPERATION_TYPES`, `STEPS` | +| **PascalCase** | Domain types, status enums, classification enums | `ActionStatus`, `WalletType`, `AccountType`, `RequestState`, `SecurityLevel`, `NetworkCongestion` | + +### Enum Values + +All enums use **string values** — no numeric enums in the codebase: + +```typescript +// SCREAMING_SNAKE enum with SCREAMING_SNAKE values +export enum SERVICE_TYPES { + GET_ACCOUNT_HISTORY = "GET_ACCOUNT_HISTORY", + LOAD_SETTINGS = "LOAD_SETTINGS", +} + +// PascalCase enum with PascalCase values +export enum ActionStatus { + IDLE = "IDLE", + PENDING = "PENDING", + SUCCESS = "SUCCESS", + ERROR = "ERROR", +} + +// PascalCase enum with display-name values +export enum WalletType { + LEDGER = "Ledger", +} + +// SCREAMING_SNAKE enum with kebab-case values (for CSS/URL identifiers) +export enum STEPS { + SELECT_ASSET = "select-asset", + SET_AMOUNT = "set-amount", +} +``` + +### const enum vs enum + +Never use `const enum` — all 37+ enums in the codebase are standard runtime +enums. + +### Prefer Enums Over Union Type Aliases + +When you have a finite set of named string values, use an enum, not a union +type: + +```typescript +// BAD — union type for a finite set +type SwapStatus = "pending" | "confirming" | "completed" | "failed"; +type InputType = "crypto" | "fiat"; +type AssetVisibility = "visible" | "hidden"; +type NotificationType = "warning" | "info"; + +// GOOD — enum +enum SwapStatus { + Pending = "pending", + Confirming = "confirming", + Completed = "completed", + Failed = "failed", +} +``` + +The codebase has ~4 remaining union types that should be enums (`InputType`, +`AssetVisibility`, `NotificationType`, `PillType`). Don't add more. + +## Type vs Interface + +Follow this pattern (observed in ~200+ type definitions): + +| Use | When | Example | +| --------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | +| **`interface`** | Object shapes with known properties | `interface Response { ... }`, `interface UserInfo { ... }` | +| **`type`** | Unions, intersections, aliases, mapped types | `type ExternalRequest = ExternalRequestToken \| ...`, `type MigratableAccount = Account & { keyIdIndex: number }` | + +## No Magic Numbers + +Extract all numeric literals into named SCREAMING_SNAKE_CASE constants: + +```typescript +// BAD +const fee = amount * 0.003; +if (retries > 5) { ... } +setTimeout(() => setDidSaveFail(false), 750); + +// GOOD +const FEE_RATE = 0.003; +const MAX_RETRIES = 5; +const SAVE_FEEDBACK_DELAY_MS = 750; +``` + +**Currently well-extracted:** Storage keys (34 constants in +`localStorageTypes.ts`), queue timeouts (`QUEUE_ITEM_TTL_MS`, +`CLEANUP_INTERVAL_MS`), image loader timeout (`DEFAULT_TIMEOUT_MS`). + +**Still hardcoded in codebase:** Some `setTimeout` delays (500ms debounce, 750ms +feedback) and `setInterval` values. Extract these when modifying those files. + +## No Loose Strings + +### User-Facing Strings + +All user-facing text must be wrapped in `t()` from `react-i18next`. Both English +and Portuguese translations required. + +### Error Messages + +Error messages in catch blocks are currently hardcoded strings (e.g., +`"Failed to scan site"`, `"Failed to scan transaction"`). There is no +centralized error constants file. When adding new error messages, use +descriptive string literals — but consider extracting to a constants file for +shared messages. + +### Storage Keys + +All `chrome.storage` keys must use constants from +`extension/src/constants/localStorageTypes.ts` — never hardcoded strings. + +### Action Type Strings + +The reducer action types (`"FETCH_DATA_START"`, `"FETCH_DATA_SUCCESS"`, +`"FETCH_DATA_ERROR"`) are string literals in the shared reducer. This is +acceptable for the shared `useReducer` pattern, but Redux slice action types +should always use `createSlice` (which auto-generates them). + +## Function Style + +Arrow functions dominate (~85%). Function declarations are used rarely: + +```typescript +// STANDARD — arrow function export (85% of codebase) +export const getAccountBalances = async (publicKey: string) => { ... }; +export const useNetworkFees = () => { ... }; + +// ACCEPTABLE — function declaration for complex utilities (15% of codebase) +export function cleanupQueue(responseQueue: ResponseQueue) { ... } +``` + +Both styles are acceptable. Don't refactor between them. + +## JSX Return Style + +Multi-line JSX wrapped in parentheses. Single-expression returns may omit +parens: + +```typescript +// Single expression — implicit return OK +const Loading = () =>
; + +// Multi-line — always use parentheses +const AccountHeader = () => { + return ( +
+

{t("Account")}

+ +
+ ); +}; +``` + +## Boolean Naming + +Use `is`, `has`, or `should` prefixes consistently: + +```typescript +// CORRECT +const isConnected = true; +const hasInitialized = false; +const shouldShowWarning = true; +const isMainnet = network === NETWORKS.PUBLIC; +const isHttpsDomain = url.startsWith("https"); +const hasValidMemo = memo.length > 0; + +// WRONG — missing prefix +const connected = true; // Should be isConnected +const initialized = false; // Should be hasInitialized +``` + +## Event Handler Naming + +Props use `on` prefix, internal handlers use `handle` prefix: + +```typescript +// Props: on + Event +interface Props { + onClick: () => void; + onSelect: (asset: Asset) => void; + onClose: () => void; +} + +// Internal handlers: handle + Action +const handleClick = () => { ... }; +const handleApprove = () => { ... }; +const handleTokenLookup = () => { ... }; +``` + +## Naming Conventions Summary + +| Category | Convention | Example | +| -------------------------- | -------------------------------- | ---------------------------------------- | +| Components | PascalCase directory + index.tsx | `AccountHeader/index.tsx` | +| Functions / variables | camelCase | `getAccountBalances`, `isConnected` | +| Constants | SCREAMING_SNAKE_CASE | `NETWORKS`, `QUEUE_ITEM_TTL_MS` | +| File names (helpers/hooks) | camelCase | `useNetworkFees.ts`, `getSiteFavicon.ts` | +| Enums (constants) | SCREAMING_SNAKE_CASE | `SERVICE_TYPES`, `ROUTES` | +| Enums (domain types) | PascalCase | `ActionStatus`, `SecurityLevel` | +| Booleans | `is/has/should` prefix | `isMainnet`, `hasInitialized` | +| Event props | `on` prefix | `onClick`, `onSelect` | +| Event handlers | `handle` prefix | `handleClick`, `handleApprove` | + +## Pre-commit Hooks + +Runs via Husky (`.husky/pre-commit`): + +1. **Translation build** -- `addTranslations.sh` regenerates locale files and + stages them +2. **pretty-quick --staged** -- formats staged files with Prettier + +Note: `lint-staged` config exists in `package.json` but is not invoked by the +current hook. + +## Destructuring Patterns + +Inline parameter destructuring is the dominant pattern (75% of components): + +```typescript +// PREFERRED — inline destructuring (75%) +const AccountCard = ({ balance, network, onSelect }: AccountCardProps) => { ... }; + +// ACCEPTABLE — separate destructuring (25%) +const AccountCard = (props: AccountCardProps) => { + const { balance, network } = props; +}; +``` + +For selectors, destructure the result: + +```typescript +// PREFERRED +const { balances, status } = useSelector(accountBalancesSelector); + +// LESS COMMON +const balances = useSelector((state) => state.accountBalances.balances); +``` + +Renaming during destructuring (~12% of destructuring operations): + +```typescript +const { data: balanceData, error: fetchError } = response; +``` + +## Optional Chaining and Nullish Coalescing + +The codebase uses `&&` (659 occurrences) more than `?.` (405 occurrences). Both +are acceptable: + +```typescript +// Both patterns used — pick whichever reads better +const name = account?.name; // optional chaining (405 occurrences) +const name = account && account.name; // logical AND (659 occurrences) +``` + +`??` (nullish coalescing) is underutilized (41 occurrences vs `||`). Prefer `??` +for new code when you need to preserve `0` or `""`: + +```typescript +// PREFERRED for new code +const timeout = config.timeout ?? DEFAULT_TIMEOUT; + +// LEGACY — still common but treats 0 and "" as falsy +const timeout = config.timeout || DEFAULT_TIMEOUT; +``` + +## Export Patterns + +Named exports dominate (95-97%). Default exports are rare (~17 occurrences): + +```typescript +// STANDARD — named export (97%) +export const AccountHeader = () => { ... }; +export const getBalance = async () => { ... }; + +// RARE — default export (3%, mostly layout components) +export default View; +``` + +Barrel files (index.ts re-exports) exist but are not heavily used (~15-20 +instances). Most imports go directly to source files. + +## Component Prop Types + +Separate Props interface is preferred (60%) over inline (40%): + +```typescript +// PREFERRED — separate interface (60%) +interface AccountCardProps { + balance: Balance; + network: string; + onSelect: (account: Account) => void; +} +const AccountCard = ({ balance, network, onSelect }: AccountCardProps) => { ... }; + +// ACCEPTABLE — inline for simple props (40%) +const Loading = ({ size }: { size: number }) => { ... }; +``` + +Do NOT use `React.FC` — only 17 occurrences remain (legacy). Use plain +typed params: + +```typescript +// WRONG — legacy pattern +const MyComponent: React.FC = ({ title }) => { ... }; + +// CORRECT — modern pattern +const MyComponent = ({ title }: Props) => { ... }; +``` + +## Conditional Rendering + +`&&` pattern dominates for conditional rendering (169 files): + +```typescript +// MOST COMMON — logical AND +{isLoading && } +{error && } + +// USED FOR EITHER/OR — ternary +{isLoading ? : } +``` + +Early returns for loading/error states (~15-20 files): + +```typescript +if (!data) return ; +if (error) return ; +return ; +``` + +## Array/Object Operations + +- `.map()` preferred over `.forEach()` (1.3:1 ratio, 162 vs 124 occurrences) +- Spread `{ ...obj }` overwhelmingly preferred over `Object.assign` (554 + occurrences vs <2%) +- `.reduce()` is rare (<10 files) — prefer `.map()` + `.filter()` chains +- Template literals are near-universal (99%+) — never use string concatenation + +## Async Patterns + +`async/await` is the universal pattern (>97%). `.then().catch()` is legacy +(<3%): + +```typescript +// STANDARD +const data = await fetchBalance(publicKey); + +// LEGACY — avoid for new code +fetchBalance(publicKey).then(data => { ... }).catch(e => { ... }); +``` + +`try/catch` wraps whole functions (60%) or specific operations (40%). +`Promise.allSettled` preferred over `Promise.all` for robustness. + +## Type Assertions and Generics + +Always use `as Type` syntax (TSX files forbid angle-bracket `` syntax): + +```typescript +const payload = action.payload as ErrorMessage; // CORRECT +const payload = action.payload; // WRONG in TSX +``` + +Prefer descriptive generic names over single letters: + +```typescript +// PREFERRED (50+ files) +function fetchData(url: string): Promise { ... } + +// ACCEPTABLE for simple generics (15 files) +function identity(value: T): T { ... } +``` + +Common utility types in use: `Pick<>` (30 files), `Omit<>` (40 files), +`Partial<>` (30 files), `Record<>` (15 files), `keyof`/`typeof` (167 occurrences +in 71 files). + +## TypeScript Strictness + +- `any` exists in ~330 occurrences (mostly test files). Avoid in production + code. Use `unknown` instead. +- `unknown` has low adoption (41 occurrences) — prefer it over `any` for new + code +- `as unknown as Type` double-cast: only 13 occurrences (acceptable in tests + only) + +## CSS/SCSS Patterns + +110 SCSS files using BEM naming with kebab-case: + +```scss +.AccountDetail { + &__wrapper { ... } + &__balance-info { ... } + &__action-button { ... } +} +``` + +Design system tokens: use `var(--sds-clr-*)` CSS variables and `pxToRem()` mixin +from `styles/utils.scss`. Never use raw pixel values or hardcoded colors. + +## Test File Patterns + +- File naming: always `.test.ts` / `.test.tsx` (never `.spec.ts`) +- Structure: `describe("feature")` + `it("should...")` (universal — 922 test + cases across 95 files) +- Setup: `beforeEach` in 88 of 95 test files (93%) +- Assertions: `expect().toBe()` for primitives, `expect().toEqual()` for objects + +## Comments + +Comments are intentionally sparse (~2-5 per 500 lines). Don't add unnecessary +comments. + +- **JSDoc:** Only on exported types/interfaces in shared packages +- **Inline comments:** Only for non-obvious logic +- **TODOs:** Must reference a GitHub issue (`// TODO(#1234): description`) +- **No license headers** — files begin directly with imports diff --git a/docs/skills/freighter-best-practices/references/dependencies.md b/docs/skills/freighter-best-practices/references/dependencies.md new file mode 100644 index 0000000000..c134c3b88e --- /dev/null +++ b/docs/skills/freighter-best-practices/references/dependencies.md @@ -0,0 +1,116 @@ +# Dependencies -- Freighter Extension + +## Package Manager + +- **Yarn 4.10.0** with workspaces enabled +- Node requirement: **>= 22** (specified in `.nvmrc`) +- Always use `nvm use` or equivalent before running commands to ensure the + correct Node version + +## Workspace Structure + +The root `package.json` defines 6 workspaces: + +```json +{ + "workspaces": [ + "extension", + "@stellar/freighter-api", + "@shared/api", + "@shared/constants", + "@shared/helpers", + "docs" + ] +} +``` + +## Dependency Placement + +| Dependency Type | Where to Add | Example | +| -------------------------- | ------------------------------------- | ----------------------------------------- | +| Extension runtime deps | `extension/package.json` | `react`, `redux`, `stellar-sdk` | +| freighter-api runtime deps | `@stellar/freighter-api/package.json` | Minimal, SDK-only deps | +| Shared module deps | `@shared/*/package.json` | Shared utilities | +| Dev tooling (shared) | Root `package.json` | `typescript`, `eslint`, `webpack`, `jest` | +| Docs | `docs/package.json` | Documentation tooling | + +## @shared Modules + +Shared packages are imported as workspace dependencies. Yarn resolves them via +the workspaces configuration: + +```json +// extension/package.json +{ + "dependencies": { + "@shared/api": "1.0.0", + "@shared/constants": "1.0.0", + "@shared/helpers": "1.0.0" + } +} +``` + +In code, import directly: + +```typescript +import { sendMessageToBackground } from "@shared/api/internal"; +import { SERVICE_TYPES } from "@shared/constants/services"; +``` + +## Adding Dependencies + +1. Identify the correct workspace (see table above) +2. Add from the workspace directory: + +```bash +cd extension && yarn add some-package +# or from root: +yarn workspace extension add some-package +``` + +3. For dev dependencies shared across workspaces, add to root: + +```bash +yarn add -D some-dev-tool +``` + +## Lavamoat + +Lavamoat provides a script allowlist in `package.json` for supply-chain +security. It restricts which packages can run install scripts (postinstall, +preinstall, etc.). When adding a package that requires install scripts, you may +need to update the Lavamoat configuration. + +## npmMinimalAgeGate + +The `.yarnrc.yml` file includes: + +```yaml +npmMinimalAgeGate: 7d +``` + +This means newly published packages must be at least 7 days old before Yarn will +allow installation. This protects against supply-chain attacks using freshly +published malicious packages. If you need to install a package published less +than 7 days ago, you will need to wait or temporarily override this setting +(with team approval). + +## Upgrading Dependencies + +1. Run `yarn install` to resolve and update the lockfile +2. Verify workspace symlinks are intact (`@shared/*` packages resolve correctly) +3. Build all workspaces -- some depend on others: + +```bash +yarn build # builds all workspaces in dependency order +``` + +4. Run the full test suite to verify nothing broke: + +```bash +yarn test:ci +yarn test:e2e +``` + +5. Check for breaking changes in changelogs, especially for `stellar-sdk`, + `react`, and `webpack` diff --git a/docs/skills/freighter-best-practices/references/error-handling.md b/docs/skills/freighter-best-practices/references/error-handling.md new file mode 100644 index 0000000000..dafffa537b --- /dev/null +++ b/docs/skills/freighter-best-practices/references/error-handling.md @@ -0,0 +1,100 @@ +# Error Handling -- Freighter Extension + +## Async Thunks + +All async thunks must catch errors and use `rejectWithValue` to propagate them +through Redux: + +```typescript +export const submitTransaction = createAsyncThunk( + "transactionSubmission/submit", + async (xdr: string, { rejectWithValue }) => { + try { + const result = await sendTransaction(xdr); + return result; + } catch (error) { + if (error instanceof Error) { + return rejectWithValue({ errorMessage: error.message }); + } + return rejectWithValue({ errorMessage: "Unknown error" }); + } + }, +); +``` + +Handle rejected cases in extra reducers: + +```typescript +builder.addCase(submitTransaction.rejected, (state, action) => { + state.status = ActionStatus.ERROR; + state.error = (action.payload as ErrorMessage)?.errorMessage; +}); +``` + +## Background Handlers + +Background message handlers must never throw. Always return structured response +objects: + +```typescript +// CORRECT: return result or error objects +export const handleGetBalance = async (publicKey: string) => { + try { + const balance = await fetchBalance(publicKey); + return { result: balance }; + } catch (error) { + captureException(error); + return { error: "Failed to fetch balance" }; + } +}; + +// WRONG: throwing from a handler +export const handleGetBalance = async (publicKey: string) => { + const balance = await fetchBalance(publicKey); // throws on failure + return balance; +}; +``` + +Use `captureException()` from Sentry for unexpected errors that should be +tracked. + +## ErrorBoundary + +The popup wraps its component tree in a class-based `ErrorBoundary` at +`popup/components/ErrorBoundary/`: + +- Implements `getDerivedStateFromError` to catch render errors +- Implements `componentDidCatch` to report errors to Sentry +- Displays a fallback UI when a render crash occurs +- This is the only class component in the codebase -- React does not support + error boundaries as functional components + +## Network Errors + +Horizon transaction submission can return specific error codes that need +user-friendly mapping: + +| Horizon Error Code | Meaning | +| --------------------- | ------------------------------------------ | +| `op_underfunded` | Insufficient balance for the operation | +| `tx_insufficient_fee` | Fee too low for current network conditions | +| `op_no_destination` | Destination account does not exist | + +These are parsed by the `getResultCodes()` helper and mapped to translated +user-facing messages. + +## Error Type Hierarchy + +- **`FreighterApiDeclinedError`** -- thrown when the user declines a dApp + request (signing, access). This is an expected error, not a bug. +- **`ErrorMessage` type** -- `{ errorMessage: string }` used as the payload for + thunk rejections via `rejectWithValue`. + +## Sentry Integration + +- `captureException()` for unexpected errors in handlers and async operations +- Error boundaries catch and report render crashes automatically +- Do not call `captureException()` for expected errors (user cancellation, + invalid input) +- Never swallow errors silently -- at minimum, report to Sentry if the error is + unexpected diff --git a/docs/skills/freighter-best-practices/references/git-workflow.md b/docs/skills/freighter-best-practices/references/git-workflow.md new file mode 100644 index 0000000000..1b14a9cd03 --- /dev/null +++ b/docs/skills/freighter-best-practices/references/git-workflow.md @@ -0,0 +1,76 @@ +# Git & PR Workflow -- Freighter Extension + +## Main Branch + +The primary branch is `master`. All feature work branches from and merges back +to `master`. + +## Branch Naming + +Use descriptive prefixes: + +| Prefix | Use Case | Example | +| ---------- | -------------------------- | ------------------------------ | +| `feature/` | New functionality | `feature/token-swap-ui` | +| `fix/` | Bug fixes | `fix/balance-display-rounding` | +| `chore/` | Maintenance, config, deps | `chore/upgrade-webpack` | +| `bugfix/` | Alternative bug fix prefix | `bugfix/content-script-race` | + +## Commit Messages + +- Start with an action verb in present tense: **Add**, **Fix**, **Update**, + **Improve**, **Remove**, **Refactor** +- Keep the subject line concise (under 72 characters) +- PR number is auto-added by GitHub on squash merge + +``` +Add token swap confirmation dialog +Fix balance rounding for very small amounts +Update webpack to v5.90 +Refactor background message handler registration +``` + +## Pull Request Expectations + +- **Focused scope** -- one concern per PR. Split large changes into stacked PRs. +- **Screenshots** for any UI changes (before/after) +- **Self-review** -- review your own diff before requesting reviews +- **Tests** for new features and bug fixes +- **Cross-browser testing** -- verify in both Chrome and Firefox +- **Translations updated** -- run `yarn build:extension:translations` and commit + locale changes + +## CI on Pull Requests + +Two workflows run automatically on every PR: + +1. **`runTests.yml`** -- runs Jest unit tests and Playwright e2e tests +2. **`codeql.yml`** -- runs CodeQL security analysis + +Both must pass before merging. + +## Release Process + +Releases are managed by the `newRelease.yml` GitHub Actions workflow: + +1. Manual dispatch with `appVersion` parameter (e.g., `5.12.0`) +2. Workflow creates a release branch and version tag +3. Bumps `package.json` version and `manifest.json` version +4. Creates a GitHub release with changelog + +## Release Branches + +| Branch | Purpose | +| ------------------- | ------------------------------------------------------------ | +| `release` | Auto-created by the release workflow for the current release | +| `emergency-release` | Hotfix branch for critical production issues | +| `v{X.Y.Z}` | Version tag branches for historical reference | + +## Submission Workflows + +Separate workflows handle submission to each distribution channel: + +- **Chrome Web Store** -- uploads the extension to the Chrome Web Store +- **Firefox AMO** -- submits to Firefox Add-ons +- **Safari** -- builds and submits the Safari version +- **npm** -- publishes `@stellar/freighter-api` to the npm registry diff --git a/docs/skills/freighter-best-practices/references/i18n.md b/docs/skills/freighter-best-practices/references/i18n.md new file mode 100644 index 0000000000..84813572b2 --- /dev/null +++ b/docs/skills/freighter-best-practices/references/i18n.md @@ -0,0 +1,83 @@ +# Internationalization -- Freighter Extension + +## Framework + +Freighter uses `react-i18next` for internationalization: + +- Auto-detects the browser's language setting +- Falls back to English when a translation is not available +- String keys use the English text directly, making them greppable in the + codebase + +## Locale Files + +Translation JSON files live in `extension/src/popup/locales/` with one file per +language: + +``` +extension/src/popup/locales/ + en/ + translation.json + pt/ + translation.json +``` + +## Adding New Strings + +1. Wrap the string in `t()` from the `useTranslation` hook: + +```typescript +import { useTranslation } from "react-i18next"; + +const MyComponent = () => { + const { t } = useTranslation(); + return

{t("Send Payment")}

; +}; +``` + +2. Add the string to ALL language JSON files (currently English and Portuguese) + +3. Run the translation build to verify: + +```bash +yarn build:extension:translations +``` + +## Auto-Generation + +The command `yarn build:extension:translations` scans the codebase for `t()` +calls and generates missing keys in locale files. Missing translations default +to the English text. + +## Pre-commit Hook + +The translation build runs automatically as a pre-commit hook: + +1. Scans for new or modified `t()` calls +2. Regenerates locale JSON files +3. Stages the updated locale files for the commit + +This ensures translations are never out of sync with the code. + +## Testing Translations + +To verify translations locally: + +1. Change the browser's language setting (e.g., to Portuguese) +2. Clear `react-i18next` localStorage cache +3. Refresh the extension popup +4. Verify all strings display in the target language + +## Rules + +- Every user-facing string must be wrapped in `t()` +- Every string must have both English (`en`) and Portuguese (`pt`) translations +- The custom lint rule `i18n-pt-en` enforces that both translations exist +- Never use template literals or string concatenation for user-facing text -- + use `t()` with interpolation instead: + +```typescript +// CORRECT +t("Send {{amount}} XLM", { amount: "100" })// WRONG +`Send ${amount} XLM`; +``` diff --git a/docs/skills/freighter-best-practices/references/messaging.md b/docs/skills/freighter-best-practices/references/messaging.md new file mode 100644 index 0000000000..f742de1697 --- /dev/null +++ b/docs/skills/freighter-best-practices/references/messaging.md @@ -0,0 +1,169 @@ +# Messaging -- Freighter Extension + +## Message Flow Architecture + +``` +dApp (web page) + | + | window.postMessage (EXTERNAL_MSG_REQUEST) + v +@stellar/freighter-api (injected SDK) + | + | window.postMessage + v +Content Script (extension/src/contentScript/) + | + | chrome.runtime.sendMessage (EXTERNAL_SERVICE_TYPES) + v +Background Service Worker (extension/src/background/) + ^ | + | | Opens approval window (if needed) + | v + | Approval Popup + | | + | chrome.runtime.sendMessage (response) + | + v +Popup (extension/src/popup/) + | + | sendMessageToBackground() --> chrome.runtime.sendMessage (SERVICE_TYPES) + v +Background Service Worker +``` + +## Two Message Type Enums + +- **`SERVICE_TYPES`** -- messages from the popup to the background (internal + extension communication) +- **`EXTERNAL_SERVICE_TYPES`** -- messages from dApps to the background via the + content script (external communication) + +Both enums are defined in `@shared/constants/` and must be used as the single +source of truth for message types. + +## Adding a New Message Type + +### Step 1: Add to the Enum + +Add the new type to `SERVICE_TYPES` (for popup-to-background) or +`EXTERNAL_SERVICE_TYPES` (for dApp-to-background): + +```typescript +// @shared/constants/services.ts +export enum SERVICE_TYPES { + // ... existing types + GET_NEW_DATA = "GET_NEW_DATA", +} +``` + +### Step 2: Create a Handler + +Create a handler function in +`extension/src/background/messageListener/handlers/`: + +```typescript +// extension/src/background/messageListener/handlers/getNewData.ts +export const handleGetNewData = async (request: MessageRequest) => { + try { + const data = await fetchNewData(request.params); + return { result: data }; + } catch (error) { + captureException(error); + return { error: "Failed to fetch data" }; + } +}; +``` + +### Step 3: Register in the Listener + +Register the handler in the background message listener: + +```typescript +case SERVICE_TYPES.GET_NEW_DATA: + return handleGetNewData(request); +``` + +### Step 4: Send from the Popup + +Use `sendMessageToBackground()` from `@shared/api/internal`: + +```typescript +import { sendMessageToBackground } from "@shared/api/internal"; +import { SERVICE_TYPES } from "@shared/constants/services"; + +const response = await sendMessageToBackground({ + type: SERVICE_TYPES.GET_NEW_DATA, + params: { + /* ... */ + }, +}); +``` + +## Response Queue Pattern + +For operations requiring user approval (signing, connecting): + +1. Background generates a unique ID via `crypto.randomUUID()` +2. A promise resolver is stored in a response queue array, keyed by the UUID +3. Background opens the approval window with the UUID as a URL parameter +4. When the user responds, the approval window sends the result back +5. The matching resolver is found by UUID, called with the result, and spliced + from the array +6. Timeout cleanup removes stale entries if the user closes the window without + responding + +## Response Structure (CRITICAL) + +Handlers MUST return structured objects rather than throwing. Follow this exact +pattern: + +### Success Response + +Return domain-specific fields WITHOUT an `error` field: + +```typescript +// CORRECT success responses +return { signedTransaction: "XDR..." }; +return { publicKey: "G...", allAccounts: [...] }; +return { preparedTransaction: xdr, simulationResponse }; +``` + +### Error Response + +Return ONLY `{ error: string }` — no domain-specific fields mixed in: + +```typescript +// CORRECT error response +return { error: "Soroban simulation failed" }; + +// WRONG — mixing error with domain fields +return { error: "Soroban simulation failed", simulationResponse }; // DON'T DO THIS +``` + +### Why This Matters + +The SDK and popup code check for the presence of `error` to determine +success/failure. Mixing error with data fields creates ambiguous responses. + +### Exception: apiError for SDK consumers + +When the caller is `@stellar/freighter-api`, include `apiError` alongside +`error` for detailed diagnostics: + +```typescript +return { + error: "User declined", + apiError: "User rejected the transaction request", +}; +``` + +## Shared API Layer + +The `sendMessageToBackground()` function is the only approved way to send +messages from the popup to the background. It is defined in +`@shared/api/helpers/extensionMessaging.ts` and re-exported from +`@shared/api/internal.ts`: + +- Wraps `browser.runtime.sendMessage` with proper typing +- Handles response parsing and error extraction +- Never call `browser.runtime.sendMessage` directly from popup code diff --git a/docs/skills/freighter-best-practices/references/performance.md b/docs/skills/freighter-best-practices/references/performance.md new file mode 100644 index 0000000000..2677759f8f --- /dev/null +++ b/docs/skills/freighter-best-practices/references/performance.md @@ -0,0 +1,241 @@ +# Performance -- Freighter Extension + +Based on analysis of 224 component files. Current performance score: **5.9/10**. + +## CRITICAL: Complete Implementations First + +**Performance patterns are optimizations WITHIN complete implementations, not +replacements.** + +When asked to create a component that needs derived state: + +1. **First** produce the complete React component with UI, props interface, and + rendering logic +2. **Then** apply performance optimizations (createSelector, useMemo, + useCallback, React.memo) + +```typescript +// WRONG — only selectors, no component +export const selectTotalBalance = createSelector([...], ...); +// Missing: the actual component that USES these selectors + +// CORRECT — complete implementation with performance optimizations +// 1. Memoized selectors (performance layer) +const selectTotalBalance = createSelector([balancesSelector], (balances) => ...); + +// 2. Props interface (type safety) +interface DashboardProps { + title: string; +} + +// 3. Complete component (UI layer) +export const Dashboard = ({ title }: DashboardProps) => { + const totalBalance = useSelector(selectTotalBalance); + const formattedBalance = useMemo(() => formatBalance(totalBalance), [totalBalance]); + + return ( +
+

{title}

+ {formattedBalance} +
+ ); +}; +``` + +**Never produce only selectors when a component is requested.** + +## React.memo -- CRITICAL GAP + +Only **1 memo() usage** in the entire popup codebase (`AccountAssets/AssetIcon` +with custom `shouldAssetIconSkipUpdate` comparator). This is a critical +performance gap. + +**RULE: Components rendered in lists or receiving frequently-changing parent +props MUST use `React.memo()`.** + +Components that should be memoized: + +- All list item components (TokenList items, AssetRows, OperationsKeyVal) +- AccountTabs, AccountHeader sub-components +- ManageAssetRows children + +```typescript +// REQUIRED for list-rendered components +export const AssetRow = memo( + ({ asset, onSelect }: AssetRowProps) => { ... }, + (prev, next) => isEqual(prev.asset, next.asset), +); +``` + +## useMemo -- 13 occurrences across 7 files (GOOD) + +Currently used for: + +- Conditional computations (muxed checks, contract validation) +- Debounced function creation +- Object creation in dependencies + +**RULE: Wrap in useMemo when:** + +1. Computing derived values from expensive operations (parsing, formatting, + asset lookups) +2. Creating objects/arrays that are passed as props to memoized children +3. Values used as useEffect dependencies that would otherwise change every + render + +```typescript +// REQUIRED — computed value passed as prop +const formattedBalances = useMemo( + () => balances.map((b) => formatBalance(b, network)), + [balances, network], +); + +// NOT NEEDED — simple primitive +const isMainnet = network === NETWORKS.PUBLIC; +``` + +## useCallback -- 15 occurrences across 9 files (MODERATE) + +**RULE: ANY callback passed as a prop MUST be wrapped in useCallback.** + +```typescript +// REQUIRED — passed to child component +const handleSelect = useCallback( + (asset: Asset) => { dispatch(selectAsset(asset)); }, + [dispatch], +); + +// CRITICAL ANTI-PATTERN — 130 inline arrows found in codebase +// WRONG: + handleClick(asset)} /> + +// CORRECT: +const onClick = useCallback(() => handleClick(asset), [asset]); + +``` + +**High-priority files needing useCallback refactoring:** + +- `AccountAssets/index.tsx` — 10+ inline handlers +- `ManageAssetRows/ChangeTrustInternal/index.tsx` — 10 inline handlers +- `AccountHeader/index.tsx` — 10+ inline handlers + +## Inline Functions in JSX -- CRITICAL (130 occurrences) + +**130 inline arrow functions** across 54 files. **15 inline style objects** +across 8 files. + +**RULE: Never create inline functions inside .map() or list renders. Extract to +useCallback.** + +```typescript +// WRONG — creates new function per render per item +{balances.map((b) => ( + handleClick(b.canonical)} key={b.canonical} /> +))} + +// CORRECT +const handleRowClick = useCallback((canonical: string) => { + handleClick(canonical); +}, [handleClick]); + +{balances.map((b) => ( + +))} +``` + +## Selector Memoization -- createSelector in 4 files (GOOD) + +`createSelector` from reselect is used in `remoteConfig.ts`, `settings.ts`, +`cache.ts`, `accountServices.ts`. 161 `useSelector` calls across 81 files. + +**RULE: All derived/computed state MUST use `createSelector`. Never compute +inline in components.** + +```typescript +// CORRECT — memoized selector +export const selectFormattedBalances = createSelector( + [selectBalances, selectNetwork], + (balances, network) => balances.map((b) => formatBalance(b, network)), +); + +// WRONG — recomputes every render +const formatted = balances.map((b) => formatBalance(b, network)); +``` + +## useEffect Dependencies -- 48 eslint-disable-next-line + +48 `react-hooks/exhaustive-deps` suppressions across the codebase. + +**RULE: Every eslint-disable for exhaustive-deps MUST have a comment explaining +WHY.** + +```typescript +// ACCEPTABLE — documented intentional mount-only effect +useEffect(() => { + fetchInitialData(); + // eslint-disable-next-line react-hooks/exhaustive-deps — intentional mount-only fetch +}, []); + +// WRONG — bare suppression +useEffect(() => { + fetchData(publicKey, network); + // eslint-disable-next-line react-hooks/exhaustive-deps +}, [publicKey]); +``` + +## Code Splitting -- 0 React.lazy (MISSED OPPORTUNITY) + +No `React.lazy` or `Suspense` usage. + +**RULE: Lazy-load components not needed on initial render:** + +- Debug views (`Debug.tsx`, `IntegrationTest.tsx`) +- Heavy modal/drawer components +- Detail views that load on navigation + +```typescript +const Debug = React.lazy(() => import("popup/views/Debug")); + +// In router: +}> + } /> + +``` + +## Context Usage -- EXCELLENT (3 contexts only) + +Only 3 React contexts exist (`InputWidthContext`, `AccountTabsContext`, `View`). +All hold simple primitive values. Do not add more contexts for data state — use +Redux. + +## Key Props -- 4 index-as-key anti-patterns + +4 files use array index as key. Fix these: + +- `GrantAccess/index.tsx` +- `SignMessage/index.tsx` +- `TransactionDetail/index.tsx` +- `ConfirmMnemonicPhrase/index.tsx` + +**RULE: Use stable unique identifiers as keys, not array indices.** + +## useReducer + Redux Dispatch Separation + +For complex async flows, separate local UI loading state from global Redux +state: + +- Use `useReducer` for local component state (loading spinners, form validation, + step tracking) +- Use Redux dispatch for global state changes (account data, network state, + transaction results) + +## Performance Priority Actions + +| Priority | Action | Impact | +| -------- | ------------------------------------------------- | ---------------------------- | +| **P0** | Add React.memo() to list item components | 30-40% fewer re-renders | +| **P0** | Convert 130 inline arrow functions to useCallback | Stabilize reference equality | +| **P1** | Add React.lazy for debug/modal views | Reduce initial bundle | +| **P1** | Document all 48 exhaustive-deps suppressions | Prevent future bugs | +| **P2** | Add useMemo for computed values in map() renders | Prevent recomputation | diff --git a/docs/skills/freighter-best-practices/references/security.md b/docs/skills/freighter-best-practices/references/security.md new file mode 100644 index 0000000000..69155ff0cd --- /dev/null +++ b/docs/skills/freighter-best-practices/references/security.md @@ -0,0 +1,112 @@ +# Security -- Freighter Extension + +## Key Storage Architecture + +Freighter is a non-custodial wallet. Private keys never leave the device. + +- **Encrypted vault:** stored in `chrome.storage.local`, encrypted with the + user's password +- **Decrypted keys:** held only in `chrome.storage.session` (memory-only, + cleared when the browser session ends) +- **Keys exist only in the background context** -- the popup and content scripts + never have access to raw key material + +## Popup Never Sees Raw Keys + +The popup sends unsigned XDR to the background and receives signed XDR back. The +signing flow: + +1. Popup builds an unsigned transaction (XDR) +2. Popup sends XDR to background via `sendMessageToBackground()` +3. Background decrypts the key from session storage, signs the XDR +4. Background returns the signed XDR to the popup +5. Popup submits the signed transaction to the network + +## Hardware Wallet Path + +Hardware wallets (Ledger) use the same interface as software signing. The +background detects the key type and routes to the appropriate signing backend +(local key vs. hardware device). The popup code is identical for both paths. + +## Message Validation + +Every message received by the background must pass validation. The required +checks depend on data sensitivity: + +1. **Valid message type** -- the message type must be a member of the + `SERVICE_TYPES` or `EXTERNAL_SERVICE_TYPES` enum (all messages) +2. **Sender origin check** -- verify the message comes from a trusted source + (sensitive endpoints only) +3. **Allow list check** -- the dApp's origin must be in the user's approved + list, or trigger an approval prompt (sensitive endpoints only) +4. **Public key existence** -- the requested account must exist in the wallet + (user-specific endpoints only) + +### Allowlist Rules by Endpoint Type + +| Endpoint Type | Allowlist Check | Examples | +| --------------------------------------------- | --------------- | -------------------------------------------------------------------- | +| Read-only non-sensitive (network info) | NOT required | `requestNetwork`, `requestNetworkDetails`, `requestConnectionStatus` | +| User-specific sensitive (public key, signing) | Required | `requestPublicKey`, `requestAccess`, `signTransaction` | +| State-modifying | Required | `setAllowedStatus` | + +## Window-Based Approval Flow + +When a dApp requests a sensitive operation (signing, account access): + +1. Background generates a unique request ID via `crypto.randomUUID()` +2. Background opens a new browser window with the approval UI, passing params + via URL encoding +3. A promise resolver is stored in a response queue, keyed by the request ID +4. When the user approves or rejects, the approval window sends the response + back +5. The promise resolves with the result, and the entry is spliced from the + response queue + +## Content Script Filtering + +The content script acts as a gatekeeper between the page and the extension: + +- Listens for `window.postMessage` events from the page +- Filters by source: only messages with `EXTERNAL_MSG_REQUEST` source are + processed +- Validates the message type against `EXTERNAL_SERVICE_TYPES` enum +- Only valid messages are forwarded to the background via + `chrome.runtime.sendMessage` +- All other messages are silently dropped + +## Content Security Policy + +Manifest V3 enforces strict CSP by default: + +- No inline scripts (`script-src 'self'`) +- No `eval()` or dynamic code generation +- Minimal permissions: only `storage` and `alarms` are declared in the manifest + +## Blockaid Integration + +- Every dApp transaction is scanned by Blockaid before signing +- Blockaid provides risk assessment (malicious, benign, unknown) +- Debug panel available at the `/debug` route in the extension popup during + development (requires dev mode) +- Scan results are displayed to the user in the approval UI + +## Production Guardrails + +- Production builds block connections to the dev server +- Additional security checks are enabled in production mode +- Source maps are not shipped in production builds + +## Common Security Mistakes to Avoid + +- **Logging keys:** never log private keys, mnemonics, or decrypted key material + to the console +- **Weakening CSP:** never add `unsafe-inline` or `unsafe-eval` to the manifest + CSP +- **Trusting page content:** never use data from `window.postMessage` without + validation against the enum +- **Non-null assertions on key material:** `privateKey!` can mask missing keys + -- always handle the undefined case explicitly +- **Storing secrets in local storage:** `chrome.storage.local` persists across + sessions and is not memory-only. Use `chrome.storage.session` for decrypted + material diff --git a/docs/skills/freighter-best-practices/references/testing.md b/docs/skills/freighter-best-practices/references/testing.md new file mode 100644 index 0000000000..1b388e65d5 --- /dev/null +++ b/docs/skills/freighter-best-practices/references/testing.md @@ -0,0 +1,91 @@ +# Testing -- Freighter Extension + +## Jest Unit Tests + +- Test files live in `__tests__/` directories alongside the source they test +- Environment: JSDOM (`jest-fixed-jsdom`) +- Coverage collected from `src/**/*.{ts,tsx,mjs}` +- Configuration in the root `jest.config.js` + +### Mocking Pattern + +Use typed mocks via `jest.requireMock` for type-safe mock access: + +```typescript +const { someFunction } = jest.requireMock< + typeof import("@shared/api/internal") +>("@shared/api/internal"); +``` + +Factory functions create consistent test fixtures: + +- `makeStore()` -- creates a Redux store with test-specific initial state +- `store.getState()` -- standard Redux method to get a snapshot of the current + store state + +### Testing Redux + +1. Create a test store with `configureStore()` and the real reducers +2. Dispatch thunks against the test store +3. Assert state changes via `store.getState()` + +```typescript +const store = makeStore({ + preloadedState: { + /* ... */ + }, +}); +await store.dispatch(fetchAccountBalances("GABC...")); +expect(store.getState().accountBalances.status).toBe(ActionStatus.SUCCESS); +``` + +### Testing Background Handlers + +Call handler functions directly with mock parameters: + +```typescript +const response = await handleSignTransaction({ + transactionXdr: "AAAA...", + publicKey: "GABC...", +}); +expect(response).toEqual({ signedTransaction: "signed-xdr" }); +expect(captureException).not.toHaveBeenCalled(); +``` + +## Playwright End-to-End Tests + +- **Browser:** Chromium only +- **Viewport:** 1280x720 +- **Timeout:** 15 seconds per test +- **Retries:** 5 +- **Workers:** 8 locally, 4 in CI + +### Fixtures + +The `test-fixtures.ts` file provides: + +- A browser context with the extension loaded +- Extension ID extraction from the service worker URL +- Direct access to the service worker for background testing + +### Snapshot Testing + +- Snapshots stored in `[testName].test.ts-snapshots/` +- Update snapshots with `--update-snapshots` flag + +## Running Tests + +| Command | Description | +| ------------------------- | ------------------------------------ | +| `yarn test` | Run Jest in watch mode | +| `yarn test:ci` | Run Jest for CI (no watch, coverage) | +| `yarn test:e2e` | Run all Playwright e2e tests | +| `yarn test:e2e --headed` | Run e2e with visible browser | +| `yarn test:e2e --ui` | Run e2e with Playwright UI mode | +| `PWDEBUG=1 yarn test:e2e` | Run e2e with Playwright debugger | + +## CI Pipeline + +- **`runTests.yml`** -- runs Jest unit tests and Playwright e2e tests on every + pull request +- **`codeql.yml`** -- runs CodeQL security analysis on every pull request diff --git a/llms.txt b/llms.txt new file mode 100644 index 0000000000..178f70b251 --- /dev/null +++ b/llms.txt @@ -0,0 +1,34 @@ +# Freighter + +> Non-custodial Stellar wallet — browser extension for Chrome, Firefox, and Safari + +## Documentation +- [User Guides](https://docs.freighter.app/docs/guide/introduction) +- [API Playgrounds](https://docs.freighter.app/docs/playground/getAddress) +- [Extension Dev Guide](./extension/README.md) +- [E2E Testing Guide](./extension/e2e-tests/README.md) + +## Development +- [Getting Started](./README.md) +- [Contributing](./CONTRIBUTING.MD) +- [Style Guide](./extension/STYLEGUIDE.MD) +- [Localization](./extension/LOCALIZATION.MD) + +## API Reference +- [@stellar/freighter-api SDK](./@stellar/freighter-api/README.md) +- [API Changelog](./@stellar/freighter-api/CHANGELOG.MD) + +## Integration Guides +- [Hardware Wallet Integration](./extension/INTEGRATING_HARDWARE_WALLET.MD) +- [Soroswap Integration](./extension/INTEGRATING_SOROSWAP.MD) + +## Architecture +- [AI Agent Context](./CLAUDE.md) +- [Extension Changelog](./extension/CHANGELOG.MD) + +## Key Concepts +- Monorepo: extension, SDK (@stellar/freighter-api), shared utils (@shared), docs (Docusaurus) +- Extension runtime: popup (React UI), background (service worker), content script (dApp bridge) +- State: Redux +- Testing: Jest (unit) + Playwright (e2e) +- Build: Webpack, Yarn workspaces, Node >= 22 From b747526de1802ec6cd1e2903e3c726168fb02a02 Mon Sep 17 00:00:00 2001 From: leofelix077 Date: Thu, 9 Apr 2026 14:58:01 -0300 Subject: [PATCH 2/4] Adjust code review and update agentic files best practices --- AGENTS.md | 121 ++++++++++++++++++ CLAUDE.md | 9 +- .../references/i18n.md | 4 +- 3 files changed, 129 insertions(+), 5 deletions(-) create mode 100644 AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..cb5c2a1fcf --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,121 @@ +# AGENTS.md + +> Freighter -- Non-custodial Stellar wallet browser extension (Chrome, Firefox, +> Safari). Monorepo with extension, client SDK, shared utilities, and docs site. + +## Setup commands + +- Install deps: `yarn setup` +- Start all workspaces: `yarn start` +- Start extension only: `yarn start:extension` +- Build extension (dev): `yarn build:extension` +- Build extension (production): `yarn build:extension:production` +- Build translations: `yarn build:extension:translations` +- Run unit tests: `yarn test` +- Run unit tests (CI): `yarn test:ci` +- Run e2e tests: `yarn test:e2e` + +## Environment setup + +Create `extension/.env`: + +```env +INDEXER_URL=https://freighter-backend-prd.stellar.org/api/v1 +INDEXER_V2_URL=https://freighter-backend-v2.stellar.org/api/v1 +``` + +Requires Node >= 22 (.nvmrc), Yarn 4.10.0 (workspaces). + +## Code style + +- Double quotes, 2-space indent, trailing commas, 80-char width, semicolons +- Config in `.prettierrc.yaml` +- ESLint flat config (`eslint.config.js`) +- Named exports preferred (97%), arrow functions dominate (85%) +- For detailed rules: + `docs/skills/freighter-best-practices/references/code-style.md` + +## Pre-commit hooks + +Husky runs on every commit: + +1. `./.husky/addTranslations.sh` -- auto-generates + `extension/src/popup/locales/` +2. `pretty-quick --staged` -- Prettier on staged files + +## Testing instructions + +- Unit tests: `yarn test` (Jest, JSDOM environment) +- E2E tests: `yarn test:e2e` (Playwright, Chromium only) +- Test files use `.test.ts` / `.test.tsx` in `__tests__/` directories +- For detailed patterns: + `docs/skills/freighter-best-practices/references/testing.md` + +## PR instructions + +- Branch naming: `type/description` -- `feature/`, `fix/`, `chore/`, + `refactor/`, `bugfix/` +- Commit messages: imperative action verb + description +- Default branch: `master` +- Run `yarn test:ci` and `yarn build:extension` before pushing +- For detailed workflow: + `docs/skills/freighter-best-practices/references/git-workflow.md` + +## Security considerations + +These areas require careful review: + +- `extension/src/background/` -- private keys, signing, encrypted storage +- `extension/src/contentScript/` -- attack surface between dApps and extension +- `extension/public/static/manifest/` -- extension permissions and CSP +- `@shared/constants/` -- network endpoints, key derivation parameters +- Any code touching `chrome.storage` or key material +- For detailed rules: + `docs/skills/freighter-best-practices/references/security.md` + +## Architecture + +Three runtime contexts communicate via message passing: + +1. **Popup** (`extension/src/popup/`) -- React UI, dispatches to background +2. **Background** (`extension/src/background/`) -- service worker, keys, + signing, storage +3. **Content Script** (`extension/src/contentScript/`) -- bridges dApps to + extension + +State: Redux Toolkit (createSlice, createAsyncThunk, createSelector). + +For detailed architecture: +`docs/skills/freighter-best-practices/references/architecture.md` + +## Best practices entry points + +Detailed best practices are split by concern area. Read the relevant file when +working in that domain: + +| Concern | Entry Point | When to Read | +| -------------------- | ------------------------------------------------------------------- | -------------------------------------------------------- | +| Code Style | `docs/skills/freighter-best-practices/references/code-style.md` | Writing or reviewing any code | +| Architecture | `docs/skills/freighter-best-practices/references/architecture.md` | Adding features, understanding the codebase | +| Security | `docs/skills/freighter-best-practices/references/security.md` | Touching keys, messages, storage, or dApp interactions | +| Testing | `docs/skills/freighter-best-practices/references/testing.md` | Writing or fixing tests | +| Performance | `docs/skills/freighter-best-practices/references/performance.md` | Optimizing renders, bundle size, or load times | +| Error Handling | `docs/skills/freighter-best-practices/references/error-handling.md` | Adding error states, catch blocks, or user-facing errors | +| Internationalization | `docs/skills/freighter-best-practices/references/i18n.md` | Adding or modifying user-facing strings | +| Messaging | `docs/skills/freighter-best-practices/references/messaging.md` | Adding popup/background/content script communication | +| Git & PR Workflow | `docs/skills/freighter-best-practices/references/git-workflow.md` | Branching, committing, opening PRs, CI | +| Dependencies | `docs/skills/freighter-best-practices/references/dependencies.md` | Adding, updating, or auditing packages | +| Anti-Patterns | `docs/skills/freighter-best-practices/references/anti-patterns.md` | Code review, avoiding common mistakes | + +## Key documentation + +| Topic | Location | +| --------------------------- | ------------------------------------------ | +| Extension dev guide | `extension/README.md` | +| Hardware wallet integration | `extension/INTEGRATING_HARDWARE_WALLET.MD` | +| Soroswap integration | `extension/INTEGRATING_SOROSWAP.MD` | +| Localization | `extension/LOCALIZATION.MD` | +| Style guide | `extension/STYLEGUIDE.MD` | +| E2E testing | `extension/e2e-tests/README.md` | +| API reference | `@stellar/freighter-api/README.md` | +| User guides | https://docs.freighter.app | diff --git a/CLAUDE.md b/CLAUDE.md index 0ee4fdf7cb..ca8546a8fc 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -109,7 +109,8 @@ INDEXER_V2_URL=https://freighter-backend-v2.stellar.org/api/v1 - **Translations:** All user-facing strings wrapped in `t()` from `react-i18next`. Run `yarn build:extension:translations` to generate keys. See `extension/LOCALIZATION.MD`. -- **Imports:** External packages first, then internal. ESLint enforces ordering. +- **Imports:** External packages first, then internal; follow this convention + consistently. - **Formatting:** Double quotes, 2-space indent, trailing commas, 80-char width, semicolons. Config in `.prettierrc.yaml`. - **Branch naming:** `type/description` — `feature/`, `fix/`, `chore/`, @@ -121,9 +122,9 @@ INDEXER_V2_URL=https://freighter-backend-v2.stellar.org/api/v1 Husky runs on every commit: -1. `pretty-quick --staged` — Prettier on staged files -2. `lint-staged` — ESLint fix on staged `.ts`/`.tsx` -3. Translation build — auto-generates `extension/src/popup/locales/` files +1. `./.husky/addTranslations.sh` — translation build that auto-generates + `extension/src/popup/locales/` files +2. `pretty-quick --staged` — Prettier on staged files If using nvm, create `~/.huskyrc` to ensure correct Node version. diff --git a/docs/skills/freighter-best-practices/references/i18n.md b/docs/skills/freighter-best-practices/references/i18n.md index 84813572b2..d6eb569dd4 100644 --- a/docs/skills/freighter-best-practices/references/i18n.md +++ b/docs/skills/freighter-best-practices/references/i18n.md @@ -78,6 +78,8 @@ To verify translations locally: ```typescript // CORRECT -t("Send {{amount}} XLM", { amount: "100" })// WRONG +t("Send {{amount}} XLM", { amount: "100" }); + +// WRONG `Send ${amount} XLM`; ``` From 5f129b542dbdb45795a44a97501f2d1f12b04757 Mon Sep 17 00:00:00 2001 From: leofelix077 Date: Thu, 9 Apr 2026 15:32:38 -0300 Subject: [PATCH 3/4] Unify duplicate agent instruction files Merge CLAUDE.md into AGENTS.md: add documentation link index at the top (llms.txt style), full quick reference table, repository structure, architecture details, conventions, and known complexity from CLAUDE.md. Best practices entry points table kept at the bottom. Remove CLAUDE.md and update llms.txt to point to AGENTS.md. --- AGENTS.md | 236 ++++++++++++++++++++++++++++++++++++------------------ CLAUDE.md | 170 --------------------------------------- llms.txt | 3 +- 3 files changed, 158 insertions(+), 251 deletions(-) delete mode 100644 CLAUDE.md diff --git a/AGENTS.md b/AGENTS.md index cb5c2a1fcf..434389e1ed 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,94 +1,183 @@ -# AGENTS.md - -> Freighter -- Non-custodial Stellar wallet browser extension (Chrome, Firefox, -> Safari). Monorepo with extension, client SDK, shared utilities, and docs site. - -## Setup commands - -- Install deps: `yarn setup` -- Start all workspaces: `yarn start` -- Start extension only: `yarn start:extension` -- Build extension (dev): `yarn build:extension` -- Build extension (production): `yarn build:extension:production` -- Build translations: `yarn build:extension:translations` -- Run unit tests: `yarn test` -- Run unit tests (CI): `yarn test:ci` -- Run e2e tests: `yarn test:e2e` +# Freighter + +> Non-custodial Stellar wallet — browser extension for Chrome, Firefox, and +> Safari. Monorepo with extension, client SDK, shared utilities, and docs site. + +## Documentation + +- [User Guides](https://docs.freighter.app/docs/guide/introduction) +- [API Playgrounds](https://docs.freighter.app/docs/playground/getAddress) +- [Extension Dev Guide](./extension/README.md) +- [E2E Testing Guide](./extension/e2e-tests/README.md) +- [Style Guide](./extension/STYLEGUIDE.MD) +- [Localization](./extension/LOCALIZATION.MD) +- [Hardware Wallet Integration](./extension/INTEGRATING_HARDWARE_WALLET.MD) +- [Soroswap Integration](./extension/INTEGRATING_SOROSWAP.MD) +- [@stellar/freighter-api SDK](./@stellar/freighter-api/README.md) +- [API Changelog](./@stellar/freighter-api/CHANGELOG.MD) +- [Extension Changelog](./extension/CHANGELOG.MD) +- [Getting Started](./README.md) +- [Contributing](./CONTRIBUTING.MD) + +## Quick Reference + +| Item | Value | +| ----------------- | --------------------------------------------------------------- | +| Language | TypeScript, React | +| Node | >= 22 (.nvmrc) | +| Package Manager | Yarn 4.10.0 (workspaces) | +| State Management | Redux (extension) | +| Testing | Jest (unit), Playwright (e2e) | +| Linting | ESLint flat config + Prettier | +| CI | GitHub Actions (10 workflows) | +| Docs | Docusaurus at docs.freighter.app | +| Default Branch | `master` | +| Branch Convention | `type/description` (feature/, fix/, chore/, refactor/, bugfix/) | + +## Build & Test Commands + +```bash +yarn setup # Install + allow scripts +yarn start # Dev mode (all workspaces) +yarn start:extension # Extension only +yarn start:freighter-api # SDK only +yarn start:docs # Docs only +yarn build:extension # Dev build +yarn build:extension:experimental # Experimental features enabled +yarn build:extension:production # Minified production build +yarn build:extension:translations # Auto-generate translation keys +yarn test # Jest watch mode +yarn test:ci # Jest CI mode +yarn test:e2e # Playwright e2e +``` -## Environment setup +## Environment Setup -Create `extension/.env`: +Only two env vars required for local dev — create `extension/.env`: ```env INDEXER_URL=https://freighter-backend-prd.stellar.org/api/v1 INDEXER_V2_URL=https://freighter-backend-v2.stellar.org/api/v1 ``` -Requires Node >= 22 (.nvmrc), Yarn 4.10.0 (workspaces). +## Repository Structure -## Code style +``` +freighter/ +├── extension/ # Browser extension +│ ├── src/popup/ # React UI (user-facing) +│ ├── src/background/ # Service worker (signing, storage, messages) +│ ├── src/contentScript/ # Content script (dApp <-> extension bridge) +│ ├── e2e-tests/ # Playwright tests +│ └── public/ # Static assets, manifest +├── @stellar/freighter-api/ # npm SDK for dApp integration +├── @shared/ # Shared code (api, constants, helpers) +│ ├── api/ +│ ├── constants/ +│ └── helpers/ +├── docs/ # Docusaurus documentation site +├── config/ # Shared build configs (Jest, Babel, Webpack) +└── .github/ + ├── workflows/ # 10 CI/CD workflows + └── agents/ # Playwright test AI agents (generator, healer, planner) +``` -- Double quotes, 2-space indent, trailing commas, 80-char width, semicolons -- Config in `.prettierrc.yaml` -- ESLint flat config (`eslint.config.js`) -- Named exports preferred (97%), arrow functions dominate (85%) -- For detailed rules: - `docs/skills/freighter-best-practices/references/code-style.md` +## Architecture -## Pre-commit hooks +The extension has three runtime contexts that communicate via message passing: + +1. **Popup** (`extension/src/popup/`) — React app. UI layer. Dispatches actions + to background via `chrome.runtime.sendMessage`. +2. **Background** (`extension/src/background/`) — Service worker. Holds + encrypted keys, signs transactions, manages storage. Processes messages from + popup and content script. +3. **Content Script** (`extension/src/contentScript/`) — Injected into web + pages. Bridges `window.postMessage` from `@stellar/freighter-api` to + `chrome.runtime.sendMessage` for the background. + +### Build Variants + +| Variant | Command | Features | +| ------------ | ----------------------------------- | -------------------------------------------- | +| Standard | `yarn build:extension` | Everyday development, connects to dev server | +| Experimental | `yarn build:extension:experimental` | Feature-flagged features enabled | +| Production | `yarn build:extension:production` | Minified, security guardrails, no dev server | + +### Dev Server URLs + +| URL | Purpose | +| ----------------------------------- | ----------------------------------------- | +| `localhost:9000` | Extension popup | +| Extension popup `/debug` route | Blockaid debug panel (dev mode only) | +| `localhost:9000/#/integration-test` | Integration test helper (clears app data) | +| `localhost:3000` | Documentation site | + +### Hot Reload + +- **Popup changes** hot reload automatically via dev server at `localhost:9000` +- **Background/content script changes** require `yarn build:extension` + reload + extension in browser + +## Code Style & Conventions + +- **Formatting:** Double quotes, 2-space indent, trailing commas, 80-char width, + semicolons. Config in `.prettierrc.yaml`. +- **Navigation:** Use `navigateTo` helper + `ROUTES` enum, never hardcoded + paths. See `extension/STYLEGUIDE.MD`. +- **Translations:** All user-facing strings wrapped in `t()` from + `react-i18next`. Run `yarn build:extension:translations` to generate keys. See + `extension/LOCALIZATION.MD`. +- **Imports:** External packages first, then internal; follow this convention + consistently. +- **Branch naming:** `type/description` — `feature/`, `fix/`, `chore/`, + `refactor/`, `bugfix/`. +- **Commit messages:** Imperative action verb + description. PR number appended + on merge. + +### Pre-commit Hooks Husky runs on every commit: -1. `./.husky/addTranslations.sh` -- auto-generates - `extension/src/popup/locales/` -2. `pretty-quick --staged` -- Prettier on staged files +1. `./.husky/addTranslations.sh` — translation build that auto-generates + `extension/src/popup/locales/` files +2. `pretty-quick --staged` — Prettier on staged files -## Testing instructions +If using nvm, create `~/.huskyrc` to ensure correct Node version. -- Unit tests: `yarn test` (Jest, JSDOM environment) -- E2E tests: `yarn test:e2e` (Playwright, Chromium only) -- Test files use `.test.ts` / `.test.tsx` in `__tests__/` directories -- For detailed patterns: - `docs/skills/freighter-best-practices/references/testing.md` +## Security-Sensitive Areas -## PR instructions +These areas require careful review — do not modify without understanding the +security implications: -- Branch naming: `type/description` -- `feature/`, `fix/`, `chore/`, - `refactor/`, `bugfix/` -- Commit messages: imperative action verb + description -- Default branch: `master` -- Run `yarn test:ci` and `yarn build:extension` before pushing -- For detailed workflow: - `docs/skills/freighter-best-practices/references/git-workflow.md` - -## Security considerations - -These areas require careful review: - -- `extension/src/background/` -- private keys, signing, encrypted storage -- `extension/src/contentScript/` -- attack surface between dApps and extension -- `extension/public/static/manifest/` -- extension permissions and CSP -- `@shared/constants/` -- network endpoints, key derivation parameters +- `extension/src/background/` — handles private keys, signing, encrypted storage +- `extension/src/contentScript/` — attack surface between dApps and extension + (postMessage) +- `extension/public/static/manifest/` — extension permissions and CSP +- `@shared/constants/` — network endpoints, key derivation parameters - Any code touching `chrome.storage` or key material -- For detailed rules: - `docs/skills/freighter-best-practices/references/security.md` - -## Architecture -Three runtime contexts communicate via message passing: +## Known Complexity -1. **Popup** (`extension/src/popup/`) -- React UI, dispatches to background -2. **Background** (`extension/src/background/`) -- service worker, keys, - signing, storage -3. **Content Script** (`extension/src/contentScript/`) -- bridges dApps to - extension +- **Message passing:** Popup <-> background <-> content script communication is + async and uses typed message enums. Follow existing patterns. +- **Build variants:** Standard, experimental, and production builds have + different feature flags and security settings. +- **Blockaid integration:** Transaction scanning is wired through multiple + layers. Test with the debug panel at `localhost:9000/#/debug`. +- **Soroswap:** Asset swap integration via `popup/helpers/sorobanSwap.ts` — + three key methods (getSoroswapTokens, soroswapGetBestPath, + buildAndSimulateSoroswapTx). +- **Store submissions:** Separate CI workflows for Chrome Web Store, Firefox + AMO, Safari, and npm publishing. +- **AI agents:** Three Playwright test agents in `.github/agents/` (generator, + healer, planner) for test automation. -State: Redux Toolkit (createSlice, createAsyncThunk, createSelector). +## PR Instructions -For detailed architecture: -`docs/skills/freighter-best-practices/references/architecture.md` +- Run `yarn test:ci` and `yarn build:extension` before pushing +- Default branch: `master` -## Best practices entry points +## Best Practices Entry Points Detailed best practices are split by concern area. Read the relevant file when working in that domain: @@ -106,16 +195,3 @@ working in that domain: | Git & PR Workflow | `docs/skills/freighter-best-practices/references/git-workflow.md` | Branching, committing, opening PRs, CI | | Dependencies | `docs/skills/freighter-best-practices/references/dependencies.md` | Adding, updating, or auditing packages | | Anti-Patterns | `docs/skills/freighter-best-practices/references/anti-patterns.md` | Code review, avoiding common mistakes | - -## Key documentation - -| Topic | Location | -| --------------------------- | ------------------------------------------ | -| Extension dev guide | `extension/README.md` | -| Hardware wallet integration | `extension/INTEGRATING_HARDWARE_WALLET.MD` | -| Soroswap integration | `extension/INTEGRATING_SOROSWAP.MD` | -| Localization | `extension/LOCALIZATION.MD` | -| Style guide | `extension/STYLEGUIDE.MD` | -| E2E testing | `extension/e2e-tests/README.md` | -| API reference | `@stellar/freighter-api/README.md` | -| User guides | https://docs.freighter.app | diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index ca8546a8fc..0000000000 --- a/CLAUDE.md +++ /dev/null @@ -1,170 +0,0 @@ -# Freighter — AI Agent Context - -> Non-custodial Stellar wallet browser extension. Monorepo with extension, -> client SDK, shared utilities, and docs site. - -## Quick Reference - -| Item | Value | -| ----------------- | --------------------------------------------------------------- | -| Language | TypeScript, React | -| Node | >= 22 (.nvmrc) | -| Package Manager | Yarn 4.10.0 (workspaces) | -| State Management | Redux (extension) | -| Testing | Jest (unit), Playwright (e2e) | -| Linting | ESLint flat config + Prettier | -| CI | GitHub Actions (10 workflows) | -| Docs | Docusaurus at docs.freighter.app | -| Default Branch | `master` | -| Branch Convention | `type/description` (feature/, fix/, chore/, refactor/, bugfix/) | - -## Build & Test Commands - -```bash -yarn setup # Install + allow scripts -yarn start # Dev mode (all workspaces) -yarn start:extension # Extension only -yarn start:freighter-api # SDK only -yarn start:docs # Docs only -yarn build:extension # Dev build -yarn build:extension:experimental # Experimental features enabled -yarn build:extension:production # Minified production build -yarn build:extension:translations # Auto-generate translation keys -yarn test # Jest watch mode -yarn test:ci # Jest CI mode -yarn test:e2e # Playwright e2e -``` - -## Repository Structure - -``` -freighter/ -├── extension/ # Browser extension -│ ├── src/popup/ # React UI (user-facing) -│ ├── src/background/ # Service worker (signing, storage, messages) -│ ├── src/contentScript/ # Content script (dApp <-> extension bridge) -│ ├── e2e-tests/ # Playwright tests -│ └── public/ # Static assets, manifest -├── @stellar/freighter-api/ # npm SDK for dApp integration -├── @shared/ # Shared code (api, constants, helpers) -│ ├── api/ -│ ├── constants/ -│ └── helpers/ -├── docs/ # Docusaurus documentation site -├── config/ # Shared build configs (Jest, Babel, Webpack) -└── .github/ - ├── workflows/ # 10 CI/CD workflows - └── agents/ # Playwright test AI agents (generator, healer, planner) -``` - -## Architecture - -The extension has three runtime contexts that communicate via message passing: - -1. **Popup** (`extension/src/popup/`) — React app. UI layer. Dispatches actions - to background via `chrome.runtime.sendMessage`. -2. **Background** (`extension/src/background/`) — Service worker. Holds - encrypted keys, signs transactions, manages storage. Processes messages from - popup and content script. -3. **Content Script** (`extension/src/contentScript/`) — Injected into web - pages. Bridges `window.postMessage` from `@stellar/freighter-api` to - `chrome.runtime.sendMessage` for the background. - -### Build Variants - -| Variant | Command | Features | -| ------------ | ----------------------------------- | -------------------------------------------- | -| Standard | `yarn build:extension` | Everyday development, connects to dev server | -| Experimental | `yarn build:extension:experimental` | Feature-flagged features enabled | -| Production | `yarn build:extension:production` | Minified, security guardrails, no dev server | - -### Dev Server URLs - -| URL | Purpose | -| ----------------------------------- | ----------------------------------------- | -| `localhost:9000` | Extension popup | -| Extension popup `/debug` route | Blockaid debug panel (dev mode only) | -| `localhost:9000/#/integration-test` | Integration test helper (clears app data) | -| `localhost:3000` | Documentation site | - -### Hot Reload - -- **Popup changes** hot reload automatically via dev server at `localhost:9000` -- **Background/content script changes** require `yarn build:extension` + reload - extension in browser - -### Environment Variables - -Only two required for local dev — create `extension/.env`: - -```env -INDEXER_URL=https://freighter-backend-prd.stellar.org/api/v1 -INDEXER_V2_URL=https://freighter-backend-v2.stellar.org/api/v1 -``` - -## Key Conventions - -- **Navigation:** Use `navigateTo` helper + `ROUTES` enum, never hardcoded - paths. See `extension/STYLEGUIDE.MD`. -- **Translations:** All user-facing strings wrapped in `t()` from - `react-i18next`. Run `yarn build:extension:translations` to generate keys. See - `extension/LOCALIZATION.MD`. -- **Imports:** External packages first, then internal; follow this convention - consistently. -- **Formatting:** Double quotes, 2-space indent, trailing commas, 80-char width, - semicolons. Config in `.prettierrc.yaml`. -- **Branch naming:** `type/description` — `feature/`, `fix/`, `chore/`, - `refactor/`, `bugfix/`. -- **Commit messages:** Imperative action verb + description. PR number appended - on merge. - -### Pre-commit Hooks - -Husky runs on every commit: - -1. `./.husky/addTranslations.sh` — translation build that auto-generates - `extension/src/popup/locales/` files -2. `pretty-quick --staged` — Prettier on staged files - -If using nvm, create `~/.huskyrc` to ensure correct Node version. - -## Security-Sensitive Areas - -These areas require careful review — do not modify without understanding the -security implications: - -- `extension/src/background/` — handles private keys, signing, encrypted storage -- `extension/src/contentScript/` — attack surface between dApps and extension - (postMessage) -- `extension/public/static/manifest/` — extension permissions and CSP -- `@shared/constants/` — network endpoints, key derivation parameters -- Any code touching `chrome.storage` or key material - -## Known Complexity - -- **Message passing:** Popup <-> background <-> content script communication is - async and uses typed message enums. Follow existing patterns. -- **Build variants:** Standard, experimental, and production builds have - different feature flags and security settings. -- **Blockaid integration:** Transaction scanning is wired through multiple - layers. Test with the debug panel at `localhost:9000/#/debug`. -- **Soroswap:** Asset swap integration via `popup/helpers/sorobanSwap.ts` — - three key methods (getSoroswapTokens, soroswapGetBestPath, - buildAndSimulateSoroswapTx). -- **Store submissions:** Separate CI workflows for Chrome Web Store, Firefox - AMO, Safari, and npm publishing. -- **AI agents:** Three Playwright test agents in `.github/agents/` (generator, - healer, planner) for test automation. - -## Key Documentation - -| Topic | Location | -| --------------------------- | ------------------------------------------ | -| Extension dev guide | `extension/README.md` | -| Hardware wallet integration | `extension/INTEGRATING_HARDWARE_WALLET.MD` | -| Soroswap integration | `extension/INTEGRATING_SOROSWAP.MD` | -| Localization | `extension/LOCALIZATION.MD` | -| Style guide | `extension/STYLEGUIDE.MD` | -| E2E testing | `extension/e2e-tests/README.md` | -| Changelog | `extension/CHANGELOG.MD` | -| API playgrounds | docs.freighter.app | diff --git a/llms.txt b/llms.txt index 178f70b251..d198f5ea0f 100644 --- a/llms.txt +++ b/llms.txt @@ -23,7 +23,7 @@ - [Soroswap Integration](./extension/INTEGRATING_SOROSWAP.MD) ## Architecture -- [AI Agent Context](./CLAUDE.md) +- [AI Agent Context](./AGENTS.md) - [Extension Changelog](./extension/CHANGELOG.MD) ## Key Concepts @@ -32,3 +32,4 @@ - State: Redux - Testing: Jest (unit) + Playwright (e2e) - Build: Webpack, Yarn workspaces, Node >= 22 + From 92f6796571f3642e17919df4c181a521b17e51eb Mon Sep 17 00:00:00 2001 From: leofelix077 Date: Thu, 9 Apr 2026 15:39:04 -0300 Subject: [PATCH 4/4] Unify duplicate agent instruction files Rewrite AGENTS.md as the single AI agent entry point: - Add glossary section with domain-specific terminology - Add documentation link index (replaces llms.txt) - Remove sections that duplicate best-practices reference files (code style details, branch conventions, PR instructions) - Keep unique context: env setup, repo map, architecture orientation, security alert list, known complexity/gotchas, pre-submission checklist - Delete llms.txt (content absorbed into AGENTS.md) - Delete CLAUDE.md (already removed in previous commit) --- AGENTS.md | 211 +++++++++++++++++++++++------------------------------- llms.txt | 35 --------- 2 files changed, 90 insertions(+), 156 deletions(-) delete mode 100644 llms.txt diff --git a/AGENTS.md b/AGENTS.md index 434389e1ed..93038ae1c0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,7 +1,26 @@ # Freighter -> Non-custodial Stellar wallet — browser extension for Chrome, Firefox, and -> Safari. Monorepo with extension, client SDK, shared utilities, and docs site. +> Non-custodial Stellar wallet browser extension for Chrome, Firefox, and +> Safari. Monorepo: extension, client SDK (`@stellar/freighter-api`), shared +> utilities, and Docusaurus docs site. + +## Glossary + +Domain terms you will encounter throughout this codebase: + +| Term | Meaning | +| ------------------ | ---------------------------------------------------------------------------------- | +| **Popup** | Browser extension UI — React app at `extension/src/popup/`, runs in its own page | +| **Background** | Service worker at `extension/src/background/` — holds keys, signs, manages storage | +| **Content Script** | Injected into web pages; bridges dApp `window.postMessage` to the extension | +| **dApp** | Decentralized app that connects to Freighter via `@stellar/freighter-api` | +| **XDR** | Stellar binary serialization format used for transactions and ledger entries | +| **Soroban** | Stellar smart contract platform; transactions carry `SorobanData` | +| **Blockaid** | Third-party malicious-transaction scanning service integrated into sign flows | +| **Horizon** | Stellar REST API for querying ledger data and submitting transactions | +| **Mnemonic** | BIP-39 seed phrase used to derive HD wallet private keys | +| **Redux slice** | Feature-scoped reducer + actions created with `createSlice` (Redux Toolkit) | +| **i18n key** | Translation token; all user-facing text must go through `t()` from `react-i18next` | ## Documentation @@ -14,46 +33,40 @@ - [Hardware Wallet Integration](./extension/INTEGRATING_HARDWARE_WALLET.MD) - [Soroswap Integration](./extension/INTEGRATING_SOROSWAP.MD) - [@stellar/freighter-api SDK](./@stellar/freighter-api/README.md) -- [API Changelog](./@stellar/freighter-api/CHANGELOG.MD) -- [Extension Changelog](./extension/CHANGELOG.MD) - [Getting Started](./README.md) - [Contributing](./CONTRIBUTING.MD) ## Quick Reference -| Item | Value | -| ----------------- | --------------------------------------------------------------- | -| Language | TypeScript, React | -| Node | >= 22 (.nvmrc) | -| Package Manager | Yarn 4.10.0 (workspaces) | -| State Management | Redux (extension) | -| Testing | Jest (unit), Playwright (e2e) | -| Linting | ESLint flat config + Prettier | -| CI | GitHub Actions (10 workflows) | -| Docs | Docusaurus at docs.freighter.app | -| Default Branch | `master` | -| Branch Convention | `type/description` (feature/, fix/, chore/, refactor/, bugfix/) | +| Item | Value | +| ----------------- | ------------------------------------------------------------ | +| Language | TypeScript, React | +| Node | >= 22 (`.nvmrc`) | +| Package Manager | Yarn 4.10.0 (workspaces) | +| State Management | Redux Toolkit | +| Testing | Jest (unit), Playwright (e2e) | +| Linting | ESLint flat config + Prettier | +| Default Branch | `master` | +| Branch Convention | `type/description` (`feature/`, `fix/`, `chore/`, `bugfix/`) | ## Build & Test Commands ```bash -yarn setup # Install + allow scripts -yarn start # Dev mode (all workspaces) -yarn start:extension # Extension only -yarn start:freighter-api # SDK only -yarn start:docs # Docs only -yarn build:extension # Dev build -yarn build:extension:experimental # Experimental features enabled -yarn build:extension:production # Minified production build -yarn build:extension:translations # Auto-generate translation keys -yarn test # Jest watch mode -yarn test:ci # Jest CI mode -yarn test:e2e # Playwright e2e +yarn setup # Install + allow scripts +yarn start # Dev mode (all workspaces) +yarn start:extension # Extension only +yarn build:extension # Dev build +yarn build:extension:experimental # Experimental features enabled +yarn build:extension:production # Minified production build +yarn build:extension:translations # Auto-generate translation keys +yarn test # Jest watch mode +yarn test:ci # Jest CI mode (use before pushing) +yarn test:e2e # Playwright e2e ``` ## Environment Setup -Only two env vars required for local dev — create `extension/.env`: +Create `extension/.env`: ```env INDEXER_URL=https://freighter-backend-prd.stellar.org/api/v1 @@ -64,128 +77,84 @@ INDEXER_V2_URL=https://freighter-backend-v2.stellar.org/api/v1 ``` freighter/ -├── extension/ # Browser extension -│ ├── src/popup/ # React UI (user-facing) -│ ├── src/background/ # Service worker (signing, storage, messages) -│ ├── src/contentScript/ # Content script (dApp <-> extension bridge) -│ ├── e2e-tests/ # Playwright tests -│ └── public/ # Static assets, manifest -├── @stellar/freighter-api/ # npm SDK for dApp integration -├── @shared/ # Shared code (api, constants, helpers) -│ ├── api/ -│ ├── constants/ -│ └── helpers/ -├── docs/ # Docusaurus documentation site -├── config/ # Shared build configs (Jest, Babel, Webpack) +├── extension/ +│ ├── src/popup/ # React UI +│ ├── src/background/ # Service worker (keys, signing, storage) +│ ├── src/contentScript/ # dApp ↔ extension bridge +│ ├── e2e-tests/ # Playwright tests +│ └── public/ # Static assets, manifest +├── @stellar/freighter-api/ # npm SDK for dApp integration +├── @shared/ # Shared api, constants, helpers +├── docs/ # Docusaurus site +├── config/ # Shared Jest/Babel/Webpack configs └── .github/ - ├── workflows/ # 10 CI/CD workflows - └── agents/ # Playwright test AI agents (generator, healer, planner) + ├── workflows/ # 10 CI/CD workflows + └── agents/ # Playwright test AI agents (generator, healer, planner) ``` ## Architecture -The extension has three runtime contexts that communicate via message passing: +Three runtime contexts communicate exclusively via message passing: -1. **Popup** (`extension/src/popup/`) — React app. UI layer. Dispatches actions - to background via `chrome.runtime.sendMessage`. -2. **Background** (`extension/src/background/`) — Service worker. Holds - encrypted keys, signs transactions, manages storage. Processes messages from - popup and content script. -3. **Content Script** (`extension/src/contentScript/`) — Injected into web - pages. Bridges `window.postMessage` from `@stellar/freighter-api` to - `chrome.runtime.sendMessage` for the background. +1. **Popup** — React UI, dispatches to background via + `chrome.runtime.sendMessage` +2. **Background** — service worker, processes messages, never directly touches + the DOM +3. **Content Script** — injected per tab, relays between `window.postMessage` + and background -### Build Variants - -| Variant | Command | Features | -| ------------ | ----------------------------------- | -------------------------------------------- | -| Standard | `yarn build:extension` | Everyday development, connects to dev server | -| Experimental | `yarn build:extension:experimental` | Feature-flagged features enabled | -| Production | `yarn build:extension:production` | Minified, security guardrails, no dev server | - -### Dev Server URLs +Dev server URLs: | URL | Purpose | | ----------------------------------- | ----------------------------------------- | -| `localhost:9000` | Extension popup | -| Extension popup `/debug` route | Blockaid debug panel (dev mode only) | +| `localhost:9000` | Extension popup (hot reload) | +| `localhost:9000/#/debug` | Blockaid debug panel (dev only) | | `localhost:9000/#/integration-test` | Integration test helper (clears app data) | -| `localhost:3000` | Documentation site | - -### Hot Reload - -- **Popup changes** hot reload automatically via dev server at `localhost:9000` -- **Background/content script changes** require `yarn build:extension` + reload - extension in browser - -## Code Style & Conventions - -- **Formatting:** Double quotes, 2-space indent, trailing commas, 80-char width, - semicolons. Config in `.prettierrc.yaml`. -- **Navigation:** Use `navigateTo` helper + `ROUTES` enum, never hardcoded - paths. See `extension/STYLEGUIDE.MD`. -- **Translations:** All user-facing strings wrapped in `t()` from - `react-i18next`. Run `yarn build:extension:translations` to generate keys. See - `extension/LOCALIZATION.MD`. -- **Imports:** External packages first, then internal; follow this convention - consistently. -- **Branch naming:** `type/description` — `feature/`, `fix/`, `chore/`, - `refactor/`, `bugfix/`. -- **Commit messages:** Imperative action verb + description. PR number appended - on merge. -### Pre-commit Hooks - -Husky runs on every commit: - -1. `./.husky/addTranslations.sh` — translation build that auto-generates - `extension/src/popup/locales/` files -2. `pretty-quick --staged` — Prettier on staged files - -If using nvm, create `~/.huskyrc` to ensure correct Node version. +Background and content script changes require `yarn build:extension` + extension +reload. Popup changes hot reload automatically. ## Security-Sensitive Areas -These areas require careful review — do not modify without understanding the -security implications: +Do not modify these without fully understanding the security implications: -- `extension/src/background/` — handles private keys, signing, encrypted storage -- `extension/src/contentScript/` — attack surface between dApps and extension - (postMessage) +- `extension/src/background/` — private keys, signing, encrypted storage +- `extension/src/contentScript/` — postMessage attack surface from dApps - `extension/public/static/manifest/` — extension permissions and CSP - `@shared/constants/` — network endpoints, key derivation parameters - Any code touching `chrome.storage` or key material -## Known Complexity +## Known Complexity / Gotchas -- **Message passing:** Popup <-> background <-> content script communication is - async and uses typed message enums. Follow existing patterns. -- **Build variants:** Standard, experimental, and production builds have - different feature flags and security settings. -- **Blockaid integration:** Transaction scanning is wired through multiple - layers. Test with the debug panel at `localhost:9000/#/debug`. -- **Soroswap:** Asset swap integration via `popup/helpers/sorobanSwap.ts` — - three key methods (getSoroswapTokens, soroswapGetBestPath, - buildAndSimulateSoroswapTx). -- **Store submissions:** Separate CI workflows for Chrome Web Store, Firefox - AMO, Safari, and npm publishing. -- **AI agents:** Three Playwright test agents in `.github/agents/` (generator, - healer, planner) for test automation. +- **Message passing** uses typed enums. Follow existing patterns — do not add + raw string messages. +- **Build variants** (standard / experimental / production) have different + feature flags; test the right one. +- **Translations** require running `yarn build:extension:translations` after + adding any `t()` call; the pre-commit hook handles this automatically but + re-run if the hook is skipped. +- **Soroswap** swap logic lives in `popup/helpers/sorobanSwap.ts` — three + chained async calls; debug with logs before refactoring. +- **Store submissions** use separate CI workflows per platform (Chrome, Firefox, + Safari, npm). +- **AI test agents** live in `.github/agents/` — don't delete or rename without + updating the workflows. -## PR Instructions +## Pre-submission Checklist -- Run `yarn test:ci` and `yarn build:extension` before pushing -- Default branch: `master` +```bash +yarn test:ci # All unit tests must pass +yarn build:extension # Build must succeed (catches type errors) +``` ## Best Practices Entry Points -Detailed best practices are split by concern area. Read the relevant file when -working in that domain: +Read the relevant file when working in that area: | Concern | Entry Point | When to Read | | -------------------- | ------------------------------------------------------------------- | -------------------------------------------------------- | | Code Style | `docs/skills/freighter-best-practices/references/code-style.md` | Writing or reviewing any code | -| Architecture | `docs/skills/freighter-best-practices/references/architecture.md` | Adding features, understanding the codebase | +| Architecture | `docs/skills/freighter-best-practices/references/architecture.md` | Adding features, understanding context/message flow | | Security | `docs/skills/freighter-best-practices/references/security.md` | Touching keys, messages, storage, or dApp interactions | | Testing | `docs/skills/freighter-best-practices/references/testing.md` | Writing or fixing tests | | Performance | `docs/skills/freighter-best-practices/references/performance.md` | Optimizing renders, bundle size, or load times | diff --git a/llms.txt b/llms.txt deleted file mode 100644 index d198f5ea0f..0000000000 --- a/llms.txt +++ /dev/null @@ -1,35 +0,0 @@ -# Freighter - -> Non-custodial Stellar wallet — browser extension for Chrome, Firefox, and Safari - -## Documentation -- [User Guides](https://docs.freighter.app/docs/guide/introduction) -- [API Playgrounds](https://docs.freighter.app/docs/playground/getAddress) -- [Extension Dev Guide](./extension/README.md) -- [E2E Testing Guide](./extension/e2e-tests/README.md) - -## Development -- [Getting Started](./README.md) -- [Contributing](./CONTRIBUTING.MD) -- [Style Guide](./extension/STYLEGUIDE.MD) -- [Localization](./extension/LOCALIZATION.MD) - -## API Reference -- [@stellar/freighter-api SDK](./@stellar/freighter-api/README.md) -- [API Changelog](./@stellar/freighter-api/CHANGELOG.MD) - -## Integration Guides -- [Hardware Wallet Integration](./extension/INTEGRATING_HARDWARE_WALLET.MD) -- [Soroswap Integration](./extension/INTEGRATING_SOROSWAP.MD) - -## Architecture -- [AI Agent Context](./AGENTS.md) -- [Extension Changelog](./extension/CHANGELOG.MD) - -## Key Concepts -- Monorepo: extension, SDK (@stellar/freighter-api), shared utils (@shared), docs (Docusaurus) -- Extension runtime: popup (React UI), background (service worker), content script (dApp bridge) -- State: Redux -- Testing: Jest (unit) + Playwright (e2e) -- Build: Webpack, Yarn workspaces, Node >= 22 -