Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .claude/skills/freighter-mobile-best-practices
178 changes: 178 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# Freighter Mobile

> Non-custodial Stellar wallet for iOS and Android. React Native app with
> Zustand state management (ducks pattern).

## Glossary

Domain terms you will encounter throughout this codebase:

| Term | Meaning |
| ----------------- | ---------------------------------------------------------------------------------------- |
| **Duck** | A Zustand store module in `src/ducks/` managing a single domain of state |
| **XDR** | Stellar binary serialization format used for transactions and ledger entries |
| **WalletConnect** | Web3 protocol (v2) for dApp-to-wallet connections via `@reown/walletkit` |
| **RPC method** | WalletConnect v2 handler (`stellar_signXDR`, `stellar_signAndSubmitXDR`, etc.) |
| **Metro** | React Native JavaScript bundler (replaces webpack); cache issues are common |
| **Bundle ID** | App identifier — `org.stellar.freighterdev` (dev) / `org.stellar.freighterwallet` (prod) |
| **Fastlane** | Ruby automation for iOS/Android builds and App Store/Play Store submissions |
| **Maestro** | YAML-based mobile e2e test runner; flows live in `e2e/flows/` |
| **NativeWind** | Tailwind CSS utility classes adapted for React Native (v4) |
| **rn-nodeify** | Polyfills Node.js APIs (crypto, stream, buffer) required by the Stellar SDK |
| **jail-monkey** | Jailbreak/root detection library — never bypass in production code |

## Documentation

- [Auth Flow Architecture](./docs/auth_flow_diagram.md)
- [WalletConnect RPC Methods](./docs/walletconnect-rpc-methods.md)
- [Release Process](./RELEASE.md)
- [E2E Testing Guide](./e2e/README.md)
- [E2E CI & Triggers](./e2e/docs/ci-and-triggers.md)
- [E2E Local Setup](./e2e/docs/local-setup-and-env.md)
- [E2E Running Tests](./e2e/docs/running-tests.md)
- [E2E Debugging](./e2e/docs/artifacts-and-debugging.md)
- [E2E Creating Tests](./e2e/docs/creating-tests.md)
- [WalletConnect E2E](./e2e/docs/walletconnect-e2e-testing.md)
- [Mock dApp for Testing](./mock-dapp/README.md)
- [Getting Started](./README.md)
- [Contributing](./CONTRIBUTING.md)

## Quick Reference

| Item | Value |
| ---------------- | -------------------------------------------------------- |
| Language | TypeScript, React Native 0.81 |
| Node | >= 20 (CI: Node 20; locally 22 also works) |
| Package Manager | Yarn 4.10.0 (Corepack) |
| State Management | Zustand 5 (ducks pattern) |
| Navigation | React Navigation 7 (nested stack/tab) |
| Styling | NativeWind 4 (Tailwind) + Styled Components 6 |
| Testing | Jest 30 (unit), Maestro (e2e) |
| Linting | ESLint Airbnb + TS strict + Prettier |
| iOS Build | Xcode + Fastlane + CocoaPods |
| Android Build | Gradle + Fastlane (SDK 36, min SDK 24, NDK 28.2, JDK 17) |
| Default Branch | `main` |

## Build & Test Commands

```bash
yarn install # Install deps (runs Husky, polyfills, pods)
bundle install # Ruby deps (Fastlane, CocoaPods)
yarn ios # Run on iOS simulator
yarn android # Run on Android emulator
yarn start # Metro bundler only
yarn start-c # Metro with cache reset
yarn test # Jest unit tests
yarn check # lint:ts + lint:check + format:check
yarn fix # Auto-fix lint + format
yarn lint:translations # Check for missing i18n keys
yarn test:e2e:ios <flow> # Maestro e2e (iOS)
yarn test:e2e:android <flow> # Maestro e2e (Android)
```

### Cleaning Builds (escalation order)

