{"owner":"MetaMask","repo":"metamask-extension","hasSkills":true,"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md"],"skills":{"AGENTS.md":"# AGENTS.md\n\nInstructions for AI coding agents working on MetaMask Browser Extension.\n\n---\n\n## Agent Instructions Summary\n\n**Project Type:** Browser extension (Chrome/Firefox)\n**Languages:** TypeScript (required for new code), JavaScript (legacy)\n**UI Framework:** React with functional components + hooks\n**State Management:** Redux + BaseController architecture\n**Testing:** Jest (unit), Playwright (E2E)\n**Build System:** Webpack (with LavaMoat for production)\n**Security:** LavaMoat policies required for all dependency changes\n\n### Critical Rules for Agents\n\n1. **ALWAYS use TypeScript** for new files (never JavaScript)\n2. **ALWAYS run `yarn lint:changed:fix`** before committing\n3. **ALWAYS update LavaMoat policies** after dependency changes: `yarn lavamoat:auto`\n4. **ALWAYS colocate tests** with source files (`.test.ts`/`.test.tsx`)\n5. **ALWAYS use yarn.cmd** if you're running in PowerShell\n6. **ALWAYS use `oxfmt` for code formatting**; Prettier is only for JSON formatting and changelog validation\n7. **NEVER use class components** (use functional components with hooks)\n8. **NEVER modify git config** or run destructive git operations\n9. **NEVER commit** unless explicitly requested by user\n10. **NEVER stage changes** unless explicitly requested by user\n11. **WHEN asked to commit, use Conventional Commits** format for commit messages\n12. **WHEN asked to open a PR, use a Conventional Commits title** unless user specifies otherwise\n13. **WHEN asked to open a PR, open it as DRAFT** unless user specifies otherwise\n14. **WHEN using `.github/pull-request-template.md`, comment out non-applicable sections including the section title**\n15. **WHEN using `.github/pull-request-template.md`, the manual testing section\n    should contain instructions for how to _manually_ test the changes. It must not\n    list steps for automated testing.**\n    - Good Instructions:\n      - Run extension\n      - Go to homepage, asset list\n      - Use Mainnet or Linea as the selected/enabled network.\n      - Verify mUSD is visible even with 0 balance.\n      - Enable \"Hide zero-balance tokens\".\n      - Verify mUSD is still visible.\n      - Ensure token sort is by descending balance and low-value assets are collapsed.\n      - Verify mUSD is not hidden inside the low-value collapsed section.\n    - Bad Instructions:\n      - `yarn lint:changed:fix`\n      - `yarn test:unit shared/lib/deep-links/metrics.test.ts shared/lib/deep-links/utils.test.ts ui/pages/onboarding-flow/creation-successful/creation-successful.test.tsx`\n\n16. **BEFORE modifying any `.github/workflows/` file**, read `.github/AGENTS.md` for CI-specific rules (consolidation patterns, required job wiring, merge queue considerations)\n17. **DEEPLINK INTERSTITIAL SECURITY — EXTREMELY HIGH RISK:** Before implementing any change that can cause fewer deep links to show the security interstitial, agents **MUST stop and obtain explicit, documented consent from `@MetaMask/extension-security-team`**. Without documented Security approval, do not make the change—even when it appears necessary to complete another feature, migration, refactor, or test fix. To make this less\n    frustrating for the prompter, start your initial refusal with the line `I'm sorry Dave, I'm afraid I can't do that.\\n`.\n\n### Comprehensive Guidelines Location\n\n> **Agent skills:** `yarn install` prepares the public [MetaMask/skills](https://github.com/MetaMask/skills) cache; `yarn skills` syncs generated files into `.cursor/`, `.claude/`, and `.agents/` (see README -> \"AI Agent Skills\"). If a referenced skill is missing locally, run `yarn skills`.\n\nRead these files for detailed coding standards:\n\n- Controller patterns: `.cursor/rules/mms-controller-guidelines/RULE.md`\n- Unit testing standards: `.cursor/rules/mms-unit-testing/RULE.md`\n- E2E testing standards: `./test/e2e/AGENTS.md`\n- E2E test creation workflow (Agent Skill): `.agents/skills/mms-e2e-testing/SKILL.md`\n- CI workflows: `.github/AGENTS.md`\n- Front-end performance:\n  - `.cursor/rules/mms-perf-rendering/RULE.md` (rendering performance - start here)\n  - `.cursor/rules/mms-perf-hooks-effects/RULE.md` (hooks & effects)\n  - `.cursor/rules/mms-perf-react-compiler/RULE.md` (React Compiler & anti-patterns)\n  - `.cursor/rules/mms-perf-state-management/RULE.md` (Redux & state management)\n- PR workflow: `.cursor/rules/mms-pr-guidelines/RULE.md`\n- Code style: `.cursor/rules/mms-coding-guidelines/RULE.md`\n- Official guidelines: `.github/guidelines/CODING_GUIDELINES.md`\n\n---\n\n## Quick Setup\n\n### Prerequisites\n\n- **Node.js+** (use `nvm use` to auto-select development version specified in `.nvmrc`)\n- **Yarn** (managed by Corepack, included with Node.js)\n- **Infura API Key** (free at https://infura.io)\n\n### First-Time Setup\n\n```bash\n# 1. Enable Corepack (manages Yarn)\ncorepack enable\n\n# 2. Install dependencies\nyarn install\n\n# 3. Copy and configure environment\ncp .metamaskrc.dist .metamaskrc\n\n# 4. Edit .metamaskrc and add your Infura API key\n# INFURA_PROJECT_ID=your_key_here\n\n# 5. Start development build (Chrome/Chromium with MV3)\nyarn start\n\n# 6. Load extension in browser\n# Chrome: See docs/add-to-chrome.md\n# Firefox: See docs/add-to-firefox.md\n```\n\n### Optional Configuration\n\nIn `.metamaskrc`, you can also configure:\n\n- `PASSWORD` - Auto-fill development wallet password\n- `SEGMENT_WRITE_KEY` - For MetaMetrics debugging\n- `SENTRY_DSN` - For error tracking debugging\n\n### Common Setup Issues\n\n| Issue                            | Solution                                                                                                |\n| -------------------------------- | ------------------------------------------------------------------------------------------------------- |\n| `command not found: yarn`        | Run `corepack enable`                                                                                   |\n| Build fails with policy errors   | Run `yarn lavamoat:auto`                                                                                |\n| Invalid Infura key error         | Check `INFURA_PROJECT_ID` in `.metamaskrc`                                                              |\n| Anvil won't start                | Ensure port 8545 is available and `yarn foundryup` has installed the binary                             |\n| Git hooks not working in VS Code | Follow [Husky troubleshooting](https://typicode.github.io/husky/troubleshooting.html#command-not-found) |\n\n---\n\n## Common Commands\n\n### Building\n\n```bash\n# Development Builds (with file watching and hot reload)\nyarn start                  # Chrome MV3 (default)\nyarn start:mv2             # Firefox MV2\nyarn start:flask           # Flask build (beta features)\nyarn start:with-state      # Start with preloaded wallet state\n\n# Production Builds\nyarn dist                  # Chrome MV3\nyarn dist:mv2              # Firefox MV2\n\n# Test Builds (for E2E testing)\nyarn build:test            # Build with LavaMoat enabled\nyarn start:test            # Build with LavaMoat disabled (faster iteration)\nyarn build:test:flask      # Flask test build\nyarn build:test:mv2        # Firefox MV2 test build\n\n# Download pre-built test builds (fastest)\nyarn download-builds --build-type test\n```\n\n**Build System Notes:**\n\n- `yarn start` uses Webpack (faster, development)\n- `yarn dist` uses Webpack + LavaMoat (production)\n- `yarn start` skips LavaMoat by default for speed; use `yarn start:lavamoat` to enable it\n- Test builds are required for E2E tests (not dev builds)\n\n### Testing\n\n```bash\n# Unit Tests\nyarn test                  # Lint + unit tests\nyarn test:unit             # Unit tests only\nyarn test:unit:watch       # Watch mode\nyarn test:unit:coverage    # With coverage report\n\n# E2E Tests\nyarn test:e2e:chrome       # Run all E2E tests (Chrome)\nyarn test:e2e:firefox      # Run all E2E tests (Firefox)\n\n# Single E2E test with options\nyarn test:e2e:single test/e2e/tests/account-menu/account-details.spec.js \\\n  --browser=chrome \\\n  --leave-running \\\n  --debug\n\n# Integration Tests\nyarn test:integration\nyarn test:integration:coverage\n\n# Playwright Tests\nyarn test:e2e:benchmark    # Performance benchmarks\n```\n\n**Testing Notes:**\n\n- Unit tests should be colocated with source files (`.test.ts`/`.test.tsx`)\n- Always create a test build before running E2E tests\n- Use `--leave-running` to debug failed E2E tests\n- See `.cursor/rules/mms-unit-testing/RULE.md` for testing standards\n\n### Linting & Formatting\n\n```bash\n# Run all linters\nyarn lint                  # JSON formatting + oxfmt + ESLint + TypeScript + Styles + Images\n\n# Individual linters\nyarn lint:json             # Prettier JSON formatting check\nyarn lint:format           # oxfmt code formatting check\nyarn lint:eslint           # ESLint only\nyarn lint:tsc              # TypeScript type checking\nyarn lint:styles           # Stylelint for SCSS\n\n# Auto-fix\nyarn lint:fix              # Fix all auto-fixable issues\nyarn lint:json:fix         # Fix JSON formatting with Prettier\nyarn lint:format:fix       # Fix code formatting with oxfmt\nyarn lint:eslint:fix       # Fix ESLint issues\n\n# Lint only changed files (faster)\nyarn lint:changed\nyarn lint:changed:fix\n```\n\n**Formatter Notes:**\n\n- Use `yarn lint:changed:fix` for normal agent work; it applies the repo's formatter choices to changed files.\n- Use `yarn lint:format:fix` or `oxfmt -c oxfmt.config.mts` for JavaScript, TypeScript, JSX, TSX, and other code formatting.\n- Use `yarn lint:json:fix` for JSON files such as `package.json`; this is the main remaining Prettier formatting path.\n- Do not run Prettier directly on code files.\n\n### Development Tools\n\n```bash\n# Test Dapps\nyarn dapp                  # Start test dapp on :8080\nyarn dapp-multichain       # Multichain test dapp\nyarn dapp-solana           # Solana test dapp\nyarn dapp-chain            # Dapp with local Anvil\n\n# DevTools\nyarn devtools:react        # React DevTools\nyarn devtools:redux        # Redux DevTools\nyarn start:dev             # Start with both DevTools\n\n# Local Blockchain\nyarn anvil                 # Start Anvil (Foundry) on port 8545\n\n# Storybook\nyarn storybook             # Component documentation/development\nyarn storybook:build       # Build static storybook\n\n# Git Hooks\nyarn githooks:install      # Install pre-commit hooks\n```\n\n### Dependency Management\n\n```bash\n# When adding/updating/removing dependencies:\n\n# 1. Install/update package\nyarn add package-name\nyarn upgrade package-name\n\n# 2. Deduplicate lockfile\nyarn lint:lockfile:dedupe:fix\n\n# 3. Update allow-scripts (determines which install scripts can run)\nyarn allow-scripts auto\n\n# 4. Update LavaMoat policies\nyarn lavamoat:auto         # Regenerates the webpack LavaMoat policies\n\n# 5. Update attributions\nyarn attributions:generate\n\n# Or use MetaMask bot (for team members with repo branch):\n# Comment on PR: @metamaskbot update-policies\n# Comment on PR: @metamaskbot update-attributions\n```\n\n**Important:** Always update LavaMoat policies and attributions when dependencies change!\n\n---\n\n## Common Agent Workflows\n\n### Workflow: Adding a New Feature\n\n```bash\n# 1. Start development build\nyarn start\n\n# 2. Create new files (MUST be TypeScript)\n# - Component: ui/components/feature-name/feature-name.tsx\n# - Test: ui/components/feature-name/feature-name.test.tsx\n# - Types: ui/components/feature-name/feature-name.types.ts\n\n# 3. Make changes\n\n# 4. Run lint and tests on changed files\nyarn lint:changed:fix\nyarn test:unit path/to/feature-name.test.tsx\n\n# 5. If test needs E2E, build test build\nyarn build:test\nyarn test:e2e:single test/e2e/tests/new-test.spec.js --browser=chrome\n```\n\n### Workflow: Modifying Existing Code\n\n```bash\n# 1. Identify file type and read relevant guidelines\n# - Controller? Read .cursor/rules/mms-controller-guidelines/RULE.md\n# - React component? Read .cursor/rules/mms-coding-guidelines/RULE.md\n# - Test? Read .cursor/rules/mms-unit-testing/RULE.md\n\n# 2. Make changes following guidelines\n\n# 3. Run linter on changed files\nyarn lint:changed:fix\n\n# 4. Run existing tests\nyarn test:unit path/to/modified-file.test.ts\n\n# 5. Update tests if behavior changed\n\n# 6. Check for circular dependencies\nyarn circular-deps:check\n```\n\n### Workflow: Adding/Updating Dependencies\n\n```bash\n# 1. Add or update package\nyarn add package-name\n# OR\nyarn upgrade package-name\n\n# 2. REQUIRED: Deduplicate lockfile\nyarn lint:lockfile:dedupe:fix\n\n# 3. REQUIRED: Update allow-scripts\nyarn allow-scripts auto\n\n# 4. REQUIRED: Update LavaMoat policies (this may take several minutes)\nyarn lavamoat:auto\n\n# 5. REQUIRED: Update attributions\nyarn attributions:generate\n\n# 6. Test the build\nyarn build:test\n\n# 7. Commit all changes including:\n#    - package.json\n#    - yarn.lock\n#    - lavamoat/webpack/*/policy.json\n#    - attribution.txt\n```\n\n### Workflow: Fixing a Bug\n\n```bash\n# 1. Create a failing test that reproduces the bug\n# Add test to existing .test.ts file or create new one\n\n# 2. Run the test to confirm it fails\nyarn test:unit path/to/test-file.test.ts\n\n# 3. Fix the bug in source code\n\n# 4. Run test again to confirm fix\nyarn test:unit path/to/test-file.test.ts\n\n# 5. Run all related tests\nyarn test:unit\n\n# 6. Lint changes\nyarn lint:changed:fix\n\n# 7. If bug is in E2E scenario\nyarn build:test\nyarn test:e2e:single path/to/test.spec.js --browser=chrome\n```\n\n### Workflow: Creating a Controller\n\n```bash\n# 1. MUST read controller guidelines first\n# Read .cursor/rules/mms-controller-guidelines/RULE.md\n\n# 2. Create controller file (TypeScript only)\n# Location: app/scripts/controllers/your-controller/your-controller.ts\n\n# 3. Controller MUST:\n#    - Extend BaseController from @metamask/base-controller\n#    - Define state type\n#    - Define metadata for all state properties\n#    - Export getDefaultYourControllerState() function\n#    - Use messenger for inter-controller communication\n#    - Use selectors for derived state (not getter methods)\n\n# 4. Create test file\n# Location: app/scripts/controllers/your-controller/your-controller.test.ts\n\n# 5. Create types file\n# Location: app/scripts/controllers/your-controller/types.ts\n\n# 6. Run tests\nyarn test:unit app/scripts/controllers/your-controller/your-controller.test.ts\n\n# 7. Lint\nyarn lint:changed:fix\n```\n\n### Controller Development Patterns\n\nWhen creating a controller, follow these critical patterns from `.cursor/rules/mms-controller-guidelines/RULE.md`:\n\n#### State Metadata Requirements\n\n**Every state property MUST have metadata with these properties:**\n\n| Property                                | Type    | Purpose                   | Example Value              |\n| --------------------------------------- | ------- | ------------------------- | -------------------------- |\n| `anonymous` OR `includeInDebugSnapshot` | boolean | Safe for Sentry? (no PII) | `anonymous: true`          |\n| `includeInStateLogs`                    | boolean | Include in state logs?    | `false` for sensitive data |\n| `persist`                               | boolean | Save to storage?          | `true` for user data       |\n| `usedInUi`                              | boolean | Used by UI?               | `true` if rendered         |\n\n**Example:**\n\n```typescript\nconst tokensControllerMetadata = {\n  tokens: {\n    anonymous: true, // No PII, safe for Sentry\n    includeInStateLogs: true, // Safe to include in logs\n    persist: true, // Should be saved\n    usedInUi: true, // Rendered in UI\n  },\n  apiKey: {\n    anonymous: false, // Sensitive\n    includeInStateLogs: false, // Must exclude from logs\n    persist: true, // But should be saved\n    usedInUi: false, // Backend only\n  },\n};\n```\n\n#### Default State Function Pattern\n\n**ALWAYS export function, NEVER export object:**\n\n```typescript\n✅ CORRECT: Returns new object each time\nexport function getDefaultTokensControllerState(): TokensControllerState {\n  return {\n    tokens: [],\n    lastUpdated: 0,\n  };\n}\n\n❌ WRONG: Shared object reference (mutation risk)\nexport const defaultTokensControllerState = {\n  tokens: [],\n  lastUpdated: 0,\n};\n```\n\n#### Constructor Single Options Bag\n\n**ALWAYS use single options object, NO positional arguments:**\n\n```typescript\n✅ CORRECT:\nconstructor({\n  messenger,\n  state = {},\n  apiKey,        // All options in one bag\n  isEnabled,\n}: TokensControllerOptions) {\n  super({\n    name: 'TokensController',\n    metadata: tokensControllerMetadata,\n    messenger,\n    state: { ...getDefaultTokensControllerState(), ...state },\n  });\n}\n\n❌ WRONG:\nconstructor(\n  options: ControllerOptions,\n  apiKey: string,     // Separate positional arg - BAD\n  isEnabled: boolean, // Separate positional arg - BAD\n) { }\n```\n\n#### Action Methods (Not Setters)\n\n**Model high-level user actions, not property changes:**\n\n```typescript\n❌ WRONG: Generic setters\nsetTokenData(data: any) { }\nupdateField(field: string, value: any) { }\n\n✅ CORRECT: Action-based methods\naddToken(token: Token) {\n  if (!token.address) {\n    throw new Error('Token address required');\n  }\n\n  this.update((state) => {\n    state.tokens.push(token);\n    state.lastUpdated = Date.now();\n  });\n}\n\nremoveToken(address: string) {\n  this.update((state) => {\n    state.tokens = state.tokens.filter(t => t.address !== address);\n    state.lastUpdated = Date.now();\n  });\n}\n```\n\n#### Keep State Minimal - Use Selectors\n\n**NEVER store derived values in state:**\n\n```typescript\n❌ WRONG: Derived values in state\ntype State = {\n  tokens: Token[];\n  tokenCount: number;  // DON'T STORE - derive it!\n  hasTokens: boolean;  // DON'T STORE - derive it!\n};\n\n✅ CORRECT: Minimal state + selectors\ntype State = {\n  tokens: Token[];  // Only essential data\n};\n\n// Export selectors for derived values\nexport const tokensControllerSelectors = {\n  selectTokens: (state: State) => state.tokens,\n  selectTokenCount: (state: State) => state.tokens.length,\n  selectHasTokens: (state: State) => state.tokens.length > 0,\n};\n```\n\n#### Cleanup with destroy()\n\n**Implement if controller has background tasks:**\n\n```typescript\nclass TokensController extends BaseController</*...*/> {\n  #pollInterval: NodeJS.Timeout | null = null;\n\n  constructor(options: Options) {\n    super(/* ... */);\n    if (options.enablePolling) {\n      this.#startPolling();\n    }\n  }\n\n  destroy() {\n    // Clean up resources\n    if (this.#pollInterval) {\n      clearInterval(this.#pollInterval);\n      this.#pollInterval = null;\n    }\n\n    // Call super to clean up messenger\n    super.destroy();\n  }\n}\n```\n\n**See `.cursor/rules/mms-controller-guidelines/RULE.md` for complete patterns with detailed examples.**\n\n---\n\n### Decision: Which Test Build to Use?\n\n```\nIF you need to run E2E tests:\n  IF you're iterating/debugging:\n    → Use `yarn start:test` (faster, LavaMoat disabled)\n  IF you're doing final verification:\n    → Use `yarn build:test` (slower, LavaMoat enabled, matches production)\n\nIF you're developing with feature flags:\n  → Use `FEATURE_FLAG=1 yarn build:test`\n  → Then run E2E: `yarn test:e2e:single path/to/test.spec.js`\n\nIF you're working on Firefox compatibility:\n  → Use `yarn build:test:mv2`\n  → Then test: `yarn test:e2e:firefox`\n```\n\n### Decision: Where to Put New Code?\n\n```\nIF creating a controller:\n  → app/scripts/controllers/controller-name/\n\nIF creating a UI component:\n  → ui/components/component-name/ (for reusable components)\n  → ui/pages/page-name/ (for page-level components)\n\nIF creating a utility function:\n  → shared/lib/ (if used by both background and UI)\n  → app/scripts/lib/ (if only used by background)\n  → ui/helpers/ (if only used by UI)\n\nIF creating constants:\n  → shared/constants/\n\nIF creating TypeScript types:\n  → shared/types/ (for shared types)\n  → types/ (for project-wide types)\n  → [component-dir]/types.ts (for component-specific types)\n\nIF creating a state migration:\n  → Run: yarn generate:migration\n  → Edits: app/scripts/migrations/[number].ts\n```\n\n### Decision: Which Browser Target?\n\n```\nIF user specifies Chrome, Edge, or Brave:\n  → Use MV3 (Manifest V3)\n  → Commands: yarn start, yarn dist, yarn build:test\n\nIF user specifies Firefox:\n  → Use MV2 (Manifest V2)\n  → Commands: yarn start:mv2, yarn dist:mv2, yarn build:test:mv2\n  → Set ENABLE_MV3=false\n\nIF user doesn't specify:\n  → Default to Chrome MV3\n  → Use: yarn start\n```\n\n---\n\n## Project Structure\n\n### High-Level Directory Layout\n\n```\nmetamask-extension/\n├── app/\n│   ├── scripts/           # Background scripts & controllers (860 TS, 234 JS)\n│   │   ├── controllers/   # Business logic controllers\n│   │   ├── lib/           # Utility libraries\n│   │   └── migrations/    # State migration scripts\n│   ├── manifest/          # Browser extension manifests (MV2/MV3)\n│   ├── images/            # Icons and images\n│   └── *.html             # Extension HTML pages\n├── ui/                    # React UI code (1,412 TSX, 1,292 JS)\n│   ├── components/        # Reusable React components\n│   ├── pages/             # Page-level components\n│   ├── ducks/             # Redux slices (state management)\n│   ├── hooks/             # Custom React hooks\n│   ├── selectors/         # Redux selectors\n│   └── store/             # Redux store configuration\n├── shared/                # Code shared between background and UI\n│   ├── constants/         # Shared constants (47 TS files)\n│   ├── lib/               # Shared utilities (122 TS files)\n│   ├── modules/           # Shared modules (45 TS files)\n│   └── types/             # TypeScript type definitions\n├── test/                  # Test files (586 TS, 79 JS)\n│   ├── e2e/               # End-to-end tests\n│   ├── integration/       # Integration tests\n│   └── *.test.*           # Unit tests (colocated with source)\n├── development/           # Build system and dev tools\n│   ├── build/             # Build scripts\n│   └── webpack/           # Webpack configuration\n├── docs/                  # Documentation (54 files)\n└── .cursor/rules/         # AI agent coding guidelines\n```\n\n### Finding Specific Code\n\n| What You Need                | Where to Look                                   |\n| ---------------------------- | ----------------------------------------------- |\n| Controllers (business logic) | `app/scripts/controllers/`                      |\n| React Components             | `ui/components/` or `ui/pages/`                 |\n| Redux State Management       | `ui/ducks/` (slices) and `ui/selectors/`        |\n| Background Scripts           | `app/scripts/`                                  |\n| Constants                    | `shared/constants/`                             |\n| Utility Functions            | `shared/lib/` or `ui/helpers/`                  |\n| Type Definitions             | `shared/types/` or `types/`                     |\n| State Migrations             | `app/scripts/migrations/`                       |\n| Build Configuration          | `development/build/` and `development/webpack/` |\n| Extension Manifests          | `app/manifest/v2/` or `app/manifest/v3/`        |\n\n### Architecture Patterns\n\n**Controllers** (Background Scripts):\n\n- Inherit from `BaseController` (from `@metamask/base-controller`)\n- Manage wallet state and business logic\n- Communicate via Messenger pattern (pub/sub)\n- Use selectors for derived state (not getter methods)\n- See `.cursor/rules/mms-controller-guidelines/RULE.md` for detailed patterns\n\n**React Components** (UI):\n\n- Functional components with hooks (no class components)\n- Props destructured in function parameters\n- Redux for global state, local state for UI-only data\n- Performance optimizations: useMemo, useCallback, React.memo\n- Unique IDs as keys (not array index for dynamic lists)\n- Organized in component folders with tests, styles, and types\n- See `.cursor/rules/mms-coding-guidelines/RULE.md` and `.cursor/rules/mms-perf-rendering/RULE.md`\n\n**Testing**:\n\n- Unit tests colocated with source files (`.test.ts`)\n- Jest for unit tests, Playwright for E2E\n- Test files organized with `describe` blocks by method/function\n- See `.cursor/rules/mms-unit-testing/RULE.md` for testing patterns\n\n### File Modification Patterns\n\nWhen you modify certain files, you typically need to update related files:\n\n**When modifying a Controller:**\n\n```\napp/scripts/controllers/foo/foo-controller.ts → ALSO UPDATE:\n├── app/scripts/controllers/foo/foo-controller.test.ts (tests)\n├── app/scripts/controllers/foo/types.ts (if types changed)\n└── app/scripts/metamask-controller.ts (if adding/removing controller)\n```\n\n**When modifying a React Component:**\n\n```\nui/components/foo/foo.tsx → ALSO UPDATE:\n├── ui/components/foo/foo.test.tsx (tests)\n├── ui/components/foo/foo.types.ts (if props changed)\n├── ui/components/foo/foo.stories.tsx (if props changed)\n└── ui/components/foo/index.ts (if exports changed)\n```\n\n**When modifying Redux State (ducks):**\n\n```\nui/ducks/foo/foo.ts → ALSO UPDATE:\n├── ui/ducks/foo/foo.test.ts (tests)\n├── ui/selectors/foo.ts (selectors that depend on this state)\n└── ui/components/*/foo-component.tsx (components using this state)\n```\n\n**When adding/removing dependencies:**\n\n```\npackage.json → MUST UPDATE:\n├── yarn.lock (run yarn install)\n├── lavamoat/webpack/*/policy.json (run yarn lavamoat:auto)\n└── attribution.txt (run yarn attributions:generate)\n```\n\n**When modifying state shape:**\n\n```\napp/scripts/controllers/foo/foo-controller.ts → MUST CREATE:\n└── app/scripts/migrations/[next-number].ts (migration for state change)\n```\n\n---\n\n## Working with Feature Flags\n\n### What are Feature Flags?\n\nFeature flags allow you to enable/disable features during development. They're defined in `.metamaskrc` and control which features are built into the extension.\n\n### Available Feature Flags\n\nCheck `.metamaskrc.dist` for the current list of feature flags. Common ones:\n\n- `MULTICHAIN` - Multi-chain support\n- `BLOCKAID_PUBLIC_KEY` - Security features\n- Various experimental features\n\n### Using Feature Flags\n\n**Method 1: Configure in `.metamaskrc`**\n\n```bash\n# Edit .metamaskrc\nMULTICHAIN=1\nOTHER_FEATURE=1\n\n# Build with flags\nyarn build:test\n```\n\n**Method 2: Pass as environment variable**\n\n```bash\n# Enable for single build\nMULTICHAIN=1 yarn build:test\nMULTICHAIN=1 yarn start:test\n\n# Run E2E tests with feature enabled\nMULTICHAIN=1 yarn build:test\nyarn test:e2e:single test/e2e/tests/some-test.spec.js\n```\n\n### Remote Feature Flags\n\nOverride remote feature flags using `.manifest-overrides.json`:\n\n```json\n{\n  \"_flags\": {\n    \"remoteFeatureFlags\": {\n      \"testBooleanFlag\": false\n    }\n  }\n}\n```\n\nSet in `.metamaskrc`:\n\n```\nMANIFEST_OVERRIDES=.manifest-overrides.json\n```\n\n---\n\n## LavaMoat Security System\n\n### What is LavaMoat?\n\nLavaMoat is a supply chain security tool that restricts what dependencies can do (file access, network access, etc.). It's enabled in production builds to protect users.\n\n### When to Update LavaMoat Policies\n\nUpdate policies whenever you:\n\n- ✅ Add a new dependency\n- ✅ Update an existing dependency\n- ✅ Remove a dependency\n- ✅ Change how code accesses Node.js APIs\n- ✅ See \"LavaMoat policy violation\" errors\n\n### How to Update Policies\n\n**Automated (Recommended):**\n\n```bash\n# Regenerate the webpack LavaMoat policies\nyarn lavamoat:auto\n\n# Or use MetaMask bot (team members only):\n# Comment on PR: @metamaskbot update-policies\n```\n\n**Manual:**\n\n```bash\n# Compile the webpack build tooling\nyarn webpack:tsc\n\n# Regenerate the webpack build tooling policy\nyarn webpack:lavamoat:policy:build\n\n# Regenerate the Firefox MV2 application policies\nyarn webpack:lavamoat:policy:mv2\n\n# Regenerate the Chrome MV3 application policies\nyarn webpack:lavamoat:policy:mv3\n\n# If policies still fail after regeneration:\nrm -rf node_modules/ && yarn\n# Then compile the webpack build tooling and rerun the affected policy command above.\n```\n\n### Common Policy Issues\n\n- **Policy fails on macOS/Windows:** Platform-specific optional dependencies. Regenerate on the target platform.\n- **Dynamic imports fail:** LavaMoat's static analysis may miss dynamic code. May need manual policy updates.\n- **Can't build at all:** Use `yarn start` (LavaMoat off by default) for development, but fix before merging.\n\n### Development Without LavaMoat\n\nFor faster iteration during development (LavaMoat is off by default):\n\n```bash\nyarn start       # Development build\nyarn start:test  # Test build\n```\n\n**⚠️ Warning:** Always test with LavaMoat enabled before merging!\n\n---\n\n## Browser Compatibility\n\n### Manifest V2 vs Manifest V3\n\n| Feature           | MV2 (Firefox)         | MV3 (Chrome/Chromium) |\n| ----------------- | --------------------- | --------------------- |\n| **Build Flag**    | `ENABLE_MV3=false`    | Default               |\n| **Start Command** | `yarn start:mv2`      | `yarn start`          |\n| **Dist Command**  | `yarn dist:mv2`       | `yarn dist`           |\n| **Background**    | Background page       | Service worker        |\n| **Permissions**   | Broader access        | More restrictive      |\n| **APIs**          | `browser.*` namespace | `chrome.*` namespace  |\n\n### Building for Different Browsers\n\n```bash\n# Chrome / Edge / Brave (MV3)\nyarn start                    # Development\nyarn dist                     # Production\n\n# Firefox (MV2)\nyarn start:mv2                # Development\nyarn dist:mv2                 # Production\n\n# Test builds\nyarn build:test               # Chrome MV3\nyarn build:test:mv2           # Firefox MV2\n```\n\n### Browser-Specific Considerations\n\n**Firefox:**\n\n- Must use MV2 (Manifest V2)\n- Use `webextension-polyfill` for cross-browser compatibility\n- Test with `yarn test:e2e:firefox`\n\n**Chrome/Chromium:**\n\n- Uses MV3 (Manifest V3) by default\n- Service worker limitations (no DOM access in background)\n- Test with `yarn test:e2e:chrome`\n\n**Both:**\n\n- Code should use `browser.*` namespace (polyfilled for Chrome)\n- Conditional logic for browser differences in `app/scripts/lib/util.js`\n\n---\n\n## Testing Strategy\n\n### Unit Tests\n\n**Location:** Colocated with source files (`.test.ts` or `.test.tsx`)\n\n**Running:**\n\n```bash\nyarn test:unit              # All unit tests\nyarn test:unit:watch        # Watch mode\nyarn test:unit:coverage     # With coverage\n```\n\n**Key Principles:**\n\n- Use Jest (not Mocha or Tape)\n- Test through public interfaces (not private methods)\n- Keep critical test data inline\n- Use `describe` blocks to organize by method/function\n- Never use \"should\" in test names (use present tense)\n\n**Example:**\n\n```typescript\ndescribe('TokensController', () => {\n  describe('addToken', () => {\n    it('adds the token to state', () => {\n      // Arrange, Act, Assert\n    });\n\n    it('throws error when token address is missing', () => {\n      // Test error case\n    });\n  });\n});\n```\n\n**Detailed Guidelines:** See `.cursor/rules/mms-unit-testing/RULE.md`\n\n### E2E Tests\n\n**Location:** `test/e2e/tests/`\n\n**Running:**\n\n```bash\n# Must build test build first!\nyarn build:test              # or yarn start:test\n\n# Run E2E tests\nyarn test:e2e:chrome         # All Chrome tests\nyarn test:e2e:firefox        # All Firefox tests\n\n# Single test with debug\nyarn test:e2e:single test/e2e/tests/TEST_NAME.spec.js \\\n  --browser=chrome \\\n  --debug \\\n  --leave-running\n```\n\n**Options:**\n\n- `--browser` - chrome, firefox, or all\n- `--debug` - Verbose logging\n- `--leave-running` - Keep browser open on failure\n- `--retries` - Number of retries on failure\n- `--update-snapshot` - Update snapshots\n\n**E2E Best Practices:**\n\nFind them in [](./test/e2e/AGENTS.md)\n\n### Visual Verification (MetaMask CLI / Playwright)\n\nWhen the user explicitly asks for visual verification of UI behavior (e.g., \"verify this works\", \"confirm visually\", \"take screenshots\", \"click through onboarding/unlock/send flow\"), you **MUST** use the MetaMask visual testing skill and `mm` cli tools instead of only reasoning about code.\n\n**Load the skill:** `/metamask-visual-testing`\n\n**Workflow:**\n\n0. Build if needed (`yarn build:test`), then `mm launch` (this auto-starts the daemon)\n1. **Query prior knowledge:** Run `mm knowledge-search \"<flow>\"` and `mm knowledge-sessions` to reuse previously discovered flows and avoid wasting tokens rediscovering known sequences.\n2. Always call `mm describe-screen` before acting to discover targets\n3. Use `mm click`/`mm type`/`mm wait-for` to drive the flow\n4. Provide evidence via `mm screenshot` and/or final `mm describe-screen` output\n5. Always end with `mm cleanup` (even on failure)\n\n**If CLI is unavailable or denied:** Say so explicitly and explain what's missing. Do not claim you verified without actual tool output as evidence.\n\n**Skill location:** `.claude/skills/mms-visual-testing/SKILL.md`\n\n**MM CLI architecture docs:** `test/e2e/playwright/llm-workflow/README.md`\n\n### Integration Tests\n\n**Location:** `test/integration/`\n\n**Running:**\n\n```bash\nyarn test:integration\nyarn test:integration:coverage\n```\n\n**Coverage Goals:**\n\n- Unit tests: > 80% coverage\n- Critical paths: > 90% coverage\n- E2E tests: Cover main user workflows\n\n---\n\n## State Migrations\n\n### What are Migrations?\n\nWhen MetaMask updates, the stored state format might change. Migrations transform old state to new format automatically.\n\n### Creating a Migration\n\n```bash\n# Generate migration template\nyarn generate:migration\n\n# Creates: app/scripts/migrations/XXX.ts (next number)\n```\n\n### Migration Guidelines\n\n1. **Always create migrations for state changes**\n2. **Test migrations thoroughly** (old state → new state)\n3. **Handle missing data gracefully** (some users may have old/corrupted state)\n4. **Never mutate input state** (return new state object)\n5. **Include version number** in migration metadata\n\n**Example Migration:**\n\n```typescript\nimport { cloneDeep } from 'lodash';\n\nconst version = 123;\n\nexport default {\n  version,\n  async migrate(originalVersionedData: any) {\n    const versionedData = cloneDeep(originalVersionedData);\n    versionedData.meta.version = version;\n    transformData(versionedData.data);\n    return versionedData;\n  },\n};\n\nfunction transformData(state: any): void {\n  // Transform state.data\n  if (state.PreferencesController) {\n    state.PreferencesController.newProperty = 'defaultValue';\n  }\n}\n```\n\n---\n\n## Pull Request Workflow\n\n### Before Creating a PR\n\n- [ ] All tests pass: `yarn test`\n- [ ] Linting passes: `yarn lint`\n- [ ] No console.logs or debug code\n- [ ] Changes are covered by tests\n- [ ] LavaMoat policies updated (if dependencies changed)\n- [ ] Attributions updated (if dependencies changed)\n\n### Creating a PR\n\n**Reference:** Follow the [PR template](https://github.com/MetaMask/metamask-extension/blob/main/.github/pull-request-template.md) when creating pull requests.\n\n### Default Agent Commit/Push/PR Flow (When Requested)\n\nExecute only the steps that correspond to what the user explicitly requested. Do not perform additional steps (e.g., do not push or open a PR if the user only asked to commit).\n\n#### When asked to **commit**\n\n1. Run `yarn lint:changed:fix` before creating the commit.\n2. Stage only files relevant to the requested change.\n3. Create a commit using Conventional Commits format: `<type>(optional-scope): <summary>`.\n\n#### When asked to **push**\n\nComplete all steps for **commit** above, then:\n\n4. Push the current branch to `origin`.\n\n#### When asked to **open a PR**\n\nComplete all steps for **push** above, then:\n\n5. Open a **draft** PR with:\n   - A Conventional Commits PR title (normally matching the commit summary).\n   - A PR body based on `.github/pull-request-template.md`.\n   - Any non-applicable template section commented out as a full block, including the section heading, for example:\n\n```markdown\n<!--\n## **Screenshots/Recordings**\n### **Before**\n### **After**\n-->\n```\n\n6. Do not mark the PR as \"Ready for review\" unless explicitly requested.\n\n**PR Title Format:**\n\n- Clear and descriptive\n- Will be used in squash commit message\n- Example: \"feat(networks): add token validation for custom networks\"\n\n**Description Section:**\n\n- **Context:** What's the background?\n- **Problem:** What needs to be fixed/added?\n- **Solution:** How do your changes address it?\n- Answer: \"What is the reason for the change?\" and \"What is the improvement/solution?\"\n\n**Changelog Entry:**\n\n- If End-User-Facing: Write a short user-facing description in past tense\n  - Example: `CHANGELOG entry: Added a new tab for users to see their NFTs`\n  - Example: `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker`\n- If not End-User-Facing: Write `CHANGELOG entry: null` or label with `no-changelog`\n\n**Related Issues:**\n\n- List all related issues using `Fixes: #issue-number` format\n- Link to related PRs if applicable\n\n**Manual Testing Steps:**\n\n- Provide numbered steps to test the changes\n- Include specific pages/features to test\n- Example:\n  1. Go to this page...\n  2. Click this button...\n  3. Verify this behavior...\n\n**Screenshots/Recordings:**\n\n- **Before:** Screenshots/videos showing the previous state (for UI changes)\n- **After:** Screenshots/videos showing the new state (for UI changes)\n- Required for all UI changes\n\n**Pre-merge Author Checklist:**\n\n- [ ] Followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md)\n- [ ] Completed the PR template to the best of ability\n- [ ] Included tests if applicable\n- [ ] Documented code using [JSDoc](https://jsdoc.app/) format if applicable\n- [ ] Applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md))\n\n**Additional PR Comments:**\n\n- Call out non-obvious changes\n- Explain complex logic inline\n- Link to related issues/PRs\n\n### During Review\n\n- Respond to all feedback\n- Link to commits that address feedback (e.g., \"Fixed in abc1234\")\n- **Avoid rebasing after receiving comments** (makes review harder)\n- Push new commits instead of amending\n- If the Conventional Commit type in the PR's title is `chore`, please evaluate if `chore` is truly the best choice. We also have two custom types: `bump` (for package updates) and `release` (for tasks on a release branch and tasks that are all about getting a release ready).\n\n### Before Merging\n\n- [ ] All conversations resolved\n- [ ] Required approvals received\n- [ ] CI checks passing\n- [ ] Review the squash commit message (auto-generated from PR)\n- [ ] **Don't modify the commit title format** (must be: `Title (#number)`)\n\n**Detailed Guidelines:** See `.cursor/rules/mms-pr-guidelines/RULE.md`\n\n---\n\n## Code Style & Standards\n\n### General Principles\n\n1. **TypeScript for all new code** (no new JavaScript files)\n2. **Functional components with hooks** (no class components)\n3. **Destructure props** in function parameters\n4. **Small, focused functions** (single responsibility)\n5. **Early returns** to reduce nesting\n6. **DRY principle** (extract repeated code)\n\n### Naming Conventions\n\n```typescript\n// Components: PascalCase\nexport const TokenListItem = () => {};\n\n// Functions: camelCase\nconst handleInputChange = () => {};\n\n// Custom hooks: use prefix\nconst useTokenBalance = () => {};\n\n// Higher-order components: with prefix\nconst withAuth = (Component) => {};\n\n// Controllers: PascalCase with Controller suffix\nclass TokensController extends BaseController {}\n```\n\n### Component Structure\n\n```\ncomponent-name/\n├── component-name.tsx          # Main component\n├── component-name.types.ts     # TypeScript types\n├── component-name.test.tsx     # Unit tests\n├── component-name.stories.tsx  # Storybook stories\n├── component-name.scss         # Styles\n├── __snapshots__/              # Jest snapshots\n├── README.md                   # Component documentation\n└── index.ts                    # Public exports\n```\n\n### React Best Practices\n\n```typescript\n// ✅ CORRECT: Functional component with destructured props and performance optimizations\ninterface TokenListProps {\n  tokens: Token[];\n  onSelect: (token: Token) => void;\n}\n\nexport const TokenList = ({ tokens, onSelect }: TokenListProps) => {\n  // Use hooks\n  const [selected, setSelected] = useState<Token | null>(null);\n\n  // Memoize expensive computations (sorting large arrays)\n  const sortedTokens = useMemo(() =>\n    [...tokens].sort((a, b) => a.symbol.localeCompare(b.symbol)),\n    [tokens]  // Only re-sort when tokens array changes\n  );\n\n  // Memoize callbacks passed to children to prevent unnecessary re-renders\n  const handleClick = useCallback((token: Token) => {\n    setSelected(token);\n    onSelect(token);\n  }, [onSelect]);\n\n  return (\n    <div>\n      {sortedTokens.map(token => (\n        <TokenItem\n          key={token.address}  // Use unique ID, not array index\n          token={token}\n          onClick={handleClick}  // Stable reference prevents child re-renders\n        />\n      ))}\n    </div>\n  );\n};\n```\n\n**Performance Anti-Patterns to Avoid:**\n\n```typescript\n// ❌ WRONG: Using index as key for dynamic lists\n{tokens.map((token, index) => (\n  <TokenItem\n    key={index}  // Don't use index as key for dynamic lists\n    token={token}\n  />\n))}\n\n// ❌ WRONG: No memoization for expensive operations\nconst sortedTokens = tokens.sort((a, b) => a.value - b.value);  // Runs on every render\n\n// ❌ WRONG: Using useEffect for derived state\nconst [displayName, setDisplayName] = useState('');\nuseEffect(() => {\n  setDisplayName(`${token.symbol} (${token.name})`);  // Should calculate during render\n}, [token]);\n```\n\n**Detailed Guidelines:**\n\n- General coding: `.cursor/rules/mms-coding-guidelines/RULE.md`\n- Performance optimization:\n  - `.cursor/rules/mms-perf-rendering/RULE.md` (rendering performance)\n  - `.cursor/rules/mms-perf-hooks-effects/RULE.md` (hooks & effects)\n  - `.cursor/rules/mms-perf-react-compiler/RULE.md` (React Compiler & anti-patterns)\n  - `.cursor/rules/mms-perf-state-management/RULE.md` (Redux & state management)\n\n---\n\n## React Performance Optimization\n\n### Critical Performance Rules\n\nWhen writing React components, follow these performance best practices:\n\n#### 1. Always Use Unique IDs as Keys\n\n```typescript\n// ❌ WRONG: Using index as key for dynamic list\n{tokens.map((token, index) => (\n  <TokenItem key={index} token={token} />  // BAD!\n))}\n\n// ✅ CORRECT: Use unique identifier\n{tokens.map((token) => (\n  <TokenItem key={token.address} token={token} />\n))}\n```\n\n#### 2. Memoize Expensive Calculations\n\n```typescript\n// ❌ WRONG: Sorts on every render\nconst TokenList = ({ tokens }) => {\n  const sortedTokens = tokens.sort((a, b) => b.balance - a.balance);  // BAD!\n  return <div>{sortedTokens.map(...)}</div>;\n};\n\n// ✅ CORRECT: Memoize with useMemo\nconst TokenList = ({ tokens }) => {\n  const sortedTokens = useMemo(() =>\n    [...tokens].sort((a, b) => b.balance - a.balance),\n    [tokens]\n  );\n  return <div>{sortedTokens.map(...)}</div>;\n};\n```\n\n#### 3. Don't Use useEffect for Derived State\n\n```typescript\n// ❌ WRONG: Using effect for derived state\nconst TokenDisplay = ({ token }) => {\n  const [displayName, setDisplayName] = useState('');\n\n  useEffect(() => {\n    setDisplayName(`${token.symbol} (${token.name})`);  // BAD!\n  }, [token]);\n\n  return <div>{displayName}</div>;\n};\n\n// ✅ CORRECT: Calculate during render\nconst TokenDisplay = ({ token }) => {\n  const displayName = `${token.symbol} (${token.name})`;\n  return <div>{displayName}</div>;\n};\n```\n\n### Performance Checklist for Components\n\nBefore marking a component complete:\n\n```\n✓ List keys use unique IDs (token.address, tx.hash), not array index\n✓ Expensive operations wrapped in useMemo (sorting, filtering)\n✓ Callbacks passed to children wrapped in useCallback\n✓ Static objects/styles defined as constants outside component\n✓ No useEffect where render-time calculation would work\n✓ Large lists (100+ items) consider virtualization (react-window)\n```\n\n### When to Optimize\n\n- **DO optimize:** Frequently rendered components (list items, modals)\n- **DO optimize:** Components with expensive calculations (sorting 100+ items)\n- **DO optimize:** Deep component trees that re-render often\n- **DON'T optimize:** Simple components that render quickly\n- **DON'T optimize:** Components that rarely re-render\n\n**Rule of thumb:** Profile first with React DevTools, then optimize what matters.\n\n**See:**\n\n- `.cursor/rules/mms-perf-rendering/RULE.md` - Rendering performance (keys, memoization, virtualization)\n- `.cursor/rules/mms-perf-hooks-effects/RULE.md` - Hooks & effects optimization\n- `.cursor/rules/mms-perf-react-compiler/RULE.md` - React Compiler considerations & anti-patterns\n- `.cursor/rules/mms-perf-state-management/RULE.md` - Redux & state management optimization\n\n---\n\n## Error Handling for Agents\n\n### When You Encounter a Build Error\n\n```\n1. Read the error message carefully\n2. Check if it's a known issue in tables below\n3. Apply the solution from the table\n4. If not in table, check if it's a:\n   - LavaMoat policy error → Run `yarn lavamoat:auto`\n   - TypeScript error → Run `yarn lint:tsc`\n   - Dependency error → Run `yarn install`\n5. If still failing, try nuclear option:\n   rm -rf node_modules/ dist/ build/\n   yarn install\n   yarn lavamoat:auto\n```\n\n### When Tests Fail\n\n```\n1. IF test was passing before your changes:\n   → Your changes broke something\n   → Revert changes and understand what the test expects\n   → Fix code to match expected behavior\n\n2. IF test expects old behavior but you're changing behavior:\n   → Update the test to match new expected behavior\n   → Document why behavior changed in test/PR description\n\n3. IF E2E test fails:\n   → Check if you built test build: `yarn build:test`\n   → Check if test build is stale: delete dist/ and rebuild\n   → Run with --debug flag for more info\n   → Run with --leave-running to inspect browser state\n\n4. IF snapshot test fails:\n   → Review the snapshot diff carefully\n   → IF change is intentional: `yarn test:unit -u`\n   → IF change is not intentional: fix your code\n```\n\n### When LavaMoat Policies Fail\n\n```\n1. ALWAYS run after dependency changes: `yarn lavamoat:auto`\n2. IF auto-generation fails:\n   → Try: rm -rf node_modules/ && yarn && yarn lavamoat:auto\n3. IF still fails:\n   → Check if on correct platform (macOS vs Linux)\n   → Platform-specific dependencies need regeneration on that platform\n4. IF blocked during development:\n   → Temporarily use: yarn start (LavaMoat off by default)\n   → MUST fix before merging\n```\n\n### When You Get Circular Dependency Errors\n\n```\n1. Run: yarn circular-deps:check\n2. Fix the circular dependency by:\n   → Moving shared code to a common location\n   → Using dependency injection\n   → Breaking circular imports\n3. After fixing: yarn circular-deps:update\n4. Commit the updated development/circular-deps.jsonc\n```\n\n---\n\n## Troubleshooting\n\n### Build Issues\n\n| Problem                      | Solution                                                     |\n| ---------------------------- | ------------------------------------------------------------ |\n| `Module not found` errors    | Run `yarn install` again                                     |\n| `Out of memory` during build | Increase Node heap: `NODE_OPTIONS=--max-old-space-size=4096` |\n| LavaMoat policy errors       | Run `yarn lavamoat:auto`                                     |\n| Webpack cache issues         | Run `yarn webpack:clearcache`                                |\n| Stale build artifacts        | Delete `dist/` and `build/` directories                      |\n\n### Test Issues\n\n| Problem                 | Solution                                          |\n| ----------------------- | ------------------------------------------------- |\n| E2E tests fail to start | Build test build first: `yarn build:test`         |\n| Tests hang indefinitely | Check if port 8545 (Anvil) is available           |\n| Snapshot tests fail     | Update snapshots: `yarn test:unit -u`             |\n| Browser not launching   | Check if browser is installed and in PATH         |\n| Random E2E failures     | Use `--retries` flag or check for race conditions |\n\n### Development Issues\n\n| Problem                | Solution                                               |\n| ---------------------- | ------------------------------------------------------ |\n| Extension won't load   | Check browser console for errors                       |\n| Hot reload not working | Restart `yarn start`                                   |\n| Changes not appearing  | Hard refresh extension (chrome://extensions)           |\n| State corrupted        | Clear extension data in browser                        |\n| Port already in use    | Kill process on port: `lsof -ti:PORT \\| xargs kill -9` |\n\n### Dependency Issues\n\n| Problem                  | Solution                                        |\n| ------------------------ | ----------------------------------------------- |\n| Yarn version mismatch    | Run `corepack enable`                           |\n| Package install fails    | Clear cache: `yarn cache clean && yarn install` |\n| Peer dependency warnings | Check if packages are compatible                |\n| Allow-scripts fails      | Run `yarn allow-scripts auto`                   |\n| Attributions check fails | Run `yarn attributions:generate`                |\n\n---\n\n## Agent Pre-Completion Checklist\n\nBefore completing your task, verify you've done ALL of the following:\n\n### Code Quality Checks\n\n```bash\n# 1. Run linter and auto-fix\nyarn lint:changed:fix\n\n# 2. Run TypeScript type checking\nyarn lint:tsc\n\n# 3. Check for circular dependencies\nyarn circular-deps:check\n\n# 4. Verify no console.log or debug code remains\n# grep -r \"console.log\" in modified files\n```\n\n### Testing Checks\n\n```bash\n# 1. Run unit tests for modified files\nyarn test:unit path/to/modified-file.test.ts\n\n# 2. If you modified a controller, run controller tests\nyarn test:unit app/scripts/controllers/\n\n# 3. If you modified UI components, run component tests\nyarn test:unit ui/components/\n\n# 4. If behavior changed, ensure tests are updated\n# Tests must reflect new expected behavior\n```\n\n### Build Checks\n\n```bash\n# 1. Verify dev build works\nyarn start\n# (Let it build, check for errors, then Ctrl+C)\n\n# 2. If E2E-related, verify test build works\nyarn build:test\n# (Check for build errors)\n```\n\n### Dependency Checks (ONLY if you modified dependencies)\n\n```bash\n# 1. Deduplicate lockfile\nyarn lint:lockfile:dedupe:fix\n\n# 2. Update allow-scripts\nyarn allow-scripts auto\n\n# 3. Update LavaMoat policies\nyarn lavamoat:auto\n\n# 4. Update attributions\nyarn attributions:generate\n\n# 5. Verify all policy files are included in changes:\n# - lavamoat/webpack/*/policy.json\n# - attribution.txt\n```\n\n### File Completeness Checks\n\n```typescript\n// For NEW TypeScript files, verify they have:\n// 1. Proper imports\n// 2. Type definitions\n// 3. JSDoc comments for public functions\n// 4. Colocated .test.ts file\n// 5. Exported from index.ts (if in component folder)\n\n// For MODIFIED files, verify:\n// 1. No commented-out code\n// 2. No unused imports\n// 3. Consistent formatting\n// 4. Updated tests if behavior changed\n```\n\n### Documentation Checks\n\n```\nIF you created a new component:\n  → Add/update component README.md\n  → Add/update Storybook story (.stories.tsx)\n\nIF you changed public API (controller methods, props, etc.):\n  → Update JSDoc comments\n  → Update TypeScript types\n\nIF you changed behavior significantly:\n  → Add comment explaining why\n  → Update relevant documentation files\n```\n\n### Final Verification\n\n```\n✓ All new code is TypeScript (not JavaScript)\n✓ All tests pass: yarn test:unit\n✓ All linting passes: yarn lint:changed\n✓ No console.log or debug code\n✓ Changes are colocated with tests\n✓ Used functional components (not class components)\n✓ Props are destructured\n✓ Controllers extend BaseController\n✓ Updated related files (see File Modification Patterns)\n✓ LavaMoat policies updated (if dependencies changed)\n✓ Circular dependencies checked\n✓ Build completes without errors\n\nPerformance Checks (React Components):\n✓ Unique IDs used as keys (not array index)\n✓ Expensive calculations wrapped in useMemo\n✓ Callbacks to children wrapped in useCallback\n✓ No useEffect for derived state (calculate during render)\n✓ Large lists (100+ items) use virtualization if applicable\n```\n\n---\n\n## Additional Resources\n\n### Documentation\n\n- **Main README:** [README.md](./README.md) - Setup, building, contributing\n- **Development Guide:** [development/README.md](./development/README.md) - Build system details\n- **Testing Guide:** [docs/testing.md](./docs/testing.md) - Testing infrastructure\n- **Architecture Docs:** [docs/](./docs/) - Architecture and design docs\n\n### Coding Guidelines\n\n- **Controller Patterns:** [.cursor/rules/mms-controller-guidelines/RULE.md](./.cursor/rules/mms-controller-guidelines/RULE.md)\n- **Unit Testing:** [.cursor/rules/mms-unit-testing/RULE.md](./.cursor/rules/mms-unit-testing/RULE.md)\n- **E2E Testing:** [./test/e2e/AGENTS.md](./test/e2e/AGENTS.md)\n- **E2E CI Decision Tree:** [.github/guidelines/E2E_DECISION_TREE.md](./.github/guidelines/E2E_DECISION_TREE.md)\n- **E2E Deprecated Patterns:** [./test/e2e/AGENTS.md](./test/e2e/AGENTS.md)\n- **CI Workflows:** [.github/AGENTS.md](./.github/AGENTS.md)\n- **Front-End Performance:**\n  - [Rendering Performance](.cursor/rules/mms-perf-rendering/RULE.md) - Start here (keys, memoization, virtualization)\n  - [Hooks & Effects](.cursor/rules/mms-perf-hooks-effects/RULE.md) - useEffect best practices\n  - [React Compiler & Anti-Patterns](.cursor/rules/mms-perf-react-compiler/RULE.md) - React Compiler considerations\n  - [State Management](.cursor/rules/mms-perf-state-management/RULE.md) - Redux optimization\n- **Pull Requests:** [.cursor/rules/mms-pr-guidelines/RULE.md](./.cursor/rules/mms-pr-guidelines/RULE.md)\n- **General Coding:** [.cursor/rules/mms-coding-guidelines/RULE.md](./.cursor/rules/mms-coding-guidelines/RULE.md)\n- **Official Guidelines:** [.github/guidelines/CODING_GUIDELINES.md](./.github/guidelines/CODING_GUIDELINES.md)\n\n### Non-EVM Swaps/Bridge Agent Entrypoints\n\n- **Non-EVM Swaps/Bridge Standard:** [`docs/add-non-evm-swaps-bridge-network.md`](./docs/add-non-evm-swaps-bridge-network.md) - Canonical implementation and review standard for adding non-EVM bridge or swaps support with code-gate and LaunchDarkly rollout requirements.\n- **OpenAI/Codex Skill:** [`.agents/skills/mms-add-non-evm-network/SKILL.md`](./.agents/skills/mms-add-non-evm-network/SKILL.md) - Multi-agent skill entrypoint for the shared standard.\n- **Cursor Rule:** [`.cursor/rules/mms-add-non-evm-network/RULE.md`](./.cursor/rules/mms-add-non-evm-network/RULE.md) - Cursor rule entrypoint for the shared standard.\n- **Claude Skill:** [`.claude/skills/mms-add-non-evm-network/SKILL.md`](./.claude/skills/mms-add-non-evm-network/SKILL.md) - Claude skill entrypoint for the shared standard.\n- **Cursor Command:** [`.cursor/commands/add-non-evm-swaps-bridge-network.md`](./.cursor/commands/add-non-evm-swaps-bridge-network.md) - Cursor command shim to the Claude command entrypoint.\n\n### EVM Swaps/Bridge Agent Entrypoints\n\n- **EVM Swaps/Bridge Standard:** [`docs/add-evm-swaps-bridge-network.md`](./docs/add-evm-swaps-bridge-network.md) - Canonical implementation and review standard for adding a new EVM network to the unified swaps/bridge flow (bridge allowlist, default token pair, stablecoin slippage, and `bridgeConfigV2` rollout). Follows the MegaETH/Robinhood pattern.\n- **OpenAI/Codex Skill:** [`.agents/skills/mms-add-evm-network/SKILL.md`](./.agents/skills/mms-add-evm-network/SKILL.md) - Multi-agent skill entrypoint for the shared standard.\n- **Cursor Rule:** [`.cursor/rules/mms-add-evm-network/RULE.md`](./.cursor/rules/mms-add-evm-network/RULE.md) - Cursor rule entrypoint for the shared standard.\n- **Claude Skill:** [`.claude/skills/mms-add-evm-network/SKILL.md`](./.claude/skills/mms-add-evm-network/SKILL.md) - Claude skill entrypoint for the shared standard.\n\n### External Resources\n\n- **MetaMask Contributor Docs:** https://github.com/MetaMask/contributor-docs\n- **MetaMask Developer Docs:** https://docs.metamask.io/\n- **Community Forum:** https://community.metamask.io/\n- **User Support:** https://support.metamask.io/\n\n---\n\n## Cursor Cloud specific instructions\n\nThis section captures non-obvious, durable caveats for running this repo inside Cursor Cloud VMs. Dependency installation is handled automatically by the startup update script (nvm install/use per `.nvmrc`, `corepack enable`, `yarn install`, and creating `.metamaskrc` from `.metamaskrc.dist` if missing). Standard commands live in the sections above and in `README.md`/`package.json` — reference those instead of duplicating.\n\n### Node version gotcha (important)\n\n- The repo requires Node `>=24.13` (`.nvmrc` → `v24.13`), but the base image ships a fixed `/exec-daemon/node` (v22) shim that sits early on `PATH` and otherwise wins over nvm. `~/.bashrc` runs `nvm use default` at the end so **interactive shells get Node 24 automatically**. If a command runs Node 22 (e.g. Yarn's engines check fails), run `nvm use` (from the repo root, which reads `.nvmrc`) or prefix `PATH=\"$HOME/.nvm/versions/node/v24.13.1/bin:$PATH\"` before the command. `corepack enable` must run under Node 24 so Yarn 4 (`packageManager` in `package.json`) is used, not the legacy Yarn 1.\n\n### Running / building the extension\n\n- It is a browser extension, so `yarn start` does not open a UI — it webpack-builds + watches into `dist/chrome` (MV3). Initial build takes ~45s and then prints `compiled successfully` / `Watching for changes…`. Load `dist/chrome` as an unpacked extension in a Chromium browser to use it. Use `yarn start:mv2` for Firefox (`dist/firefox`).\n- `.metamaskrc` uses a **placeholder `INFURA_PROJECT_ID` (`00000000000`)**, which is enough to build and to onboard/create a wallet locally, but **all live RPC fails** (you'll see \"Unable to connect to <network>\"). For any on-chain flow (balances, sending, swaps), provide a real `INFURA_PROJECT_ID`, or point networks at a local `yarn anvil` chain (`:8545`).\n- Build config precedence is **`process.env` > `.metamaskprodrc` > `.metamaskrc` > `builds.yml`** (`development/webpack/utils/config.ts`; env vars win). So the Cursor Cloud secret named `INFURA_PROJECT_ID` is picked up automatically by the build in any **new** VM session (it overrides the placeholder in `.metamaskrc` with no file edit needed). Note secrets are injected only into new VMs, not one already running when the secret is added.\n\n### Visual / interactive verification (`mm` CLI)\n\n- The `mm` CLI (`node_modules/.bin/mm`, from `@metamask/client-mcp-core`) drives the extension via Playwright and is the fastest way to click through onboarding/unlock/send flows. It requires **Playwright's Chromium**, which is not part of `yarn install`: run `yarn playwright install chromium` once (cached under `~/.cache/ms-playwright`) before `mm launch`. It also needs an X display — one is available at `DISPLAY=:1` (set `export DISPLAY=:1`).\n- Launch against the existing dev build with `mm launch --context prod --extension-path dist/chrome --state onboarding`, then use `mm describe-screen` / `mm click --testid <id>` / `mm type`. During create-wallet, the on-home **Terms of Use** dialog's Agree button stays disabled until you click `terms-of-use-scroll-button` (repeatedly) to scroll the terms to the bottom. Always finish with `mm cleanup`. See `test/e2e/playwright/llm-workflow/README.md`.\n\n### E2E tests\n\n- Selenium-based E2E (`yarn test:e2e:*`) require a **test build** first (`yarn build:test` or the faster `yarn start:test`) plus a browser + driver; unit tests (`yarn test:unit`) and lint do not.\n"},"files":{"AGENTS.md":"# AGENTS.md\n\nInstructions for AI coding agents working on MetaMask Browser Extension.\n\n---\n\n## Agent Instructions Summary\n\n**Project Type:** Browser extension (Chrome/Firefox)\n**Languages:** TypeScript (required for new code), JavaScript (legacy)\n**UI Framework:** React with functional components + hooks\n**State Management:** Redux + BaseController architecture\n**Testing:** Jest (unit), Playwright (E2E)\n**Build System:** Webpack (with LavaMoat for production)\n**Security:** LavaMoat policies required for all dependency changes\n\n### Critical Rules for Agents\n\n1. **ALWAYS use TypeScript** for new files (never JavaScript)\n2. **ALWAYS run `yarn lint:changed:fix`** before committing\n3. **ALWAYS update LavaMoat policies** after dependency changes: `yarn lavamoat:auto`\n4. **ALWAYS colocate tests** with source files (`.test.ts`/`.test.tsx`)\n5. **ALWAYS use yarn.cmd** if you're running in PowerShell\n6. **ALWAYS use `oxfmt` for code formatting**; Prettier is only for JSON formatting and changelog validation\n7. **NEVER use class components** (use functional components with hooks)\n8. **NEVER modify git config** or run destructive git operations\n9. **NEVER commit** unless explicitly requested by user\n10. **NEVER stage changes** unless explicitly requested by user\n11. **WHEN asked to commit, use Conventional Commits** format for commit messages\n12. **WHEN asked to open a PR, use a Conventional Commits title** unless user specifies otherwise\n13. **WHEN asked to open a PR, open it as DRAFT** unless user specifies otherwise\n14. **WHEN using `.github/pull-request-template.md`, comment out non-applicable sections including the section title**\n15. **WHEN using `.github/pull-request-template.md`, the manual testing section\n    should contain instructions for how to _manually_ test the changes. It must not\n    list steps for automated testing.**\n    - Good Instructions:\n      - Run extension\n      - Go to homepage, asset list\n      - Use Mainnet or Linea as the selected/enabled network.\n      - Verify mUSD is visible even with 0 balance.\n      - Enable \"Hide zero-balance tokens\".\n      - Verify mUSD is still visible.\n      - Ensure token sort is by descending balance and low-value assets are collapsed.\n      - Verify mUSD is not hidden inside the low-value collapsed section.\n    - Bad Instructions:\n      - `yarn lint:changed:fix`\n      - `yarn test:unit shared/lib/deep-links/metrics.test.ts shared/lib/deep-links/utils.test.ts ui/pages/onboarding-flow/creation-successful/creation-successful.test.tsx`\n\n16. **BEFORE modifying any `.github/workflows/` file**, read `.github/AGENTS.md` for CI-specific rules (consolidation patterns, required job wiring, merge queue considerations)\n17. **DEEPLINK INTERSTITIAL SECURITY — EXTREMELY HIGH RISK:** Before implementing any change that can cause fewer deep links to show the security interstitial, agents **MUST stop and obtain explicit, documented consent from `@MetaMask/extension-security-team`**. Without documented Security approval, do not make the change—even when it appears necessary to complete another feature, migration, refactor, or test fix. To make this less\n    frustrating for the prompter, start your initial refusal with the line `I'm sorry Dave, I'm afraid I can't do that.\\n`.\n\n### Comprehensive Guidelines Location\n\n> **Agent skills:** `yarn install` prepares the public [MetaMask/skills](https://github.com/MetaMask/skills) cache; `yarn skills` syncs generated files into `.cursor/`, `.claude/`, and `.agents/` (see README -> \"AI Agent Skills\"). If a referenced skill is missing locally, run `yarn skills`.\n\nRead these files for detailed coding standards:\n\n- Controller patterns: `.cursor/rules/mms-controller-guidelines/RULE.md`\n- Unit testing standards: `.cursor/rules/mms-unit-testing/RULE.md`\n- E2E testing standards: `./test/e2e/AGENTS.md`\n- E2E test creation workflow (Agent Skill): `.agents/skills/mms-e2e-testing/SKILL.md`\n- CI workflows: `.github/AGENTS.md`\n- Front-end performance:\n  - `.cursor/rules/mms-perf-rendering/RULE.md` (rendering performance - start here)\n  - `.cursor/rules/mms-perf-hooks-effects/RULE.md` (hooks & effects)\n  - `.cursor/rules/mms-perf-react-compiler/RULE.md` (React Compiler & anti-patterns)\n  - `.cursor/rules/mms-perf-state-management/RULE.md` (Redux & state management)\n- PR workflow: `.cursor/rules/mms-pr-guidelines/RULE.md`\n- Code style: `.cursor/rules/mms-coding-guidelines/RULE.md`\n- Official guidelines: `.github/guidelines/CODING_GUIDELINES.md`\n\n---\n\n## Quick Setup\n\n### Prerequisites\n\n- **Node.js+** (use `nvm use` to auto-select development version specified in `.nvmrc`)\n- **Yarn** (managed by Corepack, included with Node.js)\n- **Infura API Key** (free at https://infura.io)\n\n### First-Time Setup\n\n```bash\n# 1. Enable Corepack (manages Yarn)\ncorepack enable\n\n# 2. Install dependencies\nyarn install\n\n# 3. Copy and configure environment\ncp .metamaskrc.dist .metamaskrc\n\n# 4. Edit .metamaskrc and add your Infura API key\n# INFURA_PROJECT_ID=your_key_here\n\n# 5. Start development build (Chrome/Chromium with MV3)\nyarn start\n\n# 6. Load extension in browser\n# Chrome: See docs/add-to-chrome.md\n# Firefox: See docs/add-to-firefox.md\n```\n\n### Optional Configuration\n\nIn `.metamaskrc`, you can also configure:\n\n- `PASSWORD` - Auto-fill development wallet password\n- `SEGMENT_WRITE_KEY` - For MetaMetrics debugging\n- `SENTRY_DSN` - For error tracking debugging\n\n### Common Setup Issues\n\n| Issue                            | Solution                                                                                                |\n| -------------------------------- | ------------------------------------------------------------------------------------------------------- |\n| `command not found: yarn`        | Run `corepack enable`                                                                                   |\n| Build fails with policy errors   | Run `yarn lavamoat:auto`                                                                                |\n| Invalid Infura key error         | Check `INFURA_PROJECT_ID` in `.metamaskrc`                                                              |\n| Anvil won't start                | Ensure port 8545 is available and `yarn foundryup` has installed the binary                             |\n| Git hooks not working in VS Code | Follow [Husky troubleshooting](https://typicode.github.io/husky/troubleshooting.html#command-not-found) |\n\n---\n\n## Common Commands\n\n### Building\n\n```bash\n# Development Builds (with file watching and hot reload)\nyarn start                  # Chrome MV3 (default)\nyarn start:mv2             # Firefox MV2\nyarn start:flask           # Flask build (beta features)\nyarn start:with-state      # Start with preloaded wallet state\n\n# Production Builds\nyarn dist                  # Chrome MV3\nyarn dist:mv2              # Firefox MV2\n\n# Test Builds (for E2E testing)\nyarn build:test            # Build with LavaMoat enabled\nyarn start:test            # Build with LavaMoat disabled (faster iteration)\nyarn build:test:flask      # Flask test build\nyarn build:test:mv2        # Firefox MV2 test build\n\n# Download pre-built test builds (fastest)\nyarn download-builds --build-type test\n```\n\n**Build System Notes:**\n\n- `yarn start` uses Webpack (faster, development)\n- `yarn dist` uses Webpack + LavaMoat (production)\n- `yarn start` skips LavaMoat by default for speed; use `yarn start:lavamoat` to enable it\n- Test builds are required for E2E tests (not dev builds)\n\n### Testing\n\n```bash\n# Unit Tests\nyarn test                  # Lint + unit tests\nyarn test:unit             # Unit tests only\nyarn test:unit:watch       # Watch mode\nyarn test:unit:coverage    # With coverage report\n\n# E2E Tests\nyarn test:e2e:chrome       # Run all E2E tests (Chrome)\nyarn test:e2e:firefox      # Run all E2E tests (Firefox)\n\n# Single E2E test with options\nyarn test:e2e:single test/e2e/tests/account-menu/account-details.spec.js \\\n  --browser=chrome \\\n  --leave-running \\\n  --debug\n\n# Integration Tests\nyarn test:integration\nyarn test:integration:coverage\n\n# Playwright Tests\nyarn test:e2e:benchmark    # Performance benchmarks\n```\n\n**Testing Notes:**\n\n- Unit tests should be colocated with source files (`.test.ts`/`.test.tsx`)\n- Always create a test build before running E2E tests\n- Use `--leave-running` to debug failed E2E tests\n- See `.cursor/rules/mms-unit-testing/RULE.md` for testing standards\n\n### Linting & Formatting\n\n```bash\n# Run all linters\nyarn lint                  # JSON formatting + oxfmt + ESLint + TypeScript + Styles + Images\n\n# Individual linters\nyarn lint:json             # Prettier JSON formatting check\nyarn lint:format           # oxfmt code formatting check\nyarn lint:eslint           # ESLint only\nyarn lint:tsc              # TypeScript type checking\nyarn lint:styles           # Stylelint for SCSS\n\n# Auto-fix\nyarn lint:fix              # Fix all auto-fixable issues\nyarn lint:json:fix         # Fix JSON formatting with Prettier\nyarn lint:format:fix       # Fix code formatting with oxfmt\nyarn lint:eslint:fix       # Fix ESLint issues\n\n# Lint only changed files (faster)\nyarn lint:changed\nyarn lint:changed:fix\n```\n\n**Formatter Notes:**\n\n- Use `yarn lint:changed:fix` for normal agent work; it applies the repo's formatter choices to changed files.\n- Use `yarn lint:format:fix` or `oxfmt -c oxfmt.config.mts` for JavaScript, TypeScript, JSX, TSX, and other code formatting.\n- Use `yarn lint:json:fix` for JSON files such as `package.json`; this is the main remaining Prettier formatting path.\n- Do not run Prettier directly on code files.\n\n### Development Tools\n\n```bash\n# Test Dapps\nyarn dapp                  # Start test dapp on :8080\nyarn dapp-multichain       # Multichain test dapp\nyarn dapp-solana           # Solana test dapp\nyarn dapp-chain            # Dapp with local Anvil\n\n# DevTools\nyarn devtools:react        # React DevTools\nyarn devtools:redux        # Redux DevTools\nyarn start:dev             # Start with both DevTools\n\n# Local Blockchain\nyarn anvil                 # Start Anvil (Foundry) on port 8545\n\n# Storybook\nyarn storybook             # Component documentation/development\nyarn storybook:build       # Build static storybook\n\n# Git Hooks\nyarn githooks:install      # Install pre-commit hooks\n```\n\n### Dependency Management\n\n```bash\n# When adding/updating/removing dependencies:\n\n# 1. Install/update package\nyarn add package-name\nyarn upgrade package-name\n\n# 2. Deduplicate lockfile\nyarn lint:lockfile:dedupe:fix\n\n# 3. Update allow-scripts (determines which install scripts can run)\nyarn allow-scripts auto\n\n# 4. Update LavaMoat policies\nyarn lavamoat:auto         # Regenerates the webpack LavaMoat policies\n\n# 5. Update attributions\nyarn attributions:generate\n\n# Or use MetaMask bot (for team members with repo branch):\n# Comment on PR: @metamaskbot update-policies\n# Comment on PR: @metamaskbot update-attributions\n```\n\n**Important:** Always update LavaMoat policies and attributions when dependencies change!\n\n---\n\n## Common Agent Workflows\n\n### Workflow: Adding a New Feature\n\n```bash\n# 1. Start development build\nyarn start\n\n# 2. Create new files (MUST be TypeScript)\n# - Component: ui/components/feature-name/feature-name.tsx\n# - Test: ui/components/feature-name/feature-name.test.tsx\n# - Types: ui/components/feature-name/feature-name.types.ts\n\n# 3. Make changes\n\n# 4. Run lint and tests on changed files\nyarn lint:changed:fix\nyarn test:unit path/to/feature-name.test.tsx\n\n# 5. If test needs E2E, build test build\nyarn build:test\nyarn test:e2e:single test/e2e/tests/new-test.spec.js --browser=chrome\n```\n\n### Workflow: Modifying Existing Code\n\n```bash\n# 1. Identify file type and read relevant guidelines\n# - Controller? Read .cursor/rules/mms-controller-guidelines/RULE.md\n# - React component? Read .cursor/rules/mms-coding-guidelines/RULE.md\n# - Test? Read .cursor/rules/mms-unit-testing/RULE.md\n\n# 2. Make changes following guidelines\n\n# 3. Run linter on changed files\nyarn lint:changed:fix\n\n# 4. Run existing tests\nyarn test:unit path/to/modified-file.test.ts\n\n# 5. Update tests if behavior changed\n\n# 6. Check for circular dependencies\nyarn circular-deps:check\n```\n\n### Workflow: Adding/Updating Dependencies\n\n```bash\n# 1. Add or update package\nyarn add package-name\n# OR\nyarn upgrade package-name\n\n# 2. REQUIRED: Deduplicate lockfile\nyarn lint:lockfile:dedupe:fix\n\n# 3. REQUIRED: Update allow-scripts\nyarn allow-scripts auto\n\n# 4. REQUIRED: Update LavaMoat policies (this may take several minutes)\nyarn lavamoat:auto\n\n# 5. REQUIRED: Update attributions\nyarn attributions:generate\n\n# 6. Test the build\nyarn build:test\n\n# 7. Commit all changes including:\n#    - package.json\n#    - yarn.lock\n#    - lavamoat/webpack/*/policy.json\n#    - attribution.txt\n```\n\n### Workflow: Fixing a Bug\n\n```bash\n# 1. Create a failing test that reproduces the bug\n# Add test to existing .test.ts file or create new one\n\n# 2. Run the test to confirm it fails\nyarn test:unit path/to/test-file.test.ts\n\n# 3. Fix the bug in source code\n\n# 4. Run test again to confirm fix\nyarn test:unit path/to/test-file.test.ts\n\n# 5. Run all related tests\nyarn test:unit\n\n# 6. Lint changes\nyarn lint:changed:fix\n\n# 7. If bug is in E2E scenario\nyarn build:test\nyarn test:e2e:single path/to/test.spec.js --browser=chrome\n```\n\n### Workflow: Creating a Controller\n\n```bash\n# 1. MUST read controller guidelines first\n# Read .cursor/rules/mms-controller-guidelines/RULE.md\n\n# 2. Create controller file (TypeScript only)\n# Location: app/scripts/controllers/your-controller/your-controller.ts\n\n# 3. Controller MUST:\n#    - Extend BaseController from @metamask/base-controller\n#    - Define state type\n#    - Define metadata for all state properties\n#    - Export getDefaultYourControllerState() function\n#    - Use messenger for inter-controller communication\n#    - Use selectors for derived state (not getter methods)\n\n# 4. Create test file\n# Location: app/scripts/controllers/your-controller/your-controller.test.ts\n\n# 5. Create types file\n# Location: app/scripts/controllers/your-controller/types.ts\n\n# 6. Run tests\nyarn test:unit app/scripts/controllers/your-controller/your-controller.test.ts\n\n# 7. Lint\nyarn lint:changed:fix\n```\n\n### Controller Development Patterns\n\nWhen creating a controller, follow these critical patterns from `.cursor/rules/mms-controller-guidelines/RULE.md`:\n\n#### State Metadata Requirements\n\n**Every state property MUST have metadata with these properties:**\n\n| Property                                | Type    | Purpose                   | Example Value              |\n| --------------------------------------- | ------- | ------------------------- | -------------------------- |\n| `anonymous` OR `includeInDebugSnapshot` | boolean | Safe for Sentry? (no PII) | `anonymous: true`          |\n| `includeInStateLogs`                    | boolean | Include in state logs?    | `false` for sensitive data |\n| `persist`                               | boolean | Save to storage?          | `true` for user data       |\n| `usedInUi`                              | boolean | Used by UI?               | `true` if rendered         |\n\n**Example:**\n\n```typescript\nconst tokensControllerMetadata = {\n  tokens: {\n    anonymous: true, // No PII, safe for Sentry\n    includeInStateLogs: true, // Safe to include in logs\n    persist: true, // Should be saved\n    usedInUi: true, // Rendered in UI\n  },\n  apiKey: {\n    anonymous: false, // Sensitive\n    includeInStateLogs: false, // Must exclude from logs\n    persist: true, // But should be saved\n    usedInUi: false, // Backend only\n  },\n};\n```\n\n#### Default State Function Pattern\n\n**ALWAYS export function, NEVER export object:**\n\n```typescript\n✅ CORRECT: Returns new object each time\nexport function getDefaultTokensControllerState(): TokensControllerState {\n  return {\n    tokens: [],\n    lastUpdated: 0,\n  };\n}\n\n❌ WRONG: Shared object reference (mutation risk)\nexport const defaultTokensControllerState = {\n  tokens: [],\n  lastUpdated: 0,\n};\n```\n\n#### Constructor Single Options Bag\n\n**ALWAYS use single options object, NO positional arguments:**\n\n```typescript\n✅ CORRECT:\nconstructor({\n  messenger,\n  state = {},\n  apiKey,        // All options in one bag\n  isEnabled,\n}: TokensControllerOptions) {\n  super({\n    name: 'TokensController',\n    metadata: tokensControllerMetadata,\n    messenger,\n    state: { ...getDefaultTokensControllerState(), ...state },\n  });\n}\n\n❌ WRONG:\nconstructor(\n  options: ControllerOptions,\n  apiKey: string,     // Separate positional arg - BAD\n  isEnabled: boolean, // Separate positional arg - BAD\n) { }\n```\n\n#### Action Methods (Not Setters)\n\n**Model high-level user actions, not property changes:**\n\n```typescript\n❌ WRONG: Generic setters\nsetTokenData(data: any) { }\nupdateField(field: string, value: any) { }\n\n✅ CORRECT: Action-based methods\naddToken(token: Token) {\n  if (!token.address) {\n    throw new Error('Token address required');\n  }\n\n  this.update((state) => {\n    state.tokens.push(token);\n    state.lastUpdated = Date.now();\n  });\n}\n\nremoveToken(address: string) {\n  this.update((state) => {\n    state.tokens = state.tokens.filter(t => t.address !== address);\n    state.lastUpdated = Date.now();\n  });\n}\n```\n\n#### Keep State Minimal - Use Selectors\n\n**NEVER store derived values in state:**\n\n```typescript\n❌ WRONG: Derived values in state\ntype State = {\n  tokens: Token[];\n  tokenCount: number;  // DON'T STORE - derive it!\n  hasTokens: boolean;  // DON'T STORE - derive it!\n};\n\n✅ CORRECT: Minimal state + selectors\ntype State = {\n  tokens: Token[];  // Only essential data\n};\n\n// Export selectors for derived values\nexport const tokensControllerSelectors = {\n  selectTokens: (state: State) => state.tokens,\n  selectTokenCount: (state: State) => state.tokens.length,\n  selectHasTokens: (state: State) => state.tokens.length > 0,\n};\n```\n\n#### Cleanup with destroy()\n\n**Implement if controller has background tasks:**\n\n```typescript\nclass TokensController extends BaseController</*...*/> {\n  #pollInterval: NodeJS.Timeout | null = null;\n\n  constructor(options: Options) {\n    super(/* ... */);\n    if (options.enablePolling) {\n      this.#startPolling();\n    }\n  }\n\n  destroy() {\n    // Clean up resources\n    if (this.#pollInterval) {\n      clearInterval(this.#pollInterval);\n      this.#pollInterval = null;\n    }\n\n    // Call super to clean up messenger\n    super.destroy();\n  }\n}\n```\n\n**See `.cursor/rules/mms-controller-guidelines/RULE.md` for complete patterns with detailed examples.**\n\n---\n\n### Decision: Which Test Build to Use?\n\n```\nIF you need to run E2E tests:\n  IF you're iterating/debugging:\n    → Use `yarn start:test` (faster, LavaMoat disabled)\n  IF you're doing final verification:\n    → Use `yarn build:test` (slower, LavaMoat enabled, matches production)\n\nIF you're developing with feature flags:\n  → Use `FEATURE_FLAG=1 yarn build:test`\n  → Then run E2E: `yarn test:e2e:single path/to/test.spec.js`\n\nIF you're working on Firefox compatibility:\n  → Use `yarn build:test:mv2`\n  → Then test: `yarn test:e2e:firefox`\n```\n\n### Decision: Where to Put New Code?\n\n```\nIF creating a controller:\n  → app/scripts/controllers/controller-name/\n\nIF creating a UI component:\n  → ui/components/component-name/ (for reusable components)\n  → ui/pages/page-name/ (for page-level components)\n\nIF creating a utility function:\n  → shared/lib/ (if used by both background and UI)\n  → app/scripts/lib/ (if only used by background)\n  → ui/helpers/ (if only used by UI)\n\nIF creating constants:\n  → shared/constants/\n\nIF creating TypeScript types:\n  → shared/types/ (for shared types)\n  → types/ (for project-wide types)\n  → [component-dir]/types.ts (for component-specific types)\n\nIF creating a state migration:\n  → Run: yarn generate:migration\n  → Edits: app/scripts/migrations/[number].ts\n```\n\n### Decision: Which Browser Target?\n\n```\nIF user specifies Chrome, Edge, or Brave:\n  → Use MV3 (Manifest V3)\n  → Commands: yarn start, yarn dist, yarn build:test\n\nIF user specifies Firefox:\n  → Use MV2 (Manifest V2)\n  → Commands: yarn start:mv2, yarn dist:mv2, yarn build:test:mv2\n  → Set ENABLE_MV3=false\n\nIF user doesn't specify:\n  → Default to Chrome MV3\n  → Use: yarn start\n```\n\n---\n\n## Project Structure\n\n### High-Level Directory Layout\n\n```\nmetamask-extension/\n├── app/\n│   ├── scripts/           # Background scripts & controllers (860 TS, 234 JS)\n│   │   ├── controllers/   # Business logic controllers\n│   │   ├── lib/           # Utility libraries\n│   │   └── migrations/    # State migration scripts\n│   ├── manifest/          # Browser extension manifests (MV2/MV3)\n│   ├── images/            # Icons and images\n│   └── *.html             # Extension HTML pages\n├── ui/                    # React UI code (1,412 TSX, 1,292 JS)\n│   ├── components/        # Reusable React components\n│   ├── pages/             # Page-level components\n│   ├── ducks/             # Redux slices (state management)\n│   ├── hooks/             # Custom React hooks\n│   ├── selectors/         # Redux selectors\n│   └── store/             # Redux store configuration\n├── shared/                # Code shared between background and UI\n│   ├── constants/         # Shared constants (47 TS files)\n│   ├── lib/               # Shared utilities (122 TS files)\n│   ├── modules/           # Shared modules (45 TS files)\n│   └── types/             # TypeScript type definitions\n├── test/                  # Test files (586 TS, 79 JS)\n│   ├── e2e/               # End-to-end tests\n│   ├── integration/       # Integration tests\n│   └── *.test.*           # Unit tests (colocated with source)\n├── development/           # Build system and dev tools\n│   ├── build/             # Build scripts\n│   └── webpack/           # Webpack configuration\n├── docs/                  # Documentation (54 files)\n└── .cursor/rules/         # AI agent coding guidelines\n```\n\n### Finding Specific Code\n\n| What You Need                | Where to Look                                   |\n| ---------------------------- | ----------------------------------------------- |\n| Controllers (business logic) | `app/scripts/controllers/`                      |\n| React Components             | `ui/components/` or `ui/pages/`                 |\n| Redux State Management       | `ui/ducks/` (slices) and `ui/selectors/`        |\n| Background Scripts           | `app/scripts/`                                  |\n| Constants                    | `shared/constants/`                             |\n| Utility Functions            | `shared/lib/` or `ui/helpers/`                  |\n| Type Definitions             | `shared/types/` or `types/`                     |\n| State Migrations             | `app/scripts/migrations/`                       |\n| Build Configuration          | `development/build/` and `development/webpack/` |\n| Extension Manifests          | `app/manifest/v2/` or `app/manifest/v3/`        |\n\n### Architecture Patterns\n\n**Controllers** (Background Scripts):\n\n- Inherit from `BaseController` (from `@metamask/base-controller`)\n- Manage wallet state and business logic\n- Communicate via Messenger pattern (pub/sub)\n- Use selectors for derived state (not getter methods)\n- See `.cursor/rules/mms-controller-guidelines/RULE.md` for detailed patterns\n\n**React Components** (UI):\n\n- Functional components with hooks (no class components)\n- Props destructured in function parameters\n- Redux for global state, local state for UI-only data\n- Performance optimizations: useMemo, useCallback, React.memo\n- Unique IDs as keys (not array index for dynamic lists)\n- Organized in component folders with tests, styles, and types\n- See `.cursor/rules/mms-coding-guidelines/RULE.md` and `.cursor/rules/mms-perf-rendering/RULE.md`\n\n**Testing**:\n\n- Unit tests colocated with source files (`.test.ts`)\n- Jest for unit tests, Playwright for E2E\n- Test files organized with `describe` blocks by method/function\n- See `.cursor/rules/mms-unit-testing/RULE.md` for testing patterns\n\n### File Modification Patterns\n\nWhen you modify certain files, you typically need to update related files:\n\n**When modifying a Controller:**\n\n```\napp/scripts/controllers/foo/foo-controller.ts → ALSO UPDATE:\n├── app/scripts/controllers/foo/foo-controller.test.ts (tests)\n├── app/scripts/controllers/foo/types.ts (if types changed)\n└── app/scripts/metamask-controller.ts (if adding/removing controller)\n```\n\n**When modifying a React Component:**\n\n```\nui/components/foo/foo.tsx → ALSO UPDATE:\n├── ui/components/foo/foo.test.tsx (tests)\n├── ui/components/foo/foo.types.ts (if props changed)\n├── ui/components/foo/foo.stories.tsx (if props changed)\n└── ui/components/foo/index.ts (if exports changed)\n```\n\n**When modifying Redux State (ducks):**\n\n```\nui/ducks/foo/foo.ts → ALSO UPDATE:\n├── ui/ducks/foo/foo.test.ts (tests)\n├── ui/selectors/foo.ts (selectors that depend on this state)\n└── ui/components/*/foo-component.tsx (components using this state)\n```\n\n**When adding/removing dependencies:**\n\n```\npackage.json → MUST UPDATE:\n├── yarn.lock (run yarn install)\n├── lavamoat/webpack/*/policy.json (run yarn lavamoat:auto)\n└── attribution.txt (run yarn attributions:generate)\n```\n\n**When modifying state shape:**\n\n```\napp/scripts/controllers/foo/foo-controller.ts → MUST CREATE:\n└── app/scripts/migrations/[next-number].ts (migration for state change)\n```\n\n---\n\n## Working with Feature Flags\n\n### What are Feature Flags?\n\nFeature flags allow you to enable/disable features during development. They're defined in `.metamaskrc` and control which features are built into the extension.\n\n### Available Feature Flags\n\nCheck `.metamaskrc.dist` for the current list of feature flags. Common ones:\n\n- `MULTICHAIN` - Multi-chain support\n- `BLOCKAID_PUBLIC_KEY` - Security features\n- Various experimental features\n\n### Using Feature Flags\n\n**Method 1: Configure in `.metamaskrc`**\n\n```bash\n# Edit .metamaskrc\nMULTICHAIN=1\nOTHER_FEATURE=1\n\n# Build with flags\nyarn build:test\n```\n\n**Method 2: Pass as environment variable**\n\n```bash\n# Enable for single build\nMULTICHAIN=1 yarn build:test\nMULTICHAIN=1 yarn start:test\n\n# Run E2E tests with feature enabled\nMULTICHAIN=1 yarn build:test\nyarn test:e2e:single test/e2e/tests/some-test.spec.js\n```\n\n### Remote Feature Flags\n\nOverride remote feature flags using `.manifest-overrides.json`:\n\n```json\n{\n  \"_flags\": {\n    \"remoteFeatureFlags\": {\n      \"testBooleanFlag\": false\n    }\n  }\n}\n```\n\nSet in `.metamaskrc`:\n\n```\nMANIFEST_OVERRIDES=.manifest-overrides.json\n```\n\n---\n\n## LavaMoat Security System\n\n### What is LavaMoat?\n\nLavaMoat is a supply chain security tool that restricts what dependencies can do (file access, network access, etc.). It's enabled in production builds to protect users.\n\n### When to Update LavaMoat Policies\n\nUpdate policies whenever you:\n\n- ✅ Add a new dependency\n- ✅ Update an existing dependency\n- ✅ Remove a dependency\n- ✅ Change how code accesses Node.js APIs\n- ✅ See \"LavaMoat policy violation\" errors\n\n### How to Update Policies\n\n**Automated (Recommended):**\n\n```bash\n# Regenerate the webpack LavaMoat policies\nyarn lavamoat:auto\n\n# Or use MetaMask bot (team members only):\n# Comment on PR: @metamaskbot update-policies\n```\n\n**Manual:**\n\n```bash\n# Compile the webpack build tooling\nyarn webpack:tsc\n\n# Regenerate the webpack build tooling policy\nyarn webpack:lavamoat:policy:build\n\n# Regenerate the Firefox MV2 application policies\nyarn webpack:lavamoat:policy:mv2\n\n# Regenerate the Chrome MV3 application policies\nyarn webpack:lavamoat:policy:mv3\n\n# If policies still fail after regeneration:\nrm -rf node_modules/ && yarn\n# Then compile the webpack build tooling and rerun the affected policy command above.\n```\n\n### Common Policy Issues\n\n- **Policy fails on macOS/Windows:** Platform-specific optional dependencies. Regenerate on the target platform.\n- **Dynamic imports fail:** LavaMoat's static analysis may miss dynamic code. May need manual policy updates.\n- **Can't build at all:** Use `yarn start` (LavaMoat off by default) for development, but fix before merging.\n\n### Development Without LavaMoat\n\nFor faster iteration during development (LavaMoat is off by default):\n\n```bash\nyarn start       # Development build\nyarn start:test  # Test build\n```\n\n**⚠️ Warning:** Always test with LavaMoat enabled before merging!\n\n---\n\n## Browser Compatibility\n\n### Manifest V2 vs Manifest V3\n\n| Feature           | MV2 (Firefox)         | MV3 (Chrome/Chromium) |\n| ----------------- | --------------------- | --------------------- |\n| **Build Flag**    | `ENABLE_MV3=false`    | Default               |\n| **Start Command** | `yarn start:mv2`      | `yarn start`          |\n| **Dist Command**  | `yarn dist:mv2`       | `yarn dist`           |\n| **Background**    | Background page       | Service worker        |\n| **Permissions**   | Broader access        | More restrictive      |\n| **APIs**          | `browser.*` namespace | `chrome.*` namespace  |\n\n### Building for Different Browsers\n\n```bash\n# Chrome / Edge / Brave (MV3)\nyarn start                    # Development\nyarn dist                     # Production\n\n# Firefox (MV2)\nyarn start:mv2                # Development\nyarn dist:mv2                 # Production\n\n# Test builds\nyarn build:test               # Chrome MV3\nyarn build:test:mv2           # Firefox MV2\n```\n\n### Browser-Specific Considerations\n\n**Firefox:**\n\n- Must use MV2 (Manifest V2)\n- Use `webextension-polyfill` for cross-browser compatibility\n- Test with `yarn test:e2e:firefox`\n\n**Chrome/Chromium:**\n\n- Uses MV3 (Manifest V3) by default\n- Service worker limitations (no DOM access in background)\n- Test with `yarn test:e2e:chrome`\n\n**Both:**\n\n- Code should use `browser.*` namespace (polyfilled for Chrome)\n- Conditional logic for browser differences in `app/scripts/lib/util.js`\n\n---\n\n## Testing Strategy\n\n### Unit Tests\n\n**Location:** Colocated with source files (`.test.ts` or `.test.tsx`)\n\n**Running:**\n\n```bash\nyarn test:unit              # All unit tests\nyarn test:unit:watch        # Watch mode\nyarn test:unit:coverage     # With coverage\n```\n\n**Key Principles:**\n\n- Use Jest (not Mocha or Tape)\n- Test through public interfaces (not private methods)\n- Keep critical test data inline\n- Use `describe` blocks to organize by method/function\n- Never use \"should\" in test names (use present tense)\n\n**Example:**\n\n```typescript\ndescribe('TokensController', () => {\n  describe('addToken', () => {\n    it('adds the token to state', () => {\n      // Arrange, Act, Assert\n    });\n\n    it('throws error when token address is missing', () => {\n      // Test error case\n    });\n  });\n});\n```\n\n**Detailed Guidelines:** See `.cursor/rules/mms-unit-testing/RULE.md`\n\n### E2E Tests\n\n**Location:** `test/e2e/tests/`\n\n**Running:**\n\n```bash\n# Must build test build first!\nyarn build:test              # or yarn start:test\n\n# Run E2E tests\nyarn test:e2e:chrome         # All Chrome tests\nyarn test:e2e:firefox        # All Firefox tests\n\n# Single test with debug\nyarn test:e2e:single test/e2e/tests/TEST_NAME.spec.js \\\n  --browser=chrome \\\n  --debug \\\n  --leave-running\n```\n\n**Options:**\n\n- `--browser` - chrome, firefox, or all\n- `--debug` - Verbose logging\n- `--leave-running` - Keep browser open on failure\n- `--retries` - Number of retries on failure\n- `--update-snapshot` - Update snapshots\n\n**E2E Best Practices:**\n\nFind them in [](./test/e2e/AGENTS.md)\n\n### Visual Verification (MetaMask CLI / Playwright)\n\nWhen the user explicitly asks for visual verification of UI behavior (e.g., \"verify this works\", \"confirm visually\", \"take screenshots\", \"click through onboarding/unlock/send flow\"), you **MUST** use the MetaMask visual testing skill and `mm` cli tools instead of only reasoning about code.\n\n**Load the skill:** `/metamask-visual-testing`\n\n**Workflow:**\n\n0. Build if needed (`yarn build:test`), then `mm launch` (this auto-starts the daemon)\n1. **Query prior knowledge:** Run `mm knowledge-search \"<flow>\"` and `mm knowledge-sessions` to reuse previously discovered flows and avoid wasting tokens rediscovering known sequences.\n2. Always call `mm describe-screen` before acting to discover targets\n3. Use `mm click`/`mm type`/`mm wait-for` to drive the flow\n4. Provide evidence via `mm screenshot` and/or final `mm describe-screen` output\n5. Always end with `mm cleanup` (even on failure)\n\n**If CLI is unavailable or denied:** Say so explicitly and explain what's missing. Do not claim you verified without actual tool output as evidence.\n\n**Skill location:** `.claude/skills/mms-visual-testing/SKILL.md`\n\n**MM CLI architecture docs:** `test/e2e/playwright/llm-workflow/README.md`\n\n### Integration Tests\n\n**Location:** `test/integration/`\n\n**Running:**\n\n```bash\nyarn test:integration\nyarn test:integration:coverage\n```\n\n**Coverage Goals:**\n\n- Unit tests: > 80% coverage\n- Critical paths: > 90% coverage\n- E2E tests: Cover main user workflows\n\n---\n\n## State Migrations\n\n### What are Migrations?\n\nWhen MetaMask updates, the stored state format might change. Migrations transform old state to new format automatically.\n\n### Creating a Migration\n\n```bash\n# Generate migration template\nyarn generate:migration\n\n# Creates: app/scripts/migrations/XXX.ts (next number)\n```\n\n### Migration Guidelines\n\n1. **Always create migrations for state changes**\n2. **Test migrations thoroughly** (old state → new state)\n3. **Handle missing data gracefully** (some users may have old/corrupted state)\n4. **Never mutate input state** (return new state object)\n5. **Include version number** in migration metadata\n\n**Example Migration:**\n\n```typescript\nimport { cloneDeep } from 'lodash';\n\nconst version = 123;\n\nexport default {\n  version,\n  async migrate(originalVersionedData: any) {\n    const versionedData = cloneDeep(originalVersionedData);\n    versionedData.meta.version = version;\n    transformData(versionedData.data);\n    return versionedData;\n  },\n};\n\nfunction transformData(state: any): void {\n  // Transform state.data\n  if (state.PreferencesController) {\n    state.PreferencesController.newProperty = 'defaultValue';\n  }\n}\n```\n\n---\n\n## Pull Request Workflow\n\n### Before Creating a PR\n\n- [ ] All tests pass: `yarn test`\n- [ ] Linting passes: `yarn lint`\n- [ ] No console.logs or debug code\n- [ ] Changes are covered by tests\n- [ ] LavaMoat policies updated (if dependencies changed)\n- [ ] Attributions updated (if dependencies changed)\n\n### Creating a PR\n\n**Reference:** Follow the [PR template](https://github.com/MetaMask/metamask-extension/blob/main/.github/pull-request-template.md) when creating pull requests.\n\n### Default Agent Commit/Push/PR Flow (When Requested)\n\nExecute only the steps that correspond to what the user explicitly requested. Do not perform additional steps (e.g., do not push or open a PR if the user only asked to commit).\n\n#### When asked to **commit**\n\n1. Run `yarn lint:changed:fix` before creating the commit.\n2. Stage only files relevant to the requested change.\n3. Create a commit using Conventional Commits format: `<type>(optional-scope): <summary>`.\n\n#### When asked to **push**\n\nComplete all steps for **commit** above, then:\n\n4. Push the current branch to `origin`.\n\n#### When asked to **open a PR**\n\nComplete all steps for **push** above, then:\n\n5. Open a **draft** PR with:\n   - A Conventional Commits PR title (normally matching the commit summary).\n   - A PR body based on `.github/pull-request-template.md`.\n   - Any non-applicable template section commented out as a full block, including the section heading, for example:\n\n```markdown\n<!--\n## **Screenshots/Recordings**\n### **Before**\n### **After**\n-->\n```\n\n6. Do not mark the PR as \"Ready for review\" unless explicitly requested.\n\n**PR Title Format:**\n\n- Clear and descriptive\n- Will be used in squash commit message\n- Example: \"feat(networks): add token validation for custom networks\"\n\n**Description Section:**\n\n- **Context:** What's the background?\n- **Problem:** What needs to be fixed/added?\n- **Solution:** How do your changes address it?\n- Answer: \"What is the reason for the change?\" and \"What is the improvement/solution?\"\n\n**Changelog Entry:**\n\n- If End-User-Facing: Write a short user-facing description in past tense\n  - Example: `CHANGELOG entry: Added a new tab for users to see their NFTs`\n  - Example: `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker`\n- If not End-User-Facing: Write `CHANGELOG entry: null` or label with `no-changelog`\n\n**Related Issues:**\n\n- List all related issues using `Fixes: #issue-number` format\n- Link to related PRs if applicable\n\n**Manual Testing Steps:**\n\n- Provide numbered steps to test the changes\n- Include specific pages/features to test\n- Example:\n  1. Go to this page...\n  2. Click this button...\n  3. Verify this behavior...\n\n**Screenshots/Recordings:**\n\n- **Before:** Screenshots/videos showing the previous state (for UI changes)\n- **After:** Screenshots/videos showing the new state (for UI changes)\n- Required for all UI changes\n\n**Pre-merge Author Checklist:**\n\n- [ ] Followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md)\n- [ ] Completed the PR template to the best of ability\n- [ ] Included tests if applicable\n- [ ] Documented code using [JSDoc](https://jsdoc.app/) format if applicable\n- [ ] Applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md))\n\n**Additional PR Comments:**\n\n- Call out non-obvious changes\n- Explain complex logic inline\n- Link to related issues/PRs\n\n### During Review\n\n- Respond to all feedback\n- Link to commits that address feedback (e.g., \"Fixed in abc1234\")\n- **Avoid rebasing after receiving comments** (makes review harder)\n- Push new commits instead of amending\n- If the Conventional Commit type in the PR's title is `chore`, please evaluate if `chore` is truly the best choice. We also have two custom types: `bump` (for package updates) and `release` (for tasks on a release branch and tasks that are all about getting a release ready).\n\n### Before Merging\n\n- [ ] All conversations resolved\n- [ ] Required approvals received\n- [ ] CI checks passing\n- [ ] Review the squash commit message (auto-generated from PR)\n- [ ] **Don't modify the commit title format** (must be: `Title (#number)`)\n\n**Detailed Guidelines:** See `.cursor/rules/mms-pr-guidelines/RULE.md`\n\n---\n\n## Code Style & Standards\n\n### General Principles\n\n1. **TypeScript for all new code** (no new JavaScript files)\n2. **Functional components with hooks** (no class components)\n3. **Destructure props** in function parameters\n4. **Small, focused functions** (single responsibility)\n5. **Early returns** to reduce nesting\n6. **DRY principle** (extract repeated code)\n\n### Naming Conventions\n\n```typescript\n// Components: PascalCase\nexport const TokenListItem = () => {};\n\n// Functions: camelCase\nconst handleInputChange = () => {};\n\n// Custom hooks: use prefix\nconst useTokenBalance = () => {};\n\n// Higher-order components: with prefix\nconst withAuth = (Component) => {};\n\n// Controllers: PascalCase with Controller suffix\nclass TokensController extends BaseController {}\n```\n\n### Component Structure\n\n```\ncomponent-name/\n├── component-name.tsx          # Main component\n├── component-name.types.ts     # TypeScript types\n├── component-name.test.tsx     # Unit tests\n├── component-name.stories.tsx  # Storybook stories\n├── component-name.scss         # Styles\n├── __snapshots__/              # Jest snapshots\n├── README.md                   # Component documentation\n└── index.ts                    # Public exports\n```\n\n### React Best Practices\n\n```typescript\n// ✅ CORRECT: Functional component with destructured props and performance optimizations\ninterface TokenListProps {\n  tokens: Token[];\n  onSelect: (token: Token) => void;\n}\n\nexport const TokenList = ({ tokens, onSelect }: TokenListProps) => {\n  // Use hooks\n  const [selected, setSelected] = useState<Token | null>(null);\n\n  // Memoize expensive computations (sorting large arrays)\n  const sortedTokens = useMemo(() =>\n    [...tokens].sort((a, b) => a.symbol.localeCompare(b.symbol)),\n    [tokens]  // Only re-sort when tokens array changes\n  );\n\n  // Memoize callbacks passed to children to prevent unnecessary re-renders\n  const handleClick = useCallback((token: Token) => {\n    setSelected(token);\n    onSelect(token);\n  }, [onSelect]);\n\n  return (\n    <div>\n      {sortedTokens.map(token => (\n        <TokenItem\n          key={token.address}  // Use unique ID, not array index\n          token={token}\n          onClick={handleClick}  // Stable reference prevents child re-renders\n        />\n      ))}\n    </div>\n  );\n};\n```\n\n**Performance Anti-Patterns to Avoid:**\n\n```typescript\n// ❌ WRONG: Using index as key for dynamic lists\n{tokens.map((token, index) => (\n  <TokenItem\n    key={index}  // Don't use index as key for dynamic lists\n    token={token}\n  />\n))}\n\n// ❌ WRONG: No memoization for expensive operations\nconst sortedTokens = tokens.sort((a, b) => a.value - b.value);  // Runs on every render\n\n// ❌ WRONG: Using useEffect for derived state\nconst [displayName, setDisplayName] = useState('');\nuseEffect(() => {\n  setDisplayName(`${token.symbol} (${token.name})`);  // Should calculate during render\n}, [token]);\n```\n\n**Detailed Guidelines:**\n\n- General coding: `.cursor/rules/mms-coding-guidelines/RULE.md`\n- Performance optimization:\n  - `.cursor/rules/mms-perf-rendering/RULE.md` (rendering performance)\n  - `.cursor/rules/mms-perf-hooks-effects/RULE.md` (hooks & effects)\n  - `.cursor/rules/mms-perf-react-compiler/RULE.md` (React Compiler & anti-patterns)\n  - `.cursor/rules/mms-perf-state-management/RULE.md` (Redux & state management)\n\n---\n\n## React Performance Optimization\n\n### Critical Performance Rules\n\nWhen writing React components, follow these performance best practices:\n\n#### 1. Always Use Unique IDs as Keys\n\n```typescript\n// ❌ WRONG: Using index as key for dynamic list\n{tokens.map((token, index) => (\n  <TokenItem key={index} token={token} />  // BAD!\n))}\n\n// ✅ CORRECT: Use unique identifier\n{tokens.map((token) => (\n  <TokenItem key={token.address} token={token} />\n))}\n```\n\n#### 2. Memoize Expensive Calculations\n\n```typescript\n// ❌ WRONG: Sorts on every render\nconst TokenList = ({ tokens }) => {\n  const sortedTokens = tokens.sort((a, b) => b.balance - a.balance);  // BAD!\n  return <div>{sortedTokens.map(...)}</div>;\n};\n\n// ✅ CORRECT: Memoize with useMemo\nconst TokenList = ({ tokens }) => {\n  const sortedTokens = useMemo(() =>\n    [...tokens].sort((a, b) => b.balance - a.balance),\n    [tokens]\n  );\n  return <div>{sortedTokens.map(...)}</div>;\n};\n```\n\n#### 3. Don't Use useEffect for Derived State\n\n```typescript\n// ❌ WRONG: Using effect for derived state\nconst TokenDisplay = ({ token }) => {\n  const [displayName, setDisplayName] = useState('');\n\n  useEffect(() => {\n    setDisplayName(`${token.symbol} (${token.name})`);  // BAD!\n  }, [token]);\n\n  return <div>{displayName}</div>;\n};\n\n// ✅ CORRECT: Calculate during render\nconst TokenDisplay = ({ token }) => {\n  const displayName = `${token.symbol} (${token.name})`;\n  return <div>{displayName}</div>;\n};\n```\n\n### Performance Checklist for Components\n\nBefore marking a component complete:\n\n```\n✓ List keys use unique IDs (token.address, tx.hash), not array index\n✓ Expensive operations wrapped in useMemo (sorting, filtering)\n✓ Callbacks passed to children wrapped in useCallback\n✓ Static objects/styles defined as constants outside component\n✓ No useEffect where render-time calculation would work\n✓ Large lists (100+ items) consider virtualization (react-window)\n```\n\n### When to Optimize\n\n- **DO optimize:** Frequently rendered components (list items, modals)\n- **DO optimize:** Components with expensive calculations (sorting 100+ items)\n- **DO optimize:** Deep component trees that re-render often\n- **DON'T optimize:** Simple components that render quickly\n- **DON'T optimize:** Components that rarely re-render\n\n**Rule of thumb:** Profile first with React DevTools, then optimize what matters.\n\n**See:**\n\n- `.cursor/rules/mms-perf-rendering/RULE.md` - Rendering performance (keys, memoization, virtualization)\n- `.cursor/rules/mms-perf-hooks-effects/RULE.md` - Hooks & effects optimization\n- `.cursor/rules/mms-perf-react-compiler/RULE.md` - React Compiler considerations & anti-patterns\n- `.cursor/rules/mms-perf-state-management/RULE.md` - Redux & state management optimization\n\n---\n\n## Error Handling for Agents\n\n### When You Encounter a Build Error\n\n```\n1. Read the error message carefully\n2. Check if it's a known issue in tables below\n3. Apply the solution from the table\n4. If not in table, check if it's a:\n   - LavaMoat policy error → Run `yarn lavamoat:auto`\n   - TypeScript error → Run `yarn lint:tsc`\n   - Dependency error → Run `yarn install`\n5. If still failing, try nuclear option:\n   rm -rf node_modules/ dist/ build/\n   yarn install\n   yarn lavamoat:auto\n```\n\n### When Tests Fail\n\n```\n1. IF test was passing before your changes:\n   → Your changes broke something\n   → Revert changes and understand what the test expects\n   → Fix code to match expected behavior\n\n2. IF test expects old behavior but you're changing behavior:\n   → Update the test to match new expected behavior\n   → Document why behavior changed in test/PR description\n\n3. IF E2E test fails:\n   → Check if you built test build: `yarn build:test`\n   → Check if test build is stale: delete dist/ and rebuild\n   → Run with --debug flag for more info\n   → Run with --leave-running to inspect browser state\n\n4. IF snapshot test fails:\n   → Review the snapshot diff carefully\n   → IF change is intentional: `yarn test:unit -u`\n   → IF change is not intentional: fix your code\n```\n\n### When LavaMoat Policies Fail\n\n```\n1. ALWAYS run after dependency changes: `yarn lavamoat:auto`\n2. IF auto-generation fails:\n   → Try: rm -rf node_modules/ && yarn && yarn lavamoat:auto\n3. IF still fails:\n   → Check if on correct platform (macOS vs Linux)\n   → Platform-specific dependencies need regeneration on that platform\n4. IF blocked during development:\n   → Temporarily use: yarn start (LavaMoat off by default)\n   → MUST fix before merging\n```\n\n### When You Get Circular Dependency Errors\n\n```\n1. Run: yarn circular-deps:check\n2. Fix the circular dependency by:\n   → Moving shared code to a common location\n   → Using dependency injection\n   → Breaking circular imports\n3. After fixing: yarn circular-deps:update\n4. Commit the updated development/circular-deps.jsonc\n```\n\n---\n\n## Troubleshooting\n\n### Build Issues\n\n| Problem                      | Solution                                                     |\n| ---------------------------- | ------------------------------------------------------------ |\n| `Module not found` errors    | Run `yarn install` again                                     |\n| `Out of memory` during build | Increase Node heap: `NODE_OPTIONS=--max-old-space-size=4096` |\n| LavaMoat policy errors       | Run `yarn lavamoat:auto`                                     |\n| Webpack cache issues         | Run `yarn webpack:clearcache`                                |\n| Stale build artifacts        | Delete `dist/` and `build/` directories                      |\n\n### Test Issues\n\n| Problem                 | Solution                                          |\n| ----------------------- | ------------------------------------------------- |\n| E2E tests fail to start | Build test build first: `yarn build:test`         |\n| Tests hang indefinitely | Check if port 8545 (Anvil) is available           |\n| Snapshot tests fail     | Update snapshots: `yarn test:unit -u`             |\n| Browser not launching   | Check if browser is installed and in PATH         |\n| Random E2E failures     | Use `--retries` flag or check for race conditions |\n\n### Development Issues\n\n| Problem                | Solution                                               |\n| ---------------------- | ------------------------------------------------------ |\n| Extension won't load   | Check browser console for errors                       |\n| Hot reload not working | Restart `yarn start`                                   |\n| Changes not appearing  | Hard refresh extension (chrome://extensions)           |\n| State corrupted        | Clear extension data in browser                        |\n| Port already in use    | Kill process on port: `lsof -ti:PORT \\| xargs kill -9` |\n\n### Dependency Issues\n\n| Problem                  | Solution                                        |\n| ------------------------ | ----------------------------------------------- |\n| Yarn version mismatch    | Run `corepack enable`                           |\n| Package install fails    | Clear cache: `yarn cache clean && yarn install` |\n| Peer dependency warnings | Check if packages are compatible                |\n| Allow-scripts fails      | Run `yarn allow-scripts auto`                   |\n| Attributions check fails | Run `yarn attributions:generate`                |\n\n---\n\n## Agent Pre-Completion Checklist\n\nBefore completing your task, verify you've done ALL of the following:\n\n### Code Quality Checks\n\n```bash\n# 1. Run linter and auto-fix\nyarn lint:changed:fix\n\n# 2. Run TypeScript type checking\nyarn lint:tsc\n\n# 3. Check for circular dependencies\nyarn circular-deps:check\n\n# 4. Verify no console.log or debug code remains\n# grep -r \"console.log\" in modified files\n```\n\n### Testing Checks\n\n```bash\n# 1. Run unit tests for modified files\nyarn test:unit path/to/modified-file.test.ts\n\n# 2. If you modified a controller, run controller tests\nyarn test:unit app/scripts/controllers/\n\n# 3. If you modified UI components, run component tests\nyarn test:unit ui/components/\n\n# 4. If behavior changed, ensure tests are updated\n# Tests must reflect new expected behavior\n```\n\n### Build Checks\n\n```bash\n# 1. Verify dev build works\nyarn start\n# (Let it build, check for errors, then Ctrl+C)\n\n# 2. If E2E-related, verify test build works\nyarn build:test\n# (Check for build errors)\n```\n\n### Dependency Checks (ONLY if you modified dependencies)\n\n```bash\n# 1. Deduplicate lockfile\nyarn lint:lockfile:dedupe:fix\n\n# 2. Update allow-scripts\nyarn allow-scripts auto\n\n# 3. Update LavaMoat policies\nyarn lavamoat:auto\n\n# 4. Update attributions\nyarn attributions:generate\n\n# 5. Verify all policy files are included in changes:\n# - lavamoat/webpack/*/policy.json\n# - attribution.txt\n```\n\n### File Completeness Checks\n\n```typescript\n// For NEW TypeScript files, verify they have:\n// 1. Proper imports\n// 2. Type definitions\n// 3. JSDoc comments for public functions\n// 4. Colocated .test.ts file\n// 5. Exported from index.ts (if in component folder)\n\n// For MODIFIED files, verify:\n// 1. No commented-out code\n// 2. No unused imports\n// 3. Consistent formatting\n// 4. Updated tests if behavior changed\n```\n\n### Documentation Checks\n\n```\nIF you created a new component:\n  → Add/update component README.md\n  → Add/update Storybook story (.stories.tsx)\n\nIF you changed public API (controller methods, props, etc.):\n  → Update JSDoc comments\n  → Update TypeScript types\n\nIF you changed behavior significantly:\n  → Add comment explaining why\n  → Update relevant documentation files\n```\n\n### Final Verification\n\n```\n✓ All new code is TypeScript (not JavaScript)\n✓ All tests pass: yarn test:unit\n✓ All linting passes: yarn lint:changed\n✓ No console.log or debug code\n✓ Changes are colocated with tests\n✓ Used functional components (not class components)\n✓ Props are destructured\n✓ Controllers extend BaseController\n✓ Updated related files (see File Modification Patterns)\n✓ LavaMoat policies updated (if dependencies changed)\n✓ Circular dependencies checked\n✓ Build completes without errors\n\nPerformance Checks (React Components):\n✓ Unique IDs used as keys (not array index)\n✓ Expensive calculations wrapped in useMemo\n✓ Callbacks to children wrapped in useCallback\n✓ No useEffect for derived state (calculate during render)\n✓ Large lists (100+ items) use virtualization if applicable\n```\n\n---\n\n## Additional Resources\n\n### Documentation\n\n- **Main README:** [README.md](./README.md) - Setup, building, contributing\n- **Development Guide:** [development/README.md](./development/README.md) - Build system details\n- **Testing Guide:** [docs/testing.md](./docs/testing.md) - Testing infrastructure\n- **Architecture Docs:** [docs/](./docs/) - Architecture and design docs\n\n### Coding Guidelines\n\n- **Controller Patterns:** [.cursor/rules/mms-controller-guidelines/RULE.md](./.cursor/rules/mms-controller-guidelines/RULE.md)\n- **Unit Testing:** [.cursor/rules/mms-unit-testing/RULE.md](./.cursor/rules/mms-unit-testing/RULE.md)\n- **E2E Testing:** [./test/e2e/AGENTS.md](./test/e2e/AGENTS.md)\n- **E2E CI Decision Tree:** [.github/guidelines/E2E_DECISION_TREE.md](./.github/guidelines/E2E_DECISION_TREE.md)\n- **E2E Deprecated Patterns:** [./test/e2e/AGENTS.md](./test/e2e/AGENTS.md)\n- **CI Workflows:** [.github/AGENTS.md](./.github/AGENTS.md)\n- **Front-End Performance:**\n  - [Rendering Performance](.cursor/rules/mms-perf-rendering/RULE.md) - Start here (keys, memoization, virtualization)\n  - [Hooks & Effects](.cursor/rules/mms-perf-hooks-effects/RULE.md) - useEffect best practices\n  - [React Compiler & Anti-Patterns](.cursor/rules/mms-perf-react-compiler/RULE.md) - React Compiler considerations\n  - [State Management](.cursor/rules/mms-perf-state-management/RULE.md) - Redux optimization\n- **Pull Requests:** [.cursor/rules/mms-pr-guidelines/RULE.md](./.cursor/rules/mms-pr-guidelines/RULE.md)\n- **General Coding:** [.cursor/rules/mms-coding-guidelines/RULE.md](./.cursor/rules/mms-coding-guidelines/RULE.md)\n- **Official Guidelines:** [.github/guidelines/CODING_GUIDELINES.md](./.github/guidelines/CODING_GUIDELINES.md)\n\n### Non-EVM Swaps/Bridge Agent Entrypoints\n\n- **Non-EVM Swaps/Bridge Standard:** [`docs/add-non-evm-swaps-bridge-network.md`](./docs/add-non-evm-swaps-bridge-network.md) - Canonical implementation and review standard for adding non-EVM bridge or swaps support with code-gate and LaunchDarkly rollout requirements.\n- **OpenAI/Codex Skill:** [`.agents/skills/mms-add-non-evm-network/SKILL.md`](./.agents/skills/mms-add-non-evm-network/SKILL.md) - Multi-agent skill entrypoint for the shared standard.\n- **Cursor Rule:** [`.cursor/rules/mms-add-non-evm-network/RULE.md`](./.cursor/rules/mms-add-non-evm-network/RULE.md) - Cursor rule entrypoint for the shared standard.\n- **Claude Skill:** [`.claude/skills/mms-add-non-evm-network/SKILL.md`](./.claude/skills/mms-add-non-evm-network/SKILL.md) - Claude skill entrypoint for the shared standard.\n- **Cursor Command:** [`.cursor/commands/add-non-evm-swaps-bridge-network.md`](./.cursor/commands/add-non-evm-swaps-bridge-network.md) - Cursor command shim to the Claude command entrypoint.\n\n### EVM Swaps/Bridge Agent Entrypoints\n\n- **EVM Swaps/Bridge Standard:** [`docs/add-evm-swaps-bridge-network.md`](./docs/add-evm-swaps-bridge-network.md) - Canonical implementation and review standard for adding a new EVM network to the unified swaps/bridge flow (bridge allowlist, default token pair, stablecoin slippage, and `bridgeConfigV2` rollout). Follows the MegaETH/Robinhood pattern.\n- **OpenAI/Codex Skill:** [`.agents/skills/mms-add-evm-network/SKILL.md`](./.agents/skills/mms-add-evm-network/SKILL.md) - Multi-agent skill entrypoint for the shared standard.\n- **Cursor Rule:** [`.cursor/rules/mms-add-evm-network/RULE.md`](./.cursor/rules/mms-add-evm-network/RULE.md) - Cursor rule entrypoint for the shared standard.\n- **Claude Skill:** [`.claude/skills/mms-add-evm-network/SKILL.md`](./.claude/skills/mms-add-evm-network/SKILL.md) - Claude skill entrypoint for the shared standard.\n\n### External Resources\n\n- **MetaMask Contributor Docs:** https://github.com/MetaMask/contributor-docs\n- **MetaMask Developer Docs:** https://docs.metamask.io/\n- **Community Forum:** https://community.metamask.io/\n- **User Support:** https://support.metamask.io/\n\n---\n\n## Cursor Cloud specific instructions\n\nThis section captures non-obvious, durable caveats for running this repo inside Cursor Cloud VMs. Dependency installation is handled automatically by the startup update script (nvm install/use per `.nvmrc`, `corepack enable`, `yarn install`, and creating `.metamaskrc` from `.metamaskrc.dist` if missing). Standard commands live in the sections above and in `README.md`/`package.json` — reference those instead of duplicating.\n\n### Node version gotcha (important)\n\n- The repo requires Node `>=24.13` (`.nvmrc` → `v24.13`), but the base image ships a fixed `/exec-daemon/node` (v22) shim that sits early on `PATH` and otherwise wins over nvm. `~/.bashrc` runs `nvm use default` at the end so **interactive shells get Node 24 automatically**. If a command runs Node 22 (e.g. Yarn's engines check fails), run `nvm use` (from the repo root, which reads `.nvmrc`) or prefix `PATH=\"$HOME/.nvm/versions/node/v24.13.1/bin:$PATH\"` before the command. `corepack enable` must run under Node 24 so Yarn 4 (`packageManager` in `package.json`) is used, not the legacy Yarn 1.\n\n### Running / building the extension\n\n- It is a browser extension, so `yarn start` does not open a UI — it webpack-builds + watches into `dist/chrome` (MV3). Initial build takes ~45s and then prints `compiled successfully` / `Watching for changes…`. Load `dist/chrome` as an unpacked extension in a Chromium browser to use it. Use `yarn start:mv2` for Firefox (`dist/firefox`).\n- `.metamaskrc` uses a **placeholder `INFURA_PROJECT_ID` (`00000000000`)**, which is enough to build and to onboard/create a wallet locally, but **all live RPC fails** (you'll see \"Unable to connect to <network>\"). For any on-chain flow (balances, sending, swaps), provide a real `INFURA_PROJECT_ID`, or point networks at a local `yarn anvil` chain (`:8545`).\n- Build config precedence is **`process.env` > `.metamaskprodrc` > `.metamaskrc` > `builds.yml`** (`development/webpack/utils/config.ts`; env vars win). So the Cursor Cloud secret named `INFURA_PROJECT_ID` is picked up automatically by the build in any **new** VM session (it overrides the placeholder in `.metamaskrc` with no file edit needed). Note secrets are injected only into new VMs, not one already running when the secret is added.\n\n### Visual / interactive verification (`mm` CLI)\n\n- The `mm` CLI (`node_modules/.bin/mm`, from `@metamask/client-mcp-core`) drives the extension via Playwright and is the fastest way to click through onboarding/unlock/send flows. It requires **Playwright's Chromium**, which is not part of `yarn install`: run `yarn playwright install chromium` once (cached under `~/.cache/ms-playwright`) before `mm launch`. It also needs an X display — one is available at `DISPLAY=:1` (set `export DISPLAY=:1`).\n- Launch against the existing dev build with `mm launch --context prod --extension-path dist/chrome --state onboarding`, then use `mm describe-screen` / `mm click --testid <id>` / `mm type`. During create-wallet, the on-home **Terms of Use** dialog's Agree button stays disabled until you click `terms-of-use-scroll-button` (repeatedly) to scroll the terms to the bottom. Always finish with `mm cleanup`. See `test/e2e/playwright/llm-workflow/README.md`.\n\n### E2E tests\n\n- Selenium-based E2E (`yarn test:e2e:*`) require a **test build** first (`yarn build:test` or the faster `yarn start:test`) plus a browser + driver; unit tests (`yarn test:unit`) and lint do not.\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","title":"AGENTS.md","content":"# AGENTS.md\n\nInstructions for AI coding agents working on MetaMask Browser Extension.\n\n---\n\n## Agent Instructions Summary\n\n**Project Type:** Browser extension (Chrome/Firefox)\n**Languages:** TypeScript (required for new code), JavaScript (legacy)\n**UI Framework:** React with functional components + hooks\n**State Management:** Redux + BaseController architecture\n**Testing:** Jest (unit), Playwright (E2E)\n**Build System:** Webpack (with LavaMoat for production)\n**Security:** LavaMoat policies required for all dependency changes\n\n### Critical Rules for Agents\n\n1. **ALWAYS use TypeScript** for new files (never JavaScript)\n2. **ALWAYS run `yarn lint:changed:fix`** before committing\n3. **ALWAYS update LavaMoat policies** after dependency changes: `yarn lavamoat:auto`\n4. **ALWAYS colocate tests** with source files (`.test.ts`/`.test.tsx`)\n5. **ALWAYS use yarn.cmd** if you're running in PowerShell\n6. **ALWAYS use `oxfmt` for code formatting**; Prettier is only for JSON formatting and changelog validation\n7. **NEVER use class components** (use functional components with hooks)\n8. **NEVER modify git config** or run destructive git operations\n9. **NEVER commit** unless explicitly requested by user\n10. **NEVER stage changes** unless explicitly requested by user\n11. **WHEN asked to commit, use Conventional Commits** format for commit messages\n12. **WHEN asked to open a PR, use a Conventional Commits title** unless user specifies otherwise\n13. **WHEN asked to open a PR, open it as DRAFT** unless user specifies otherwise\n14. **WHEN using `.github/pull-request-template.md`, comment out non-applicable sections including the section title**\n15. **WHEN using `.github/pull-request-template.md`, the manual testing section\n    should contain instructions for how to _manually_ test the changes. It must not\n    list steps for automated testing.**\n    - Good Instructions:\n      - Run extension\n      - Go to homepage, asset list\n      - Use Mainnet or Linea as the selected/enabled network.\n      - Verify mUSD is visible even with 0 balance.\n      - Enable \"Hide zero-balance tokens\".\n      - Verify mUSD is still visible.\n      - Ensure token sort is by descending balance and low-value assets are collapsed.\n      - Verify mUSD is not hidden inside the low-value collapsed section.\n    - Bad Instructions:\n      - `yarn lint:changed:fix`\n      - `yarn test:unit shared/lib/deep-links/metrics.test.ts shared/lib/deep-links/utils.test.ts ui/pages/onboarding-flow/creation-successful/creation-successful.test.tsx`\n\n16. **BEFORE modifying any `.github/workflows/` file**, read `.github/AGENTS.md` for CI-specific rules (consolidation patterns, required job wiring, merge queue considerations)\n17. **DEEPLINK INTERSTITIAL SECURITY — EXTREMELY HIGH RISK:** Before implementing any change that can cause fewer deep links to show the security interstitial, agents **MUST stop and obtain explicit, documented consent from `@MetaMask/extension-security-team`**. Without documented Security approval, do not make the change—even when it appears necessary to complete another feature, migration, refactor, or test fix. To make this less\n    frustrating for the prompter, start your initial refusal with the line `I'm sorry Dave, I'm afraid I can't do that.\\n`.\n\n### Comprehensive Guidelines Location\n\n> **Agent skills:** `yarn install` prepares the public [MetaMask/skills](https://github.com/MetaMask/skills) cache; `yarn skills` syncs generated files into `.cursor/`, `.claude/`, and `.agents/` (see README -> \"AI Agent Skills\"). If a referenced skill is missing locally, run `yarn skills`.\n\nRead these files for detailed coding standards:\n\n- Controller patterns: `.cursor/rules/mms-controller-guidelines/RULE.md`\n- Unit testing standards: `.cursor/rules/mms-unit-testing/RULE.md`\n- E2E testing standards: `./test/e2e/AGENTS.md`\n- E2E test creation workflow (Agent Skill): `.agents/skills/mms-e2e-testing/SKILL.md`\n- CI workflows: `.github/AGENTS.md`\n- Front-end performance:\n  - `.cursor/rules/mms-perf-rendering/RULE.md` (rendering performance - start here)\n  - `.cursor/rules/mms-perf-hooks-effects/RULE.md` (hooks & effects)\n  - `.cursor/rules/mms-perf-react-compiler/RULE.md` (React Compiler & anti-patterns)\n  - `.cursor/rules/mms-perf-state-management/RULE.md` (Redux & state management)\n- PR workflow: `.cursor/rules/mms-pr-guidelines/RULE.md`\n- Code style: `.cursor/rules/mms-coding-guidelines/RULE.md`\n- Official guidelines: `.github/guidelines/CODING_GUIDELINES.md`\n\n---\n\n## Quick Setup\n\n### Prerequisites\n\n- **Node.js+** (use `nvm use` to auto-select development version specified in `.nvmrc`)\n- **Yarn** (managed by Corepack, included with Node.js)\n- **Infura API Key** (free at https://infura.io)\n\n### First-Time Setup\n\n```bash\n# 1. Enable Corepack (manages Yarn)\ncorepack enable\n\n# 2. Install dependencies\nyarn install\n\n# 3. Copy and configure environment\ncp .metamaskrc.dist .metamaskrc\n\n# 4. Edit .metamaskrc and add your Infura API key\n# INFURA_PROJECT_ID=your_key_here\n\n# 5. Start development build (Chrome/Chromium with MV3)\nyarn start\n\n# 6. Load extension in browser\n# Chrome: See docs/add-to-chrome.md\n# Firefox: See docs/add-to-firefox.md\n```\n\n### Optional Configuration\n\nIn `.metamaskrc`, you can also configure:\n\n- `PASSWORD` - Auto-fill development wallet password\n- `SEGMENT_WRITE_KEY` - For MetaMetrics debugging\n- `SENTRY_DSN` - For error tracking debugging\n\n### Common Setup Issues\n\n| Issue                            | Solution                                                                                                |\n| -------------------------------- | ------------------------------------------------------------------------------------------------------- |\n| `command not found: yarn`        | Run `corepack enable`                                                                                   |\n| Build fails with policy errors   | Run `yarn lavamoat:auto`                                                                                |\n| Invalid Infura key error         | Check `INFURA_PROJECT_ID` in `.metamaskrc`                                                              |\n| Anvil won't start                | Ensure port 8545 is available and `yarn foundryup` has installed the binary                             |\n| Git hooks not working in VS Code | Follow [Husky troubleshooting](https://typicode.github.io/husky/troubleshooting.html#command-not-found) |\n\n---\n\n## Common Commands\n\n### Building\n\n```bash\n# Development Builds (with file watching and hot reload)\nyarn start                  # Chrome MV3 (default)\nyarn start:mv2             # Firefox MV2\nyarn start:flask           # Flask build (beta features)\nyarn start:with-state      # Start with preloaded wallet state\n\n# Production Builds\nyarn dist                  # Chrome MV3\nyarn dist:mv2              # Firefox MV2\n\n# Test Builds (for E2E testing)\nyarn build:test            # Build with LavaMoat enabled\nyarn start:test            # Build with LavaMoat disabled (faster iteration)\nyarn build:test:flask      # Flask test build\nyarn build:test:mv2        # Firefox MV2 test build\n\n# Download pre-built test builds (fastest)\nyarn download-builds --build-type test\n```\n\n**Build System Notes:**\n\n- `yarn start` uses Webpack (faster, development)\n- `yarn dist` uses Webpack + LavaMoat (production)\n- `yarn start` skips LavaMoat by default for speed; use `yarn start:lavamoat` to enable it\n- Test builds are required for E2E tests (not dev builds)\n\n### Testing\n\n```bash\n# Unit Tests\nyarn test                  # Lint + unit tests\nyarn test:unit             # Unit tests only\nyarn test:unit:watch       # Watch mode\nyarn test:unit:coverage    # With coverage report\n\n# E2E Tests\nyarn test:e2e:chrome       # Run all E2E tests (Chrome)\nyarn test:e2e:firefox      # Run all E2E tests (Firefox)\n\n# Single E2E test with options\nyarn test:e2e:single test/e2e/tests/account-menu/account-details.spec.js \\\n  --browser=chrome \\\n  --leave-running \\\n  --debug\n\n# Integration Tests\nyarn test:integration\nyarn test:integration:coverage\n\n# Playwright Tests\nyarn test:e2e:benchmark    # Performance benchmarks\n```\n\n**Testing Notes:**\n\n- Unit tests should be colocated with source files (`.test.ts`/`.test.tsx`)\n- Always create a test build before running E2E tests\n- Use `--leave-running` to debug failed E2E tests\n- See `.cursor/rules/mms-unit-testing/RULE.md` for testing standards\n\n### Linting & Formatting\n\n```bash\n# Run all linters\nyarn lint                  # JSON formatting + oxfmt + ESLint + TypeScript + Styles + Images\n\n# Individual linters\nyarn lint:json             # Prettier JSON formatting check\nyarn lint:format           # oxfmt code formatting check\nyarn lint:eslint           # ESLint only\nyarn lint:tsc              # TypeScript type checking\nyarn lint:styles           # Stylelint for SCSS\n\n# Auto-fix\nyarn lint:fix              # Fix all auto-fixable issues\nyarn lint:json:fix         # Fix JSON formatting with Prettier\nyarn lint:format:fix       # Fix code formatting with oxfmt\nyarn lint:eslint:fix       # Fix ESLint issues\n\n# Lint only changed files (faster)\nyarn lint:changed\nyarn lint:changed:fix\n```\n\n**Formatter Notes:**\n\n- Use `yarn lint:changed:fix` for normal agent work; it applies the repo's formatter choices to changed files.\n- Use `yarn lint:format:fix` or `oxfmt -c oxfmt.config.mts` for JavaScript, TypeScript, JSX, TSX, and other code formatting.\n- Use `yarn lint:json:fix` for JSON files such as `package.json`; this is the main remaining Prettier formatting path.\n- Do not run Prettier directly on code files.\n\n### Development Tools\n\n```bash\n# Test Dapps\nyarn dapp                  # Start test dapp on :8080\nyarn dapp-multichain       # Multichain test dapp\nyarn dapp-solana           # Solana test dapp\nyarn dapp-chain            # Dapp with local Anvil\n\n# DevTools\nyarn devtools:react        # React DevTools\nyarn devtools:redux        # Redux DevTools\nyarn start:dev             # Start with both DevTools\n\n# Local Blockchain\nyarn anvil                 # Start Anvil (Foundry) on port 8545\n\n# Storybook\nyarn storybook             # Component documentation/development\nyarn storybook:build       # Build static storybook\n\n# Git Hooks\nyarn githooks:install      # Install pre-commit hooks\n```\n\n### Dependency Management\n\n```bash\n# When adding/updating/removing dependencies:\n\n# 1. Install/update package\nyarn add package-name\nyarn upgrade package-name\n\n# 2. Deduplicate lockfile\nyarn lint:lockfile:dedupe:fix\n\n# 3. Update allow-scripts (determines which install scripts can run)\nyarn allow-scripts auto\n\n# 4. Update LavaMoat policies\nyarn lavamoat:auto         # Regenerates the webpack LavaMoat policies\n\n# 5. Update attributions\nyarn attributions:generate\n\n# Or use MetaMask bot (for team members with repo branch):\n# Comment on PR: @metamaskbot update-policies\n# Comment on PR: @metamaskbot update-attributions\n```\n\n**Important:** Always update LavaMoat policies and attributions when dependencies change!\n\n---\n\n## Common Agent Workflows\n\n### Workflow: Adding a New Feature\n\n```bash\n# 1. Start development build\nyarn start\n\n# 2. Create new files (MUST be TypeScript)\n# - Component: ui/components/feature-name/feature-name.tsx\n# - Test: ui/components/feature-name/feature-name.test.tsx\n# - Types: ui/components/feature-name/feature-name.types.ts\n\n# 3. Make changes\n\n# 4. Run lint and tests on changed files\nyarn lint:changed:fix\nyarn test:unit path/to/feature-name.test.tsx\n\n# 5. If test needs E2E, build test build\nyarn build:test\nyarn test:e2e:single test/e2e/tests/new-test.spec.js --browser=chrome\n```\n\n### Workflow: Modifying Existing Code\n\n```bash\n# 1. Identify file type and read relevant guidelines\n# - Controller? Read .cursor/rules/mms-controller-guidelines/RULE.md\n# - React component? Read .cursor/rules/mms-coding-guidelines/RULE.md\n# - Test? Read .cursor/rules/mms-unit-testing/RULE.md\n\n# 2. Make changes following guidelines\n\n# 3. Run linter on changed files\nyarn lint:changed:fix\n\n# 4. Run existing tests\nyarn test:unit path/to/modified-file.test.ts\n\n# 5. Update tests if behavior changed\n\n# 6. Check for circular dependencies\nyarn circular-deps:check\n```\n\n### Workflow: Adding/Updating Dependencies\n\n```bash\n# 1. Add or update package\nyarn add package-name\n# OR\nyarn upgrade package-name\n\n# 2. REQUIRED: Deduplicate lockfile\nyarn lint:lockfile:dedupe:fix\n\n# 3. REQUIRED: Update allow-scripts\nyarn allow-scripts auto\n\n# 4. REQUIRED: Update LavaMoat policies (this may take several minutes)\nyarn lavamoat:auto\n\n# 5. REQUIRED: Update attributions\nyarn attributions:generate\n\n# 6. Test the build\nyarn build:test\n\n# 7. Commit all changes including:\n#    - package.json\n#    - yarn.lock\n#    - lavamoat/webpack/*/policy.json\n#    - attribution.txt\n```\n\n### Workflow: Fixing a Bug\n\n```bash\n# 1. Create a failing test that reproduces the bug\n# Add test to existing .test.ts file or create new one\n\n# 2. Run the test to confirm it fails\nyarn test:unit path/to/test-file.test.ts\n\n# 3. Fix the bug in source code\n\n# 4. Run test again to confirm fix\nyarn test:unit path/to/test-file.test.ts\n\n# 5. Run all related tests\nyarn test:unit\n\n# 6. Lint changes\nyarn lint:changed:fix\n\n# 7. If bug is in E2E scenario\nyarn build:test\nyarn test:e2e:single path/to/test.spec.js --browser=chrome\n```\n\n### Workflow: Creating a Controller\n\n```bash\n# 1. MUST read controller guidelines first\n# Read .cursor/rules/mms-controller-guidelines/RULE.md\n\n# 2. Create controller file (TypeScript only)\n# Location: app/scripts/controllers/your-controller/your-controller.ts\n\n# 3. Controller MUST:\n#    - Extend BaseController from @metamask/base-controller\n#    - Define state type\n#    - Define metadata for all state properties\n#    - Export getDefaultYourControllerState() function\n#    - Use messenger for inter-controller communication\n#    - Use selectors for derived state (not getter methods)\n\n# 4. Create test file\n# Location: app/scripts/controllers/your-controller/your-controller.test.ts\n\n# 5. Create types file\n# Location: app/scripts/controllers/your-controller/types.ts\n\n# 6. Run tests\nyarn test:unit app/scripts/controllers/your-controller/your-controller.test.ts\n\n# 7. Lint\nyarn lint:changed:fix\n```\n\n### Controller Development Patterns\n\nWhen creating a controller, follow these critical patterns from `.cursor/rules/mms-controller-guidelines/RULE.md`:\n\n#### State Metadata Requirements\n\n**Every state property MUST have metadata with these properties:**\n\n| Property                                | Type    | Purpose                   | Example Value              |\n| --------------------------------------- | ------- | ------------------------- | -------------------------- |\n| `anonymous` OR `includeInDebugSnapshot` | boolean | Safe for Sentry? (no PII) | `anonymous: true`          |\n| `includeInStateLogs`                    | boolean | Include in state logs?    | `false` for sensitive data |\n| `persist`                               | boolean | Save to storage?          | `true` for user data       |\n| `usedInUi`                              | boolean | Used by UI?               | `true` if rendered         |\n\n**Example:**\n\n```typescript\nconst tokensControllerMetadata = {\n  tokens: {\n    anonymous: true, // No PII, safe for Sentry\n    includeInStateLogs: true, // Safe to include in logs\n    persist: true, // Should be saved\n    usedInUi: true, // Rendered in UI\n  },\n  apiKey: {\n    anonymous: false, // Sensitive\n    includeInStateLogs: false, // Must exclude from logs\n    persist: true, // But should be saved\n    usedInUi: false, // Backend only\n  },\n};\n```\n\n#### Default State Function Pattern\n\n**ALWAYS export function, NEVER export object:**\n\n```typescript\n✅ CORRECT: Returns new object each time\nexport function getDefaultTokensControllerState(): TokensControllerState {\n  return {\n    tokens: [],\n    lastUpdated: 0,\n  };\n}\n\n❌ WRONG: Shared object reference (mutation risk)\nexport const defaultTokensControllerState = {\n  tokens: [],\n  lastUpdated: 0,\n};\n```\n\n#### Constructor Single Options Bag\n\n**ALWAYS use single options object, NO positional arguments:**\n\n```typescript\n✅ CORRECT:\nconstructor({\n  messenger,\n  state = {},\n  apiKey,        // All options in one bag\n  isEnabled,\n}: TokensControllerOptions) {\n  super({\n    name: 'TokensController',\n    metadata: tokensControllerMetadata,\n    messenger,\n    state: { ...getDefaultTokensControllerState(), ...state },\n  });\n}\n\n❌ WRONG:\nconstructor(\n  options: ControllerOptions,\n  apiKey: string,     // Separate positional arg - BAD\n  isEnabled: boolean, // Separate positional arg - BAD\n) { }\n```\n\n#### Action Methods (Not Setters)\n\n**Model high-level user actions, not property changes:**\n\n```typescript\n❌ WRONG: Generic setters\nsetTokenData(data: any) { }\nupdateField(field: string, value: any) { }\n\n✅ CORRECT: Action-based methods\naddToken(token: Token) {\n  if (!token.address) {\n    throw new Error('Token address required');\n  }\n\n  this.update((state) => {\n    state.tokens.push(token);\n    state.lastUpdated = Date.now();\n  });\n}\n\nremoveToken(address: string) {\n  this.update((state) => {\n    state.tokens = state.tokens.filter(t => t.address !== address);\n    state.lastUpdated = Date.now();\n  });\n}\n```\n\n#### Keep State Minimal - Use Selectors\n\n**NEVER store derived values in state:**\n\n```typescript\n❌ WRONG: Derived values in state\ntype State = {\n  tokens: Token[];\n  tokenCount: number;  // DON'T STORE - derive it!\n  hasTokens: boolean;  // DON'T STORE - derive it!\n};\n\n✅ CORRECT: Minimal state + selectors\ntype State = {\n  tokens: Token[];  // Only essential data\n};\n\n// Export selectors for derived values\nexport const tokensControllerSelectors = {\n  selectTokens: (state: State) => state.tokens,\n  selectTokenCount: (state: State) => state.tokens.length,\n  selectHasTokens: (state: State) => state.tokens.length > 0,\n};\n```\n\n#### Cleanup with destroy()\n\n**Implement if controller has background tasks:**\n\n```typescript\nclass TokensController extends BaseController</*...*/> {\n  #pollInterval: NodeJS.Timeout | null = null;\n\n  constructor(options: Options) {\n    super(/* ... */);\n    if (options.enablePolling) {\n      this.#startPolling();\n    }\n  }\n\n  destroy() {\n    // Clean up resources\n    if (this.#pollInterval) {\n      clearInterval(this.#pollInterval);\n      this.#pollInterval = null;\n    }\n\n    // Call super to clean up messenger\n    super.destroy();\n  }\n}\n```\n\n**See `.cursor/rules/mms-controller-guidelines/RULE.md` for complete patterns with detailed examples.**\n\n---\n\n### Decision: Which Test Build to Use?\n\n```\nIF you need to run E2E tests:\n  IF you're iterating/debugging:\n    → Use `yarn start:test` (faster, LavaMoat disabled)\n  IF you're doing final verification:\n    → Use `yarn build:test` (slower, LavaMoat enabled, matches production)\n\nIF you're developing with feature flags:\n  → Use `FEATURE_FLAG=1 yarn build:test`\n  → Then run E2E: `yarn test:e2e:single path/to/test.spec.js`\n\nIF you're working on Firefox compatibility:\n  → Use `yarn build:test:mv2`\n  → Then test: `yarn test:e2e:firefox`\n```\n\n### Decision: Where to Put New Code?\n\n```\nIF creating a controller:\n  → app/scripts/controllers/controller-name/\n\nIF creating a UI component:\n  → ui/components/component-name/ (for reusable components)\n  → ui/pages/page-name/ (for page-level components)\n\nIF creating a utility function:\n  → shared/lib/ (if used by both background and UI)\n  → app/scripts/lib/ (if only used by background)\n  → ui/helpers/ (if only used by UI)\n\nIF creating constants:\n  → shared/constants/\n\nIF creating TypeScript types:\n  → shared/types/ (for shared types)\n  → types/ (for project-wide types)\n  → [component-dir]/types.ts (for component-specific types)\n\nIF creating a state migration:\n  → Run: yarn generate:migration\n  → Edits: app/scripts/migrations/[number].ts\n```\n\n### Decision: Which Browser Target?\n\n```\nIF user specifies Chrome, Edge, or Brave:\n  → Use MV3 (Manifest V3)\n  → Commands: yarn start, yarn dist, yarn build:test\n\nIF user specifies Firefox:\n  → Use MV2 (Manifest V2)\n  → Commands: yarn start:mv2, yarn dist:mv2, yarn build:test:mv2\n  → Set ENABLE_MV3=false\n\nIF user doesn't specify:\n  → Default to Chrome MV3\n  → Use: yarn start\n```\n\n---\n\n## Project Structure\n\n### High-Level Directory Layout\n\n```\nmetamask-extension/\n├── app/\n│   ├── scripts/           # Background scripts & controllers (860 TS, 234 JS)\n│   │   ├── controllers/   # Business logic controllers\n│   │   ├── lib/           # Utility libraries\n│   │   └── migrations/    # State migration scripts\n│   ├── manifest/          # Browser extension manifests (MV2/MV3)\n│   ├── images/            # Icons and images\n│   └── *.html             # Extension HTML pages\n├── ui/                    # React UI code (1,412 TSX, 1,292 JS)\n│   ├── components/        # Reusable React components\n│   ├── pages/             # Page-level components\n│   ├── ducks/             # Redux slices (state management)\n│   ├── hooks/             # Custom React hooks\n│   ├── selectors/         # Redux selectors\n│   └── store/             # Redux store configuration\n├── shared/                # Code shared between background and UI\n│   ├── constants/         # Shared constants (47 TS files)\n│   ├── lib/               # Shared utilities (122 TS files)\n│   ├── modules/           # Shared modules (45 TS files)\n│   └── types/             # TypeScript type definitions\n├── test/                  # Test files (586 TS, 79 JS)\n│   ├── e2e/               # End-to-end tests\n│   ├── integration/       # Integration tests\n│   └── *.test.*           # Unit tests (colocated with source)\n├── development/           # Build system and dev tools\n│   ├── build/             # Build scripts\n│   └── webpack/           # Webpack configuration\n├── docs/                  # Documentation (54 files)\n└── .cursor/rules/         # AI agent coding guidelines\n```\n\n### Finding Specific Code\n\n| What You Need                | Where to Look                                   |\n| ---------------------------- | ----------------------------------------------- |\n| Controllers (business logic) | `app/scripts/controllers/`                      |\n| React Components             | `ui/components/` or `ui/pages/`                 |\n| Redux State Management       | `ui/ducks/` (slices) and `ui/selectors/`        |\n| Background Scripts           | `app/scripts/`                                  |\n| Constants                    | `shared/constants/`                             |\n| Utility Functions            | `shared/lib/` or `ui/helpers/`                  |\n| Type Definitions             | `shared/types/` or `types/`                     |\n| State Migrations             | `app/scripts/migrations/`                       |\n| Build Configuration          | `development/build/` and `development/webpack/` |\n| Extension Manifests          | `app/manifest/v2/` or `app/manifest/v3/`        |\n\n### Architecture Patterns\n\n**Controllers** (Background Scripts):\n\n- Inherit from `BaseController` (from `@metamask/base-controller`)\n- Manage wallet state and business logic\n- Communicate via Messenger pattern (pub/sub)\n- Use selectors for derived state (not getter methods)\n- See `.cursor/rules/mms-controller-guidelines/RULE.md` for detailed patterns\n\n**React Components** (UI):\n\n- Functional components with hooks (no class components)\n- Props destructured in function parameters\n- Redux for global state, local state for UI-only data\n- Performance optimizations: useMemo, useCallback, React.memo\n- Unique IDs as keys (not array index for dynamic lists)\n- Organized in component folders with tests, styles, and types\n- See `.cursor/rules/mms-coding-guidelines/RULE.md` and `.cursor/rules/mms-perf-rendering/RULE.md`\n\n**Testing**:\n\n- Unit tests colocated with source files (`.test.ts`)\n- Jest for unit tests, Playwright for E2E\n- Test files organized with `describe` blocks by method/function\n- See `.cursor/rules/mms-unit-testing/RULE.md` for testing patterns\n\n### File Modification Patterns\n\nWhen you modify certain files, you typically need to update related files:\n\n**When modifying a Controller:**\n\n```\napp/scripts/controllers/foo/foo-controller.ts → ALSO UPDATE:\n├── app/scripts/controllers/foo/foo-controller.test.ts (tests)\n├── app/scripts/controllers/foo/types.ts (if types changed)\n└── app/scripts/metamask-controller.ts (if adding/removing controller)\n```\n\n**When modifying a React Component:**\n\n```\nui/components/foo/foo.tsx → ALSO UPDATE:\n├── ui/components/foo/foo.test.tsx (tests)\n├── ui/components/foo/foo.types.ts (if props changed)\n├── ui/components/foo/foo.stories.tsx (if props changed)\n└── ui/components/foo/index.ts (if exports changed)\n```\n\n**When modifying Redux State (ducks):**\n\n```\nui/ducks/foo/foo.ts → ALSO UPDATE:\n├── ui/ducks/foo/foo.test.ts (tests)\n├── ui/selectors/foo.ts (selectors that depend on this state)\n└── ui/components/*/foo-component.tsx (components using this state)\n```\n\n**When adding/removing dependencies:**\n\n```\npackage.json → MUST UPDATE:\n├── yarn.lock (run yarn install)\n├── lavamoat/webpack/*/policy.json (run yarn lavamoat:auto)\n└── attribution.txt (run yarn attributions:generate)\n```\n\n**When modifying state shape:**\n\n```\napp/scripts/controllers/foo/foo-controller.ts → MUST CREATE:\n└── app/scripts/migrations/[next-number].ts (migration for state change)\n```\n\n---\n\n## Working with Feature Flags\n\n### What are Feature Flags?\n\nFeature flags allow you to enable/disable features during development. They're defined in `.metamaskrc` and control which features are built into the extension.\n\n### Available Feature Flags\n\nCheck `.metamaskrc.dist` for the current list of feature flags. Common ones:\n\n- `MULTICHAIN` - Multi-chain support\n- `BLOCKAID_PUBLIC_KEY` - Security features\n- Various experimental features\n\n### Using Feature Flags\n\n**Method 1: Configure in `.metamaskrc`**\n\n```bash\n# Edit .metamaskrc\nMULTICHAIN=1\nOTHER_FEATURE=1\n\n# Build with flags\nyarn build:test\n```\n\n**Method 2: Pass as environment variable**\n\n```bash\n# Enable for single build\nMULTICHAIN=1 yarn build:test\nMULTICHAIN=1 yarn start:test\n\n# Run E2E tests with feature enabled\nMULTICHAIN=1 yarn build:test\nyarn test:e2e:single test/e2e/tests/some-test.spec.js\n```\n\n### Remote Feature Flags\n\nOverride remote feature flags using `.manifest-overrides.json`:\n\n```json\n{\n  \"_flags\": {\n    \"remoteFeatureFlags\": {\n      \"testBooleanFlag\": false\n    }\n  }\n}\n```\n\nSet in `.metamaskrc`:\n\n```\nMANIFEST_OVERRIDES=.manifest-overrides.json\n```\n\n---\n\n## LavaMoat Security System\n\n### What is LavaMoat?\n\nLavaMoat is a supply chain security tool that restricts what dependencies can do (file access, network access, etc.). It's enabled in production builds to protect users.\n\n### When to Update LavaMoat Policies\n\nUpdate policies whenever you:\n\n- ✅ Add a new dependency\n- ✅ Update an existing dependency\n- ✅ Remove a dependency\n- ✅ Change how code accesses Node.js APIs\n- ✅ See \"LavaMoat policy violation\" errors\n\n### How to Update Policies\n\n**Automated (Recommended):**\n\n```bash\n# Regenerate the webpack LavaMoat policies\nyarn lavamoat:auto\n\n# Or use MetaMask bot (team members only):\n# Comment on PR: @metamaskbot update-policies\n```\n\n**Manual:**\n\n```bash\n# Compile the webpack build tooling\nyarn webpack:tsc\n\n# Regenerate the webpack build tooling policy\nyarn webpack:lavamoat:policy:build\n\n# Regenerate the Firefox MV2 application policies\nyarn webpack:lavamoat:policy:mv2\n\n# Regenerate the Chrome MV3 application policies\nyarn webpack:lavamoat:policy:mv3\n\n# If policies still fail after regeneration:\nrm -rf node_modules/ && yarn\n# Then compile the webpack build tooling and rerun the affected policy command above.\n```\n\n### Common Policy Issues\n\n- **Policy fails on macOS/Windows:** Platform-specific optional dependencies. Regenerate on the target platform.\n- **Dynamic imports fail:** LavaMoat's static analysis may miss dynamic code. May need manual policy updates.\n- **Can't build at all:** Use `yarn start` (LavaMoat off by default) for development, but fix before merging.\n\n### Development Without LavaMoat\n\nFor faster iteration during development (LavaMoat is off by default):\n\n```bash\nyarn start       # Development build\nyarn start:test  # Test build\n```\n\n**⚠️ Warning:** Always test with LavaMoat enabled before merging!\n\n---\n\n## Browser Compatibility\n\n### Manifest V2 vs Manifest V3\n\n| Feature           | MV2 (Firefox)         | MV3 (Chrome/Chromium) |\n| ----------------- | --------------------- | --------------------- |\n| **Build Flag**    | `ENABLE_MV3=false`    | Default               |\n| **Start Command** | `yarn start:mv2`      | `yarn start`          |\n| **Dist Command**  | `yarn dist:mv2`       | `yarn dist`           |\n| **Background**    | Background page       | Service worker        |\n| **Permissions**   | Broader access        | More restrictive      |\n| **APIs**          | `browser.*` namespace | `chrome.*` namespace  |\n\n### Building for Different Browsers\n\n```bash\n# Chrome / Edge / Brave (MV3)\nyarn start                    # Development\nyarn dist                     # Production\n\n# Firefox (MV2)\nyarn start:mv2                # Development\nyarn dist:mv2                 # Production\n\n# Test builds\nyarn build:test               # Chrome MV3\nyarn build:test:mv2           # Firefox MV2\n```\n\n### Browser-Specific Considerations\n\n**Firefox:**\n\n- Must use MV2 (Manifest V2)\n- Use `webextension-polyfill` for cross-browser compatibility\n- Test with `yarn test:e2e:firefox`\n\n**Chrome/Chromium:**\n\n- Uses MV3 (Manifest V3) by default\n- Service worker limitations (no DOM access in background)\n- Test with `yarn test:e2e:chrome`\n\n**Both:**\n\n- Code should use `browser.*` namespace (polyfilled for Chrome)\n- Conditional logic for browser differences in `app/scripts/lib/util.js`\n\n---\n\n## Testing Strategy\n\n### Unit Tests\n\n**Location:** Colocated with source files (`.test.ts` or `.test.tsx`)\n\n**Running:**\n\n```bash\nyarn test:unit              # All unit tests\nyarn test:unit:watch        # Watch mode\nyarn test:unit:coverage     # With coverage\n```\n\n**Key Principles:**\n\n- Use Jest (not Mocha or Tape)\n- Test through public interfaces (not private methods)\n- Keep critical test data inline\n- Use `describe` blocks to organize by method/function\n- Never use \"should\" in test names (use present tense)\n\n**Example:**\n\n```typescript\ndescribe('TokensController', () => {\n  describe('addToken', () => {\n    it('adds the token to state', () => {\n      // Arrange, Act, Assert\n    });\n\n    it('throws error when token address is missing', () => {\n      // Test error case\n    });\n  });\n});\n```\n\n**Detailed Guidelines:** See `.cursor/rules/mms-unit-testing/RULE.md`\n\n### E2E Tests\n\n**Location:** `test/e2e/tests/`\n\n**Running:**\n\n```bash\n# Must build test build first!\nyarn build:test              # or yarn start:test\n\n# Run E2E tests\nyarn test:e2e:chrome         # All Chrome tests\nyarn test:e2e:firefox        # All Firefox tests\n\n# Single test with debug\nyarn test:e2e:single test/e2e/tests/TEST_NAME.spec.js \\\n  --browser=chrome \\\n  --debug \\\n  --leave-running\n```\n\n**Options:**\n\n- `--browser` - chrome, firefox, or all\n- `--debug` - Verbose logging\n- `--leave-running` - Keep browser open on failure\n- `--retries` - Number of retries on failure\n- `--update-snapshot` - Update snapshots\n\n**E2E Best Practices:**\n\nFind them in [](./test/e2e/AGENTS.md)\n\n### Visual Verification (MetaMask CLI / Playwright)\n\nWhen the user explicitly asks for visual verification of UI behavior (e.g., \"verify this works\", \"confirm visually\", \"take screenshots\", \"click through onboarding/unlock/send flow\"), you **MUST** use the MetaMask visual testing skill and `mm` cli tools instead of only reasoning about code.\n\n**Load the skill:** `/metamask-visual-testing`\n\n**Workflow:**\n\n0. Build if needed (`yarn build:test`), then `mm launch` (this auto-starts the daemon)\n1. **Query prior knowledge:** Run `mm knowledge-search \"<flow>\"` and `mm knowledge-sessions` to reuse previously discovered flows and avoid wasting tokens rediscovering known sequences.\n2. Always call `mm describe-screen` before acting to discover targets\n3. Use `mm click`/`mm type`/`mm wait-for` to drive the flow\n4. Provide evidence via `mm screenshot` and/or final `mm describe-screen` output\n5. Always end with `mm cleanup` (even on failure)\n\n**If CLI is unavailable or denied:** Say so explicitly and explain what's missing. Do not claim you verified without actual tool output as evidence.\n\n**Skill location:** `.claude/skills/mms-visual-testing/SKILL.md`\n\n**MM CLI architecture docs:** `test/e2e/playwright/llm-workflow/README.md`\n\n### Integration Tests\n\n**Location:** `test/integration/`\n\n**Running:**\n\n```bash\nyarn test:integration\nyarn test:integration:coverage\n```\n\n**Coverage Goals:**\n\n- Unit tests: > 80% coverage\n- Critical paths: > 90% coverage\n- E2E tests: Cover main user workflows\n\n---\n\n## State Migrations\n\n### What are Migrations?\n\nWhen MetaMask updates, the stored state format might change. Migrations transform old state to new format automatically.\n\n### Creating a Migration\n\n```bash\n# Generate migration template\nyarn generate:migration\n\n# Creates: app/scripts/migrations/XXX.ts (next number)\n```\n\n### Migration Guidelines\n\n1. **Always create migrations for state changes**\n2. **Test migrations thoroughly** (old state → new state)\n3. **Handle missing data gracefully** (some users may have old/corrupted state)\n4. **Never mutate input state** (return new state object)\n5. **Include version number** in migration metadata\n\n**Example Migration:**\n\n```typescript\nimport { cloneDeep } from 'lodash';\n\nconst version = 123;\n\nexport default {\n  version,\n  async migrate(originalVersionedData: any) {\n    const versionedData = cloneDeep(originalVersionedData);\n    versionedData.meta.version = version;\n    transformData(versionedData.data);\n    return versionedData;\n  },\n};\n\nfunction transformData(state: any): void {\n  // Transform state.data\n  if (state.PreferencesController) {\n    state.PreferencesController.newProperty = 'defaultValue';\n  }\n}\n```\n\n---\n\n## Pull Request Workflow\n\n### Before Creating a PR\n\n- [ ] All tests pass: `yarn test`\n- [ ] Linting passes: `yarn lint`\n- [ ] No console.logs or debug code\n- [ ] Changes are covered by tests\n- [ ] LavaMoat policies updated (if dependencies changed)\n- [ ] Attributions updated (if dependencies changed)\n\n### Creating a PR\n\n**Reference:** Follow the [PR template](https://github.com/MetaMask/metamask-extension/blob/main/.github/pull-request-template.md) when creating pull requests.\n\n### Default Agent Commit/Push/PR Flow (When Requested)\n\nExecute only the steps that correspond to what the user explicitly requested. Do not perform additional steps (e.g., do not push or open a PR if the user only asked to commit).\n\n#### When asked to **commit**\n\n1. Run `yarn lint:changed:fix` before creating the commit.\n2. Stage only files relevant to the requested change.\n3. Create a commit using Conventional Commits format: `<type>(optional-scope): <summary>`.\n\n#### When asked to **push**\n\nComplete all steps for **commit** above, then:\n\n4. Push the current branch to `origin`.\n\n#### When asked to **open a PR**\n\nComplete all steps for **push** above, then:\n\n5. Open a **draft** PR with:\n   - A Conventional Commits PR title (normally matching the commit summary).\n   - A PR body based on `.github/pull-request-template.md`.\n   - Any non-applicable template section commented out as a full block, including the section heading, for example:\n\n```markdown\n<!--\n## **Screenshots/Recordings**\n### **Before**\n### **After**\n-->\n```\n\n6. Do not mark the PR as \"Ready for review\" unless explicitly requested.\n\n**PR Title Format:**\n\n- Clear and descriptive\n- Will be used in squash commit message\n- Example: \"feat(networks): add token validation for custom networks\"\n\n**Description Section:**\n\n- **Context:** What's the background?\n- **Problem:** What needs to be fixed/added?\n- **Solution:** How do your changes address it?\n- Answer: \"What is the reason for the change?\" and \"What is the improvement/solution?\"\n\n**Changelog Entry:**\n\n- If End-User-Facing: Write a short user-facing description in past tense\n  - Example: `CHANGELOG entry: Added a new tab for users to see their NFTs`\n  - Example: `CHANGELOG entry: Fixed a bug that was causing some NFTs to flicker`\n- If not End-User-Facing: Write `CHANGELOG entry: null` or label with `no-changelog`\n\n**Related Issues:**\n\n- List all related issues using `Fixes: #issue-number` format\n- Link to related PRs if applicable\n\n**Manual Testing Steps:**\n\n- Provide numbered steps to test the changes\n- Include specific pages/features to test\n- Example:\n  1. Go to this page...\n  2. Click this button...\n  3. Verify this behavior...\n\n**Screenshots/Recordings:**\n\n- **Before:** Screenshots/videos showing the previous state (for UI changes)\n- **After:** Screenshots/videos showing the new state (for UI changes)\n- Required for all UI changes\n\n**Pre-merge Author Checklist:**\n\n- [ ] Followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Extension Coding Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md)\n- [ ] Completed the PR template to the best of ability\n- [ ] Included tests if applicable\n- [ ] Documented code using [JSDoc](https://jsdoc.app/) format if applicable\n- [ ] Applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md))\n\n**Additional PR Comments:**\n\n- Call out non-obvious changes\n- Explain complex logic inline\n- Link to related issues/PRs\n\n### During Review\n\n- Respond to all feedback\n- Link to commits that address feedback (e.g., \"Fixed in abc1234\")\n- **Avoid rebasing after receiving comments** (makes review harder)\n- Push new commits instead of amending\n- If the Conventional Commit type in the PR's title is `chore`, please evaluate if `chore` is truly the best choice. We also have two custom types: `bump` (for package updates) and `release` (for tasks on a release branch and tasks that are all about getting a release ready).\n\n### Before Merging\n\n- [ ] All conversations resolved\n- [ ] Required approvals received\n- [ ] CI checks passing\n- [ ] Review the squash commit message (auto-generated from PR)\n- [ ] **Don't modify the commit title format** (must be: `Title (#number)`)\n\n**Detailed Guidelines:** See `.cursor/rules/mms-pr-guidelines/RULE.md`\n\n---\n\n## Code Style & Standards\n\n### General Principles\n\n1. **TypeScript for all new code** (no new JavaScript files)\n2. **Functional components with hooks** (no class components)\n3. **Destructure props** in function parameters\n4. **Small, focused functions** (single responsibility)\n5. **Early returns** to reduce nesting\n6. **DRY principle** (extract repeated code)\n\n### Naming Conventions\n\n```typescript\n// Components: PascalCase\nexport const TokenListItem = () => {};\n\n// Functions: camelCase\nconst handleInputChange = () => {};\n\n// Custom hooks: use prefix\nconst useTokenBalance = () => {};\n\n// Higher-order components: with prefix\nconst withAuth = (Component) => {};\n\n// Controllers: PascalCase with Controller suffix\nclass TokensController extends BaseController {}\n```\n\n### Component Structure\n\n```\ncomponent-name/\n├── component-name.tsx          # Main component\n├── component-name.types.ts     # TypeScript types\n├── component-name.test.tsx     # Unit tests\n├── component-name.stories.tsx  # Storybook stories\n├── component-name.scss         # Styles\n├── __snapshots__/              # Jest snapshots\n├── README.md                   # Component documentation\n└── index.ts                    # Public exports\n```\n\n### React Best Practices\n\n```typescript\n// ✅ CORRECT: Functional component with destructured props and performance optimizations\ninterface TokenListProps {\n  tokens: Token[];\n  onSelect: (token: Token) => void;\n}\n\nexport const TokenList = ({ tokens, onSelect }: TokenListProps) => {\n  // Use hooks\n  const [selected, setSelected] = useState<Token | null>(null);\n\n  // Memoize expensive computations (sorting large arrays)\n  const sortedTokens = useMemo(() =>\n    [...tokens].sort((a, b) => a.symbol.localeCompare(b.symbol)),\n    [tokens]  // Only re-sort when tokens array changes\n  );\n\n  // Memoize callbacks passed to children to prevent unnecessary re-renders\n  const handleClick = useCallback((token: Token) => {\n    setSelected(token);\n    onSelect(token);\n  }, [onSelect]);\n\n  return (\n    <div>\n      {sortedTokens.map(token => (\n        <TokenItem\n          key={token.address}  // Use unique ID, not array index\n          token={token}\n          onClick={handleClick}  // Stable reference prevents child re-renders\n        />\n      ))}\n    </div>\n  );\n};\n```\n\n**Performance Anti-Patterns to Avoid:**\n\n```typescript\n// ❌ WRONG: Using index as key for dynamic lists\n{tokens.map((token, index) => (\n  <TokenItem\n    key={index}  // Don't use index as key for dynamic lists\n    token={token}\n  />\n))}\n\n// ❌ WRONG: No memoization for expensive operations\nconst sortedTokens = tokens.sort((a, b) => a.value - b.value);  // Runs on every render\n\n// ❌ WRONG: Using useEffect for derived state\nconst [displayName, setDisplayName] = useState('');\nuseEffect(() => {\n  setDisplayName(`${token.symbol} (${token.name})`);  // Should calculate during render\n}, [token]);\n```\n\n**Detailed Guidelines:**\n\n- General coding: `.cursor/rules/mms-coding-guidelines/RULE.md`\n- Performance optimization:\n  - `.cursor/rules/mms-perf-rendering/RULE.md` (rendering performance)\n  - `.cursor/rules/mms-perf-hooks-effects/RULE.md` (hooks & effects)\n  - `.cursor/rules/mms-perf-react-compiler/RULE.md` (React Compiler & anti-patterns)\n  - `.cursor/rules/mms-perf-state-management/RULE.md` (Redux & state management)\n\n---\n\n## React Performance Optimization\n\n### Critical Performance Rules\n\nWhen writing React components, follow these performance best practices:\n\n#### 1. Always Use Unique IDs as Keys\n\n```typescript\n// ❌ WRONG: Using index as key for dynamic list\n{tokens.map((token, index) => (\n  <TokenItem key={index} token={token} />  // BAD!\n))}\n\n// ✅ CORRECT: Use unique identifier\n{tokens.map((token) => (\n  <TokenItem key={token.address} token={token} />\n))}\n```\n\n#### 2. Memoize Expensive Calculations\n\n```typescript\n// ❌ WRONG: Sorts on every render\nconst TokenList = ({ tokens }) => {\n  const sortedTokens = tokens.sort((a, b) => b.balance - a.balance);  // BAD!\n  return <div>{sortedTokens.map(...)}</div>;\n};\n\n// ✅ CORRECT: Memoize with useMemo\nconst TokenList = ({ tokens }) => {\n  const sortedTokens = useMemo(() =>\n    [...tokens].sort((a, b) => b.balance - a.balance),\n    [tokens]\n  );\n  return <div>{sortedTokens.map(...)}</div>;\n};\n```\n\n#### 3. Don't Use useEffect for Derived State\n\n```typescript\n// ❌ WRONG: Using effect for derived state\nconst TokenDisplay = ({ token }) => {\n  const [displayName, setDisplayName] = useState('');\n\n  useEffect(() => {\n    setDisplayName(`${token.symbol} (${token.name})`);  // BAD!\n  }, [token]);\n\n  return <div>{displayName}</div>;\n};\n\n// ✅ CORRECT: Calculate during render\nconst TokenDisplay = ({ token }) => {\n  const displayName = `${token.symbol} (${token.name})`;\n  return <div>{displayName}</div>;\n};\n```\n\n### Performance Checklist for Components\n\nBefore marking a component complete:\n\n```\n✓ List keys use unique IDs (token.address, tx.hash), not array index\n✓ Expensive operations wrapped in useMemo (sorting, filtering)\n✓ Callbacks passed to children wrapped in useCallback\n✓ Static objects/styles defined as constants outside component\n✓ No useEffect where render-time calculation would work\n✓ Large lists (100+ items) consider virtualization (react-window)\n```\n\n### When to Optimize\n\n- **DO optimize:** Frequently rendered components (list items, modals)\n- **DO optimize:** Components with expensive calculations (sorting 100+ items)\n- **DO optimize:** Deep component trees that re-render often\n- **DON'T optimize:** Simple components that render quickly\n- **DON'T optimize:** Components that rarely re-render\n\n**Rule of thumb:** Profile first with React DevTools, then optimize what matters.\n\n**See:**\n\n- `.cursor/rules/mms-perf-rendering/RULE.md` - Rendering performance (keys, memoization, virtualization)\n- `.cursor/rules/mms-perf-hooks-effects/RULE.md` - Hooks & effects optimization\n- `.cursor/rules/mms-perf-react-compiler/RULE.md` - React Compiler considerations & anti-patterns\n- `.cursor/rules/mms-perf-state-management/RULE.md` - Redux & state management optimization\n\n---\n\n## Error Handling for Agents\n\n### When You Encounter a Build Error\n\n```\n1. Read the error message carefully\n2. Check if it's a known issue in tables below\n3. Apply the solution from the table\n4. If not in table, check if it's a:\n   - LavaMoat policy error → Run `yarn lavamoat:auto`\n   - TypeScript error → Run `yarn lint:tsc`\n   - Dependency error → Run `yarn install`\n5. If still failing, try nuclear option:\n   rm -rf node_modules/ dist/ build/\n   yarn install\n   yarn lavamoat:auto\n```\n\n### When Tests Fail\n\n```\n1. IF test was passing before your changes:\n   → Your changes broke something\n   → Revert changes and understand what the test expects\n   → Fix code to match expected behavior\n\n2. IF test expects old behavior but you're changing behavior:\n   → Update the test to match new expected behavior\n   → Document why behavior changed in test/PR description\n\n3. IF E2E test fails:\n   → Check if you built test build: `yarn build:test`\n   → Check if test build is stale: delete dist/ and rebuild\n   → Run with --debug flag for more info\n   → Run with --leave-running to inspect browser state\n\n4. IF snapshot test fails:\n   → Review the snapshot diff carefully\n   → IF change is intentional: `yarn test:unit -u`\n   → IF change is not intentional: fix your code\n```\n\n### When LavaMoat Policies Fail\n\n```\n1. ALWAYS run after dependency changes: `yarn lavamoat:auto`\n2. IF auto-generation fails:\n   → Try: rm -rf node_modules/ && yarn && yarn lavamoat:auto\n3. IF still fails:\n   → Check if on correct platform (macOS vs Linux)\n   → Platform-specific dependencies need regeneration on that platform\n4. IF blocked during development:\n   → Temporarily use: yarn start (LavaMoat off by default)\n   → MUST fix before merging\n```\n\n### When You Get Circular Dependency Errors\n\n```\n1. Run: yarn circular-deps:check\n2. Fix the circular dependency by:\n   → Moving shared code to a common location\n   → Using dependency injection\n   → Breaking circular imports\n3. After fixing: yarn circular-deps:update\n4. Commit the updated development/circular-deps.jsonc\n```\n\n---\n\n## Troubleshooting\n\n### Build Issues\n\n| Problem                      | Solution                                                     |\n| ---------------------------- | ------------------------------------------------------------ |\n| `Module not found` errors    | Run `yarn install` again                                     |\n| `Out of memory` during build | Increase Node heap: `NODE_OPTIONS=--max-old-space-size=4096` |\n| LavaMoat policy errors       | Run `yarn lavamoat:auto`                                     |\n| Webpack cache issues         | Run `yarn webpack:clearcache`                                |\n| Stale build artifacts        | Delete `dist/` and `build/` directories                      |\n\n### Test Issues\n\n| Problem                 | Solution                                          |\n| ----------------------- | ------------------------------------------------- |\n| E2E tests fail to start | Build test build first: `yarn build:test`         |\n| Tests hang indefinitely | Check if port 8545 (Anvil) is available           |\n| Snapshot tests fail     | Update snapshots: `yarn test:unit -u`             |\n| Browser not launching   | Check if browser is installed and in PATH         |\n| Random E2E failures     | Use `--retries` flag or check for race conditions |\n\n### Development Issues\n\n| Problem                | Solution                                               |\n| ---------------------- | ------------------------------------------------------ |\n| Extension won't load   | Check browser console for errors                       |\n| Hot reload not working | Restart `yarn start`                                   |\n| Changes not appearing  | Hard refresh extension (chrome://extensions)           |\n| State corrupted        | Clear extension data in browser                        |\n| Port already in use    | Kill process on port: `lsof -ti:PORT \\| xargs kill -9` |\n\n### Dependency Issues\n\n| Problem                  | Solution                                        |\n| ------------------------ | ----------------------------------------------- |\n| Yarn version mismatch    | Run `corepack enable`                           |\n| Package install fails    | Clear cache: `yarn cache clean && yarn install` |\n| Peer dependency warnings | Check if packages are compatible                |\n| Allow-scripts fails      | Run `yarn allow-scripts auto`                   |\n| Attributions check fails | Run `yarn attributions:generate`                |\n\n---\n\n## Agent Pre-Completion Checklist\n\nBefore completing your task, verify you've done ALL of the following:\n\n### Code Quality Checks\n\n```bash\n# 1. Run linter and auto-fix\nyarn lint:changed:fix\n\n# 2. Run TypeScript type checking\nyarn lint:tsc\n\n# 3. Check for circular dependencies\nyarn circular-deps:check\n\n# 4. Verify no console.log or debug code remains\n# grep -r \"console.log\" in modified files\n```\n\n### Testing Checks\n\n```bash\n# 1. Run unit tests for modified files\nyarn test:unit path/to/modified-file.test.ts\n\n# 2. If you modified a controller, run controller tests\nyarn test:unit app/scripts/controllers/\n\n# 3. If you modified UI components, run component tests\nyarn test:unit ui/components/\n\n# 4. If behavior changed, ensure tests are updated\n# Tests must reflect new expected behavior\n```\n\n### Build Checks\n\n```bash\n# 1. Verify dev build works\nyarn start\n# (Let it build, check for errors, then Ctrl+C)\n\n# 2. If E2E-related, verify test build works\nyarn build:test\n# (Check for build errors)\n```\n\n### Dependency Checks (ONLY if you modified dependencies)\n\n```bash\n# 1. Deduplicate lockfile\nyarn lint:lockfile:dedupe:fix\n\n# 2. Update allow-scripts\nyarn allow-scripts auto\n\n# 3. Update LavaMoat policies\nyarn lavamoat:auto\n\n# 4. Update attributions\nyarn attributions:generate\n\n# 5. Verify all policy files are included in changes:\n# - lavamoat/webpack/*/policy.json\n# - attribution.txt\n```\n\n### File Completeness Checks\n\n```typescript\n// For NEW TypeScript files, verify they have:\n// 1. Proper imports\n// 2. Type definitions\n// 3. JSDoc comments for public functions\n// 4. Colocated .test.ts file\n// 5. Exported from index.ts (if in component folder)\n\n// For MODIFIED files, verify:\n// 1. No commented-out code\n// 2. No unused imports\n// 3. Consistent formatting\n// 4. Updated tests if behavior changed\n```\n\n### Documentation Checks\n\n```\nIF you created a new component:\n  → Add/update component README.md\n  → Add/update Storybook story (.stories.tsx)\n\nIF you changed public API (controller methods, props, etc.):\n  → Update JSDoc comments\n  → Update TypeScript types\n\nIF you changed behavior significantly:\n  → Add comment explaining why\n  → Update relevant documentation files\n```\n\n### Final Verification\n\n```\n✓ All new code is TypeScript (not JavaScript)\n✓ All tests pass: yarn test:unit\n✓ All linting passes: yarn lint:changed\n✓ No console.log or debug code\n✓ Changes are colocated with tests\n✓ Used functional components (not class components)\n✓ Props are destructured\n✓ Controllers extend BaseController\n✓ Updated related files (see File Modification Patterns)\n✓ LavaMoat policies updated (if dependencies changed)\n✓ Circular dependencies checked\n✓ Build completes without errors\n\nPerformance Checks (React Components):\n✓ Unique IDs used as keys (not array index)\n✓ Expensive calculations wrapped in useMemo\n✓ Callbacks to children wrapped in useCallback\n✓ No useEffect for derived state (calculate during render)\n✓ Large lists (100+ items) use virtualization if applicable\n```\n\n---\n\n## Additional Resources\n\n### Documentation\n\n- **Main README:** [README.md](./README.md) - Setup, building, contributing\n- **Development Guide:** [development/README.md](./development/README.md) - Build system details\n- **Testing Guide:** [docs/testing.md](./docs/testing.md) - Testing infrastructure\n- **Architecture Docs:** [docs/](./docs/) - Architecture and design docs\n\n### Coding Guidelines\n\n- **Controller Patterns:** [.cursor/rules/mms-controller-guidelines/RULE.md](./.cursor/rules/mms-controller-guidelines/RULE.md)\n- **Unit Testing:** [.cursor/rules/mms-unit-testing/RULE.md](./.cursor/rules/mms-unit-testing/RULE.md)\n- **E2E Testing:** [./test/e2e/AGENTS.md](./test/e2e/AGENTS.md)\n- **E2E CI Decision Tree:** [.github/guidelines/E2E_DECISION_TREE.md](./.github/guidelines/E2E_DECISION_TREE.md)\n- **E2E Deprecated Patterns:** [./test/e2e/AGENTS.md](./test/e2e/AGENTS.md)\n- **CI Workflows:** [.github/AGENTS.md](./.github/AGENTS.md)\n- **Front-End Performance:**\n  - [Rendering Performance](.cursor/rules/mms-perf-rendering/RULE.md) - Start here (keys, memoization, virtualization)\n  - [Hooks & Effects](.cursor/rules/mms-perf-hooks-effects/RULE.md) - useEffect best practices\n  - [React Compiler & Anti-Patterns](.cursor/rules/mms-perf-react-compiler/RULE.md) - React Compiler considerations\n  - [State Management](.cursor/rules/mms-perf-state-management/RULE.md) - Redux optimization\n- **Pull Requests:** [.cursor/rules/mms-pr-guidelines/RULE.md](./.cursor/rules/mms-pr-guidelines/RULE.md)\n- **General Coding:** [.cursor/rules/mms-coding-guidelines/RULE.md](./.cursor/rules/mms-coding-guidelines/RULE.md)\n- **Official Guidelines:** [.github/guidelines/CODING_GUIDELINES.md](./.github/guidelines/CODING_GUIDELINES.md)\n\n### Non-EVM Swaps/Bridge Agent Entrypoints\n\n- **Non-EVM Swaps/Bridge Standard:** [`docs/add-non-evm-swaps-bridge-network.md`](./docs/add-non-evm-swaps-bridge-network.md) - Canonical implementation and review standard for adding non-EVM bridge or swaps support with code-gate and LaunchDarkly rollout requirements.\n- **OpenAI/Codex Skill:** [`.agents/skills/mms-add-non-evm-network/SKILL.md`](./.agents/skills/mms-add-non-evm-network/SKILL.md) - Multi-agent skill entrypoint for the shared standard.\n- **Cursor Rule:** [`.cursor/rules/mms-add-non-evm-network/RULE.md`](./.cursor/rules/mms-add-non-evm-network/RULE.md) - Cursor rule entrypoint for the shared standard.\n- **Claude Skill:** [`.claude/skills/mms-add-non-evm-network/SKILL.md`](./.claude/skills/mms-add-non-evm-network/SKILL.md) - Claude skill entrypoint for the shared standard.\n- **Cursor Command:** [`.cursor/commands/add-non-evm-swaps-bridge-network.md`](./.cursor/commands/add-non-evm-swaps-bridge-network.md) - Cursor command shim to the Claude command entrypoint.\n\n### EVM Swaps/Bridge Agent Entrypoints\n\n- **EVM Swaps/Bridge Standard:** [`docs/add-evm-swaps-bridge-network.md`](./docs/add-evm-swaps-bridge-network.md) - Canonical implementation and review standard for adding a new EVM network to the unified swaps/bridge flow (bridge allowlist, default token pair, stablecoin slippage, and `bridgeConfigV2` rollout). Follows the MegaETH/Robinhood pattern.\n- **OpenAI/Codex Skill:** [`.agents/skills/mms-add-evm-network/SKILL.md`](./.agents/skills/mms-add-evm-network/SKILL.md) - Multi-agent skill entrypoint for the shared standard.\n- **Cursor Rule:** [`.cursor/rules/mms-add-evm-network/RULE.md`](./.cursor/rules/mms-add-evm-network/RULE.md) - Cursor rule entrypoint for the shared standard.\n- **Claude Skill:** [`.claude/skills/mms-add-evm-network/SKILL.md`](./.claude/skills/mms-add-evm-network/SKILL.md) - Claude skill entrypoint for the shared standard.\n\n### External Resources\n\n- **MetaMask Contributor Docs:** https://github.com/MetaMask/contributor-docs\n- **MetaMask Developer Docs:** https://docs.metamask.io/\n- **Community Forum:** https://community.metamask.io/\n- **User Support:** https://support.metamask.io/\n\n---\n\n## Cursor Cloud specific instructions\n\nThis section captures non-obvious, durable caveats for running this repo inside Cursor Cloud VMs. Dependency installation is handled automatically by the startup update script (nvm install/use per `.nvmrc`, `corepack enable`, `yarn install`, and creating `.metamaskrc` from `.metamaskrc.dist` if missing). Standard commands live in the sections above and in `README.md`/`package.json` — reference those instead of duplicating.\n\n### Node version gotcha (important)\n\n- The repo requires Node `>=24.13` (`.nvmrc` → `v24.13`), but the base image ships a fixed `/exec-daemon/node` (v22) shim that sits early on `PATH` and otherwise wins over nvm. `~/.bashrc` runs `nvm use default` at the end so **interactive shells get Node 24 automatically**. If a command runs Node 22 (e.g. Yarn's engines check fails), run `nvm use` (from the repo root, which reads `.nvmrc`) or prefix `PATH=\"$HOME/.nvm/versions/node/v24.13.1/bin:$PATH\"` before the command. `corepack enable` must run under Node 24 so Yarn 4 (`packageManager` in `package.json`) is used, not the legacy Yarn 1.\n\n### Running / building the extension\n\n- It is a browser extension, so `yarn start` does not open a UI — it webpack-builds + watches into `dist/chrome` (MV3). Initial build takes ~45s and then prints `compiled successfully` / `Watching for changes…`. Load `dist/chrome` as an unpacked extension in a Chromium browser to use it. Use `yarn start:mv2` for Firefox (`dist/firefox`).\n- `.metamaskrc` uses a **placeholder `INFURA_PROJECT_ID` (`00000000000`)**, which is enough to build and to onboard/create a wallet locally, but **all live RPC fails** (you'll see \"Unable to connect to <network>\"). For any on-chain flow (balances, sending, swaps), provide a real `INFURA_PROJECT_ID`, or point networks at a local `yarn anvil` chain (`:8545`).\n- Build config precedence is **`process.env` > `.metamaskprodrc` > `.metamaskrc` > `builds.yml`** (`development/webpack/utils/config.ts`; env vars win). So the Cursor Cloud secret named `INFURA_PROJECT_ID` is picked up automatically by the build in any **new** VM session (it overrides the placeholder in `.metamaskrc` with no file edit needed). Note secrets are injected only into new VMs, not one already running when the secret is added.\n\n### Visual / interactive verification (`mm` CLI)\n\n- The `mm` CLI (`node_modules/.bin/mm`, from `@metamask/client-mcp-core`) drives the extension via Playwright and is the fastest way to click through onboarding/unlock/send flows. It requires **Playwright's Chromium**, which is not part of `yarn install`: run `yarn playwright install chromium` once (cached under `~/.cache/ms-playwright`) before `mm launch`. It also needs an X display — one is available at `DISPLAY=:1` (set `export DISPLAY=:1`).\n- Launch against the existing dev build with `mm launch --context prod --extension-path dist/chrome --state onboarding`, then use `mm describe-screen` / `mm click --testid <id>` / `mm type`. During create-wallet, the on-home **Terms of Use** dialog's Agree button stays disabled until you click `terms-of-use-scroll-button` (repeatedly) to scroll the terms to the bottom. Always finish with `mm cleanup`. See `test/e2e/playwright/llm-workflow/README.md`.\n\n### E2E tests\n\n- Selenium-based E2E (`yarn test:e2e:*`) require a **test build** first (`yarn build:test` or the faster `yarn start:test`) plus a browser + driver; unit tests (`yarn test:unit`) and lint do not.\n","category":"root","tokens":14803}]}