```bash
yarn start-c # Clear Metro cache (try first)
yarn pod-install # Reinstall CocoaPods
yarn node-c-install # Remove node_modules + reinstall
yarn c-install # Full clean (Gradle + node_modules + reinstall)
yarn r-install # Nuclear: reset env + rebuild everything
```

## Repository Structure

```
freighter-mobile/
├── src/
│ ├── components/ # RN components (screens, templates, primitives, SDS)
│ ├── ducks/ # Zustand stores (one per domain)
│ ├── hooks/ # Custom React hooks
│ ├── helpers/ # Utility functions
│ ├── services/ # Business logic (analytics, blockaid, storage, backend)
│ ├── navigators/ # React Navigation (Root, Auth, Tab, + 6 feature navigators)
│ ├── providers/ # Context providers (AuthCheck, Network, Toast, WalletKit)
│ ├── config/ # App config (envConfig, constants, theme, routes)
│ ├── i18n/ # Translations (i18next)
│ └── polyfills/ # Node.js API polyfills for Stellar SDK
├── __tests__/ # Jest unit tests (mirrors src/)
├── __mocks__/ # Jest mocks for native modules
├── e2e/flows/ # Maestro e2e test flows
├── mock-dapp/ # WalletConnect mock dApp for local testing
├── ios/ # Native iOS project
├── android/ # Native Android project
├── fastlane/ # Release automation
└── .github/workflows/ # CI: test, ios, android, ios-e2e, android-e2e, new-release
```

## Architecture

State lives in isolated Zustand ducks (`src/ducks/`). Key stores: `auth`,
`balances`, `transactionBuilder`, `swap`, `walletKit`, `preferences`,
`networkInfo`, `history`. Access via hooks (`useAuthStore`, etc.).

Navigation uses nested React Navigation 7 navigators. Entry point:
`RootNavigator` → `AuthNavigator` or `TabNavigator` → feature navigators. Deep
links: `freighterdev://` (dev) / `freighterwallet://` (prod).

dApp connectivity via WalletConnect v2 (`src/providers/WalletKitProvider.tsx`).
4 RPC methods: `stellar_signXDR`, `stellar_signAndSubmitXDR`,
`stellar_signMessage`, `stellar_signAuthEntry`.

## Security-Sensitive Areas

Do not modify these without fully understanding the security implications:

- `src/ducks/auth.ts` — authentication state, key management
- `src/services/storage/` — secure storage (iOS Keychain / Android Keystore)
- `src/helpers/` related to signing, encryption, or key derivation
- `src/navigators/` — deep link handling (injection vector)
- `src/providers/WalletKitProvider.tsx` — WalletConnect session management
- `jail-monkey` detection — never bypass in non-test code
- `SecureClipboardService` — use this for all sensitive clipboard operations

## Known Complexity / Gotchas

- **Auth flow** is a complex state machine (sign-up, sign-in, import, lock,
biometrics). Read `docs/auth_flow_diagram.md` before touching
`src/ducks/auth.ts`.
- **Dual bundle IDs** mean separate signing configs, push tokens, and deep link
schemes. Don't mix dev/prod identifiers.
- **Version bumps** require touching 5 files simultaneously. Use
`yarn set-app-version` — do not edit them manually.
- **Metro cache** is the first culprit for unexplained build failures; run
`yarn start-c` before investigating further.
- **Polyfills** (`rn-nodeify`) must stay in sync with the Stellar SDK version —
don't remove or update without testing cryptographic operations.
- **Blockaid** transaction scanning is integrated into sign flows. Don't bypass
or weaken — it's a security control.
- **Pre-commit hooks** run the full test suite + TypeScript check. This is
intentional and slow on large changesets.

## Pre-submission Checklist

```bash
yarn check # Lint + format must pass
yarn test # Unit tests must pass
# Then test manually on both iOS and Android simulators
```

## Best Practices Entry Points

Read the relevant file when working in that area:

| Concern | Entry Point | When to Read |
| -------------------- | -------------------------------------------------------------------------- | --------------------------------------------------- |
| Code Style | `docs/skills/freighter-mobile-best-practices/references/code-style.md` | Writing or reviewing any code |
| Architecture | `docs/skills/freighter-mobile-best-practices/references/architecture.md` | Adding features, understanding state/nav structure |
| Styling | `docs/skills/freighter-mobile-best-practices/references/styling.md` | Creating or modifying UI components |
| Security | `docs/skills/freighter-mobile-best-practices/references/security.md` | Touching keys, auth, storage, or dApp interactions |
| Testing | `docs/skills/freighter-mobile-best-practices/references/testing.md` | Writing or fixing tests |
| Performance | `docs/skills/freighter-mobile-best-practices/references/performance.md` | Optimizing renders, lists, images, or startup |
| Error Handling | `docs/skills/freighter-mobile-best-practices/references/error-handling.md` | Adding error states, retries, or user-facing errors |
| Internationalization | `docs/skills/freighter-mobile-best-practices/references/i18n.md` | Adding or modifying user-facing strings |
| WalletConnect | `docs/skills/freighter-mobile-best-practices/references/walletconnect.md` | Working with dApp connections or RPC methods |
| Navigation | `docs/skills/freighter-mobile-best-practices/references/navigation.md` | Adding screens, deep links, or navigation flows |
| Git & PR Workflow | `docs/skills/freighter-mobile-best-practices/references/git-workflow.md` | Branching, committing, opening PRs, CI, releases |
| Dependencies | `docs/skills/freighter-mobile-best-practices/references/dependencies.md` | Adding, updating, or auditing packages |
| Anti-Patterns | `docs/skills/freighter-mobile-best-practices/references/anti-patterns.md` | Code review, avoiding common mistakes |
37 changes: 37 additions & 0 deletions docs/skills/freighter-mobile-best-practices/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
name: freighter-mobile-best-practices
description:
Comprehensive best practices for the Freighter Mobile React Native app (iOS +
Android). Covers code style, architecture, security, testing, performance,
error handling, i18n, styling, WalletConnect, navigation, git workflow,
dependencies, and anti-patterns. Use when writing new screens, creating
Zustand stores, adding hooks, working with WalletConnect, handling secure key
storage, writing navigation flows, building transactions, styling components,
reviewing mobile PRs, or any development work in the freighter-mobile repo.
Triggers on any task touching the freighter-mobile codebase.
---

# Freighter Mobile Best Practices

Freighter Mobile is a non-custodial Stellar wallet built with React Native,
targeting both iOS and Android. The app uses Zustand for state management, React
Navigation for routing, NativeWind for styling, and Maestro for end-to-end
testing.

## Reference Index

| Concern | File | When to Read |
| -------------------- | ---------------------------- | --------------------------------------------------- |
| Code Style | references/code-style.md | Writing or reviewing any code |
| Architecture | references/architecture.md | Adding features, understanding the codebase |
Comment on lines +21 to +26
Copy link

Copilot AI Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The markdown table syntax here uses a leading || which GitHub renders as an extra empty column. Replace || ... | ... | rows with standard | ... | ... | table rows so the Reference Index renders correctly.

Copilot uses AI. Check for mistakes.
| Styling | references/styling.md | Creating or modifying UI components |
| Security | references/security.md | Touching keys, auth, storage, or dApp interactions |
| Testing | references/testing.md | Writing or fixing tests |
| Performance | references/performance.md | Optimizing renders, lists, images, or startup |
| Error Handling | references/error-handling.md | Adding error states, retries, or user-facing errors |
| Internationalization | references/i18n.md | Adding or modifying user-facing strings |
| WalletConnect | references/walletconnect.md | Working with dApp connections or RPC methods |
| Navigation | references/navigation.md | Adding screens, deep links, or navigation flows |
| Git & PR Workflow | references/git-workflow.md | Branching, committing, opening PRs, CI, releases |
| Dependencies | references/dependencies.md | Adding, updating, or auditing packages |
| Anti-Patterns | references/anti-patterns.md | Code review, avoiding common mistakes |
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Anti-Patterns

This document lists common mistakes to avoid in the Freighter Mobile codebase.

## Relative Imports

ESLint enforces absolute imports from the `src/` root. Relative imports will
fail lint.

```tsx
// Wrong - will fail lint
import { useAuth } from "../../hooks/useAuth";

// Correct
import { useAuth } from "hooks/useAuth";
```

## Function Declarations for Components

All components must use arrow function expressions. Function declarations will
fail lint (`react/function-component-definition`).

```tsx
// Wrong - will fail lint
export function MyComponent({ title }: Props) {
return <Text>{title}</Text>;
}

// Correct
export const MyComponent: React.FC<Props> = ({ title }) => {
return <Text>{title}</Text>;
};
```

## Class Components

Class components are not used anywhere in the codebase. Always use functional
components with hooks.

## Magic Strings in Navigation

Never use raw strings for route names. Always use route enum constants.

```tsx
// Wrong
navigation.navigate("EnterAmount");

// Correct
navigation.navigate(SEND_PAYMENT_ROUTES.ENTER_AMOUNT);
```

## AsyncStorage for Sensitive Data

Never store keys, seeds, or passwords in AsyncStorage. Use the appropriate tier
from `storageFactory`:

- `secureDataStorage` for keys and seeds
- `biometricDataStorage` for biometric-gated passwords

## Untyped navigation.navigate()

Always type navigation params via the param list types. Untyped navigate calls
bypass TypeScript safety and can cause runtime crashes.

## Missing JSDoc

The PR template requires JSDoc on new functions and updated JSDoc on modified
functions. While the codebase currently has few JSDoc comments, all new code
should include them. Help improve this incrementally.

## Floating Promises

Even though `@typescript-eslint/no-floating-promises` is not currently enforced
in ESLint, avoid floating promises as a best practice. Every promise should be
`await`ed or have a `.catch()` handler.

```tsx
// Wrong - unhandled promise
fetchBalances(publicKey);

// Correct
await fetchBalances(publicKey);

// Also correct
fetchBalances(publicKey).catch((error) => {
Sentry.captureException(normalizeError(error));
});
```

## Trusting dApp Metadata

WalletConnect session info (app name, icon, URL) comes from the dApp itself and
can be spoofed. Never use this metadata for security decisions. Always validate
the transaction content independently.

## Logging Key Material

Never `console.log` keys, seeds, or passwords, even in `__DEV__` mode. Logs can
be captured by crash reporting tools or device log viewers.

## Skipping Network Validation

Always verify the WalletConnect request chain matches the active Stellar
network. A dApp could request signing on `stellar:pubnet` while the user expects
`stellar:testnet`.

## Direct Store Mutations

Zustand's `set()` creates new state objects. Never mutate existing state
directly.

```tsx
// Wrong - mutating existing state
get().balances.push(newBalance);

// Correct - creating new state
set({ balances: [...get().balances, newBalance] });
```

## Cross-Store Action Chains

Do not have store A's action call store B's action which calls store C's action.
This creates hard-to-debug cascading updates. Keep actions independent and
compose at the hook or component level instead.

## Empty Catch Blocks

Always handle or log errors. At minimum, use `normalizeError()` + Sentry.

```tsx
// Wrong
try {
await riskyOp();
} catch {}

// Correct
try {
await riskyOp();
} catch (error) {
Sentry.captureException(normalizeError(error));
}
```

## Hardcoding Test Data

Use environment variables for test secrets. Never hardcode recovery phrases,
private keys, or test passwords in source code.

```tsx
// Wrong
const testPhrase = "abandon abandon abandon ...";

// Correct
const testPhrase = process.env.E2E_TEST_RECOVERY_PHRASE;
```
Loading
Loading