diff --git a/README.md b/README.md index 27a8a6c..495f7b3 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ React Native Expo wallet for Stellar Testnet. The app aims to feel polished and ## Documentation +- [Navigation and Feature Boundary Map](./docs/navigation-map.md) - Canonical route tree, screen ownership, shared state, cross-feature dependencies, and high-risk contributor boundaries + - [Architecture Readiness Review](./docs/architecture-readiness-review.md) - Feature boundaries, duplicated state, SDK integration blockers, security-sensitive areas, and test gaps - [Evaluation Readiness Checklist](./docs/evaluation-readiness-checklist.md) - GrantFox contributor checklist for mobile issues, including tests, CI, screenshots, acceptance criteria, and the reminder that merge does not guarantee payment approval - [Storage Guide](./docs/storage.md) - SecureStore vs AsyncStorage diff --git a/docs/navigation-map.md b/docs/navigation-map.md index e33a4a2..5a9baaa 100644 --- a/docs/navigation-map.md +++ b/docs/navigation-map.md @@ -1,274 +1,400 @@ -# PocketPay Mobile — Navigation Map +# PocketPay Mobile Navigation and Feature Boundary Map -This document answers the onboarding questions: +This is the contributor-facing map for PocketPay Mobile. Use it to determine: -- *"Which screen file do I open to edit the Home balance view?"* -- *"Which store slice owns wallet create/import vs. vault time-locks?"* -- *"What happens if I add a new route group inside `app/` — do I need to update the auth gate?"* -- *"Which screen do users land on after a successful payment?"* +1. which route and screen own a user flow; +2. which store, hook, or service owns its state; +3. which shared modules it depends on; +4. which navigation, persistence, and security boundaries require extra care. -All file paths are relative to repository root. All routes use **Expo Router v6 file-based routing**. For routing internals see the [Expo Router documentation](https://docs.expo.dev/router/introduction/). +The map describes the repository as it exists today. Where ownership overlaps or a boundary is incomplete, that condition is documented rather than presented as an architectural rule. ---- +Related references: -## 1. Route Inventory (20 Routes Across 3 Groups) +- [Screen inventory](./screen-inventory.md) +- [Main wallet user flows](./user-flows.md) +- [Architecture readiness review](./architecture-readiness-review.md) +- [Storage guide](./storage.md) +- [Mobile wallet security FAQ](./WALLET_SECURITY_FAQ.md) +- [Vault integration assumptions](./vault-integration-assumptions.md) +- [Release testing checklist](./release-testing-checklist.md) -Expo Router converts files in `app/` directly to routes. Parentheses in directory names (e.g. `(auth)`) are **route groups** — they organize layout nesting but do NOT appear in the URL / deep link path. +## 1. Navigation architecture -### 1.1 Unauthenticated Route Group: `(auth)/` +PocketPay uses Expo Router v6 file-based routing. `package.json` points to `expo-router/entry`. -Shown when `walletStore.walletChecked === true` AND `publicKey === null` (no wallet on device). Stack navigator inside [app/(auth)/_layout.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/(auth)/_layout.tsx). +| Tier | Layout | Navigator | Responsibility | +| --- | --- | --- | --- | +| Root shell | [`app/_layout.tsx`](../app/_layout.tsx) | `` | Startup, wallet restoration, authentication redirects, global errors, app lock, and selected-route rendering | +| Authentication | [`app/(auth)/_layout.tsx`](../app/(auth)/_layout.tsx) | `` | Wallet creation and import | +| Authenticated app | [`app/(tabs)/_layout.tsx`](../app/(tabs)/_layout.tsx) | `` | Home, Activity, Vault, Settings, and the shared network banner | -| Route Path | File on Disk | Purpose | Primary Entry: Navigate Here From… | -| ----------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -------------------------------------------- | -| `/` (in auth) | [app/(auth)/index.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/(auth)/index.tsx) | Landing tile: **Create New Wallet** vs **Import Existing** | RootLayout auth gate / app cold start | -| `/create` | [app/(auth)/create.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/(auth)/create.tsx) | 3-step onboarding: Generate → Reveal keys → Confirm backup | `(auth)/index` [Create New Wallet] | -| `/import` | [app/(auth)/import.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/(auth)/import.tsx) | Paste secret → StrKey validation → activate wallet | `(auth)/index` [Import Existing] | +### Root-layout constraint -### 1.2 Authenticated Tab Group: `(tabs)/` +The root layout uses ``; it does **not** declare a root ``. Root-level routes therefore manage their own header and back behavior unless a nested navigator provides it. -Shown when `walletStore.publicKey != null` AND `walletStore.walletChecked === true`. Bottom-tab navigator inside [app/(tabs)/_layout.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/(tabs)/_layout.tsx). Adds an `OfflineBanner` at the top on all tabs. +Do not assume that `` options inside a root-level screen configure navigation. There is no parent root stack to consume those options. -| Route Path | File on Disk | Tab Label | Purpose | -| ---------------- | ------------------------------------------------------------ | --------- | ------------------------------------------------------------ | -| `/` (tabs home) | [app/(tabs)/index.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/(tabs)/index.tsx) | Home | XLM Balance, Public Key, **Fund my Wallet** button, Backup reminder modal, 5 most-recent transactions | -| `/history` | [app/(tabs)/history.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/(tabs)/history.tsx) | Activity | **SectionList grouped by date**, filter chips (All/Sent/Received), cursor-based pagination, pull-to-refresh, empty CTA → Send | -| `/vault` | [app/(tabs)/vault.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/(tabs)/vault.tsx) | Vault | Soroban Savings Vault: balance card, Deposit / Withdraw CTAs, Pending/Matured lock lists, intro/education modals | -| `/settings` | [app/(tabs)/settings.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/(tabs)/settings.tsx) | Settings | 4 sections — Preferences (theme, app lock), Wallet (export, sign out), Network & Environment (new), About (version, diagnostics) | +## 2. Startup, authentication, and global gates -### 1.3 Stack / Modal Routes (Outside Groups) +[`app/_layout.tsx`](../app/_layout.tsx) performs this sequence: -Presented modally, or pushed onto the stack above the tabs. +```text +expo-router/entry + -> import shim first + -> install global exception and rejection handlers + -> initialize app preferences + -> restore the wallet + -> wait for appStore.isInitialized and walletStore.walletChecked + -> show secure-storage recovery UI when restoration fails + -> redirect between (auth) and (tabs) + -> wrap the selected route with ErrorBoundary and LockScreen + -> render through +``` + +The app must wait for both preference initialization and wallet restoration. Redirecting before `walletChecked` resolves can flash the onboarding flow for a returning user. + +### Current authentication behavior + +The root gate uses `router.replace`: + +- wallet holders inside `(auth)` are sent to `/(tabs)`; +- users without a wallet are generally sent to `/(auth)`; +- `send`, `receive`, and `review-transaction` are hard-coded exceptions; +- a blocked deep link may be stored and replayed after authentication. + +Route names are therefore coupled to authentication behavior. Adding, renaming, or moving a route may require changing the root gate. + +## 3. Route inventory + +The grouped `(auth)/index.tsx` and `(tabs)/index.tsx` both resolve to `/`. The root gate decides which is visible. + +### 3.1 Authentication routes + +| URL | Screen | Purpose | Main dependencies | +| --- | --- | --- | --- | +| `/` without a wallet | [`app/(auth)/index.tsx`](../app/(auth)/index.tsx) | Choose Create or Import | Router, theme, shared controls | +| `/create` | [`app/(auth)/create.tsx`](../app/(auth)/create.tsx) | Generate a Testnet keypair, reveal the secret, persist it, and require backup acknowledgement | `walletStore`, Stellar service, secure-storage error handling | +| `/import` | [`app/(auth)/import.tsx`](../app/(auth)/import.tsx) | Validate and import a Stellar secret seed | `walletStore`, Stellar `StrKey`, `pocketpay-sdk` | +| `/wallet-creation-success` | [`app/(auth)/wallet-creation-success.tsx`](../app/(auth)/wallet-creation-success.tsx) | Confirm creation and replace navigation with the tabs | Router and shared action button | + +New pre-wallet routes belong under `app/(auth)/` and must also be declared in [`app/(auth)/_layout.tsx`](../app/(auth)/_layout.tsx). + +### 3.2 Authenticated tabs + +| URL | Screen | Tab | Purpose | Primary state owner | +| --- | --- | --- | --- | --- | +| `/` with a wallet | [`app/(tabs)/index.tsx`](../app/(tabs)/index.tsx) | Home | Balance, funding, backup reminder, recent activity | `walletStore` | +| `/history` | [`app/(tabs)/history.tsx`](../app/(tabs)/history.tsx) | Activity | Filtered and paginated transactions | `walletStore` | +| `/vault` | [`app/(tabs)/vault.tsx`](../app/(tabs)/vault.tsx) | Vault | Balance, deposits, withdrawals, and time locks | `vaultStore` and vault feature hooks | +| `/settings` | [`app/(tabs)/settings.tsx`](../app/(tabs)/settings.tsx) | Settings | Theme, app lock, wallet actions, contacts, network details, diagnostics | Multiple owning stores | + +The tab layout adds `NetworkStateBanner`. Root-level routes do not inherit it automatically. -| Route Path | File on Disk | Presentation | Purpose | Primary Entry: Navigate Here From… | -| --------------------- | ------------------------------------------------------------ | ------------ | ------------------------------------------------------------ | -------------------------------------------- | -| `/send` | [app/send.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/send.tsx) | Stack push | Payment form: destination, amount, memo. Validates before navigation to sign confirmation. | Home [Send XLM], Activity [New Payment] CTA, contact row tap, scan success | -| `/sign-confirmation` | [app/sign-confirmation.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/sign-confirmation.tsx) | Stack push | Pre-signing confirmation: shows transaction summary, explains signing implications, provides clear cancel option. Separates approval intent from execution. | Send form [Send Payment] button | -| `/receive` | [app/receive.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/receive.tsx) | Stack push | Public-key QR code, address copy + share sheet | Home [Receive] icon / button | -| `/scan` | [app/scan.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/scan.tsx) | Stack push | `expo-camera` QR scanner. On valid decode → navigates back to `/send` with `?destination=` prefilled. | Home [Scan] icon, Send form [Scan] button | -| `/review-transaction` | [app/review-transaction.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/review-transaction.tsx) | Modal-like push | 8-phase Sign & Send flow: review summary → handoff → signing → submitting → completed / failed → success screen | Sign confirmation [Sign Transaction] | -| `/payment-success` | [app/payment-success.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/payment-success.tsx) | `replace` (no back history) | Post-send confirmation: big success checkmark, tx hash copy, explorer link, Back to Home | Review transaction [phase → completed] | -| `/contacts` | [app/contacts.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/contacts.tsx) | Stack push | Address book: list, search, add/edit/delete, duplicate guards. Tapping a contact pushes to `/send` with destination prefilled. | Settings [Address Book], Send form [Pick contact] | -| `/diagnostics` | [app/diagnostics.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/diagnostics.tsx) | Stack push | Read-only dev/debug panel: redacted `getDiagnostics()` JSON, environment summary, synthetic-error trigger if enabled | Settings → About → [Open Diagnostics] | -| `/transaction/:id` | [app/transaction/[id].tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/transaction/%5Bid%5D.tsx) | Stack push | **Dynamic route** (segment `:id = operation id`): single-transaction detail view, memo, fee, source/dest, explorer link | Activity row tap, Home recent-list tap | -| `/vault/:id` | [app/vault/[id].tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/vault/%5Bid%5D.tsx) | Stack push | Dynamic: deep-linkable vault overview / account view (placeholder/detail) | Vault header card, from external deep link | -| `/vault-lock/:id` | [app/vault-lock/[id].tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/vault-lock/%5Bid%5D.tsx) | Stack push | Dynamic: single time-lock detail — countdown, status, mature-withdraw CTA | Vault list row tap, matured notification | +### 3.3 Root-level routes ---- +These routes are rendered through the root ``. -## 2. Navigation Tree — ASCII Diagram +| URL | Screen | Purpose | Navigation | +| --- | --- | --- | --- | +| `/send` | [`app/send.tsx`](../app/send.tsx) | Validate payment details, choose/create a contact, or scan a recipient | Pushes `/sign-confirmation` | +| `/sign-confirmation` | [`app/sign-confirmation.tsx`](../app/sign-confirmation.tsx) | Final warning and transaction summary before execution | Pushes `/review-transaction`; cancellation replaces with tabs | +| `/review-transaction` | [`app/review-transaction.tsx`](../app/review-transaction.tsx) | Sign, submit, track phases, retry, and report completion | Replaces with `/payment-success` | +| `/payment-success` | [`app/payment-success.tsx`](../app/payment-success.tsx) | Show the result and explorer information | Replaces with `/(tabs)` | +| `/receive` | [`app/receive.tsx`](../app/receive.tsx) | Display and share the receive address and QR payload | Entered from Home | +| `/scan` | [`app/scan.tsx`](../app/scan.tsx) | Full-screen QR scanner with manual-entry fallback | Replaces with `/send?destination=...` | +| `/contacts` | [`app/contacts.tsx`](../app/contacts.tsx) | Add, scan, update, and remove contacts | Entered from Settings | +| `/diagnostics` | [`app/diagnostics.tsx`](../app/diagnostics.tsx) | Show redacted diagnostics and environment information | Entered from Settings | +| `/transaction/:id` | [`app/transaction/[id].tsx`](../app/transaction/[id].tsx) | Render or fetch one transaction | Home, Activity, or deep link | +| `/vault/:id` | [`app/vault/[id].tsx`](../app/vault/[id].tsx) | Vault detail/placeholder route | Vault or deep link | +| `/vault-lock/:id` | [`app/vault-lock/[id].tsx`](../app/vault-lock/[id].tsx) | Time-lock detail and mature-withdraw state | Vault list or deep link | -Read top-down from the single JS entry point `expo-router/entry`. Arrow `→` means "navigates to"; `—` means "wraps as child"; `↩` means "navigates back with result / prefilled params". +### Route cautions +- `/scan` and the scanner embedded in `/send` are separate implementations. +- The root scanner sets a `destination` query parameter; verify that Send consumes it before relying on that handoff. +- Root-level screens use multiple header conventions. +- There is no `app/+not-found.tsx`; unmatched deep links have no app-specific route. + +## 4. Primary flows + +### Wallet creation + +```text +(auth) landing + -> /create + -> generate keypair + -> reveal and confirm backup + -> walletStore.setWallet() stores the secret in SecureStore + -> walletStore.markBackupPending() + -> /wallet-creation-success + -> replace with /(tabs) ``` - package.json: "main": "expo-router/entry" - │ - ▼ - app/_layout.tsx (Root Stack) - ├─ preload: shim.js (polyfills FIRST) - ├─ ErrorBoundary + LockScreen wrap - └─ Auth redirect gate (walletChecked + publicKey) - │ - ┌─ NO wallet ───────┴───────── YES wallet ──┐ - │ │ - ▼ ▼ - (auth)/_layout.tsx (tabs)/_layout.tsx - ┌────────────────────┐ ┌─── OFFLINE BANNER ────┐ - │ index (Create/Imp) │ │ Home Activity │ - │ ▼ ▼ │ │ Vault Settings │ - │ create import │ └────────────────────────┘ - └────────────────────┘ │ - ┌────────────┴─────────────┬──────────────┬────────────┬───────────────┐ - ▼ ▼ ▼ ▼ ▼ - /send /receive /scan /contacts /diagnostics - │ (form validates) │ - ▼ └── on decode: /send ?destination=... - /sign-confirmation - │ (user approves signing intent) - ▼ - /review-transaction - │ - (8-phase signer SM) - │ success: replace (no back) - ▼ - /payment-success → "Back to Home" → /(tabs) - - DYNAMIC DEEP-LINKABLE ROUTES (stack-pushed from within their parent screen) - ├─ /transaction/:id ← tap row in Home recent / Activity list - ├─ /vault/:id ← tap Vault header / deep link scheme://stellar-pocketpay/vault/123 - └─ /vault-lock/:id ← tap lock row in Vault list + +### Wallet import + +```text +(auth) landing + -> /import + -> validate secret seed + -> pocketpay-sdk importWallet() + -> walletStore.setWallet() + -> success state + -> replace with /(tabs) ``` -### 2.1 Back-Stack Behavior — Key Rules +### Send and signing + +```text +Home or payment entry + -> /send + -> validate address, amount, memo, balance, funding, and network state + -> /sign-confirmation + -> explicit user approval + -> /review-transaction + -> signerStore: review -> handoff -> signing -> submitting -> confirming + -> completed or failed + -> optimistic transaction added to walletStore + -> replace with /payment-success + -> replace with /(tabs) +``` -Document these behaviors explicitly when modifying navigation: +Using `replace` after completion is intentional: it prevents back-navigation into a stale signing flow. -- `/payment-success` uses **`router.replace`**, not `push`. The user cannot go "Back to signing" → always jumps to Home. This prevents accidental duplicate sends. See: [app/review-transaction.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/review-transaction.tsx) -- RootLayout auth gate uses **`router.replace`** when swapping `(auth)`↔`(tabs)`. This means tapping hardware Back after sign-out does NOT pop a tab screen back into view. See: [app/_layout.tsx auth gate](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/_layout.tsx#L46-L66) -- `/scan` calls **`router.back()` after setting params** on a successful decode. This returns to the form, preserving what the user had already typed into Amount/Memo before scanning. See [app/scan.tsx](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/app/scan.tsx). -- Dynamic routes `/transaction/:id`, `/vault/:id`, `/vault-lock/:id` accept **both deep links and programmatic pushes**. They must render gracefully even when the parent store state is empty (e.g. user taps a stored deep link before the wallet loads — show a loading or "Wallet not loaded yet" placeholder). +### Receive and scanning -### 2.2 Deep Links (Scheme: `stellar-pocketpay://`) +```text +Home -> /receive -> render/copy/share receive payload +Home -> /scan -> QR result -> replace with /send?destination=... +/send -> embedded QrScanner -> write address directly into form state +``` -Configured in `app.json` → `scheme: "stellar-pocketpay"`. Currently 5 deep-link targets are supported because Expo Router maps file paths 1:1 to scheme paths: +### Activity detail -| Deep link | Resolves to | Prerequisite | -| ----------------------------------- | ---------------------------------- | ------------------------------------- | -| `stellar-pocketpay:///` | Home (if wallet) or auth (if not) | — | -| `stellar-pocketpay:///send?destination=G…` | Prefilled Send form | `destination` must be valid strkey | -| `stellar-pocketpay:///receive` | Receive screen | wallet loaded | -| `stellar-pocketpay:///transaction/tx-123` | Transaction detail | wallet loaded **or** network fetch succeeds; tx ID validated | -| `stellar-pocketpay:///vault-lock/4` | Single lock detail view | wallet loaded + id 4 in vaultStore.locks | +```text +Home recent row or Activity row + -> /transaction/:id + -> use walletStore data when present + -> fetch from Horizon when absent + -> render loading, detail, not-found, or retry state +``` + +### Vault + +```text +Vault tab + -> load availability, balance, and locks + -> deposit/withdraw/lock orchestration + -> /vault/:id + -> /vault-lock/:id +``` + +The vault may be mock-backed or use a configured Soroban integration. A UI success state must not be documented as confirmed settlement without checking the active configuration and confirmation path. -#### Transaction Deep Link Behavior +## 5. Feature ownership -The transaction detail route (`/transaction/[id]`) supports full deep link resolution: +This table describes current ownership, not an ideal future folder structure. -1. **In-memory lookup first** — if the transaction is already in the wallet store (e.g. user just sent it), it renders instantly. -2. **Network fetch fallback** — if not in the store, the app fetches the operation from Horizon via `fetchOperationById()`. Shows a loading spinner during fetch. -3. **Validation** — the `id` param is validated (non-empty, reasonable length, no control characters) before any network request. Invalid IDs show a clear error state. -4. **Not found / error states** — if the transaction doesn't exist on the network or the fetch fails, the user sees a descriptive error with Retry and Go Back actions. -5. **Auth gating** — if a logged-out user arrives via deep link, the URL is preserved and replayed after authentication completes (via `pendingDeepLink` in `app/_layout.tsx`). +| Feature | Screen ownership | State/orchestration | Services and deep dependencies | +| --- | --- | --- | --- | +| Wallet lifecycle | Create, Import, wallet section in Settings | `src/store/walletStore.ts` | `src/services/stellar.ts`, `pocketpay-sdk`, SecureStore, AsyncStorage backup acknowledgement | +| Balance and funding | Home, Send warnings, Activity | `walletStore` | Horizon and Friendbot helpers | +| Transactions | Home preview, Activity, transaction detail | `walletStore`, transaction feature/util modules | Horizon access in Stellar service | +| Send and signing | Send, Sign Confirmation, Review Transaction, Payment Success | `walletStore`, `signerStore`, and screen orchestration | Stellar service, wallet secret, contact labels | +| Receive and QR | Receive, Scan, Send/Contacts scanner surfaces | Mostly local screen/component state | Expo Camera, `QrScanner`, QR rendering library | +| Contacts | Contacts screen and Send contact UI | `appStore` for Contacts; `contactStore` for parts of Send | AsyncStorage, validation and normalization | +| Vault | Vault tab and dynamic vault routes | `vaultStore`, `src/features/vault/`, vault hooks | Vault/Stellar services, wallet secret, Soroban configuration | +| Settings/theme | Settings | `appStore` plus feature-specific stores | AsyncStorage, environment configuration | +| App lock/security | Root LockScreen and Settings | `appLockStore`, `walletStore` | Local Authentication, SecureStore, reset UI | +| Diagnostics/errors | Root recovery UI and Diagnostics | ErrorBoundary, global error handler, reporting utilities | Redaction and environment metadata | ---- +## 6. Store and persistence boundaries -## 3. Feature Boundaries & Ownership Grid +| Store | Owns | Cross-feature consumers | +| --- | --- | --- | +| `walletStore` | Public key, secret access, balance, funding, transactions, wallet reset, backup reminder | Root, auth, Home, Activity, Send, signing, Receive, Vault, Settings | +| `appStore` | Theme, app initialization, one contacts collection | Root, theme, Contacts, Send labels, Settings | +| `signerStore` | Transaction review/signing phase machine | Review Transaction and payment completion | +| `vaultStore` | Vault config, balance, locks, action state | Vault routes and Settings reset paths | +| `appLockStore` | Lock preference and biometric authentication | Root LockScreen and Settings | +| `contactStore` | A second contacts collection and recent recipients | Send feature | -This is the most important section for contributors. If you are working on *feature X*, the "OWNED BY" columns tell you which files to edit and which you should touch only via exported actions/hooks. +### Persistence map -**Legend:** +| Data | Storage | Owner | Caution | +| --- | --- | --- | --- | +| Wallet secret | SecureStore | `walletStore` | Never move into AsyncStorage, route params, logs, diagnostics, screenshots, or persisted Zustand state | +| Wallet runtime data | Memory and network refresh | `walletStore` | A public key alone does not prove secret restoration succeeded | +| Backup acknowledgement | AsyncStorage | `walletStore` | Must survive restarts until acknowledged | +| Theme and `appStore` contacts | AsyncStorage | `appStore` | Parse stored values defensively | +| Feature contacts and recent recipients | Persisted Zustand/AsyncStorage | `contactStore` | Separate from `appStore.contacts` | +| Vault state | Store/service-specific persistence | `vaultStore` and vault utilities | Treat local financial-looking state as cache/placeholder unless contract-confirmed | -- 🟢 **Primary — edit here first.** The file owns the logic/UI for this feature. -- 🟡 **Secondary — read-only / call via exported hooks/actions.** Depend on this, do not modify unless the feature needs a new capability. -- 🔗 **Deep dependency — rarely need to modify.** Core layout/shell, shared types, shared components. -- 🚫 **Never modify for this feature.** Would break invariants. +### Duplicate contacts boundary -### 3.1 Ownership Grid (7 Feature Areas) +There are currently two contacts sources: -| Feature Area | UI Entry: File(s) 🟢 | Hooks / Feature Logic 🟢 | Store Owner 🟢 | Service Layer 🟡 | Shared Components 🟡 | -| ------------------------ | ----------------------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | -------------------- | -| **1. Wallet Lifecycle** | `(auth)/create.tsx` `(auth)/import.tsx` `(tabs)/settings.tsx Wallet section` | — (inline orchestration in screen; import logic helper `parseStoredSecret` in store) | [store/walletStore.ts](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/src/store/walletStore.ts) — create/import/export/clear, backup reminders | [services/stellar.ts](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/src/services/stellar.ts#L17-L24) — `generateKeypair`, `validateSecretKey`; also SecureStore via store | SecretKeyReveal, WalletResetConfirmModal, BackupReminderModal | -| **2. Balance / History** | `(tabs)/index.tsx` (Top 5 + Balance card) `(tabs)/history.tsx` `transaction/[id].tsx` | Pagination inside walletStore actions; pull-to-refresh inline | walletStore → balance, transactions[], nextCursor, isLoadingMore | stellar.ts → fetchXlmBalance, fetchTransactionsPage, getExplorerTxUrl | TransactionListItem, EmptyState, NetworkStatusBanner | -| **3. Send / Receive** | `send.tsx` `receive.tsx` `review-transaction.tsx` `payment-success.tsx` `scan.tsx` | **[hooks/useSignerHandoff.ts](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/src/hooks/useSignerHandoff.ts)** — 8-phase orchestrator; validation.ts pure funcs | walletStore (getSecretKey, refresh after send), **[store/signerStore.ts](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/src/store/signerStore.ts)** — handoff phases, lastResult | stellar.ts → sendXlmTransaction, fetchBaseFee; signer.ts → LocalSigner; pocketpay-sdk → validatePublicKey | QrScanner, FundButton, SigningConfirmModal, PaymentErrorBanner, DirtyFormConfirm | -| **4. QR / Scanner** | `scan.tsx` `receive.tsx` (QR render) | — (inline Expo Camera barcode callbacks) | walletStore publicKey read only | — | **QrScanner** (component) + react-native-qrcode-svg lib | -| **5. Address Book** | `contacts.tsx` + Send form contact picker flow | dedup helpers in `utils/contacts.ts` | [store/appStore.ts](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/src/store/appStore.ts) → contacts[] + addContactIfUnique + removeContact | — (persists via AsyncStorage inside appStore actions) | FormField, ConfirmModal | -| **6. Vault** | `(tabs)/vault.tsx` `vault/[id].tsx` `vault-lock/[id].tsx` | **[features/vault/](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/src/features/vault)** — maturedLockWithdrawal, useMaturedLockWithdrawal, useVaultDepositForm, useVault | **[store/vaultStore.ts](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/src/store/vaultStore.ts)** — balance, locks[], isConfigured | **[services/vault.ts](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/src/services/vault.ts)** — Soroban RPC calls; stellar.ts — mock fallbacks | All Vault* Modal & List components (MaturedLockWithdrawalModal, VaultIntroModal, LockDurationSelector, …) | -| **7. Settings / Network / Theme / AppLock** | `(tabs)/settings.tsx` `diagnostics.tsx` | **NEW:** [features/settings/useNetworkEnvironment.ts](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/src/features/settings/useNetworkEnvironment.ts) (Network tier, hostname mask, warnings); [hooks/useTheme.ts](file:///c:/Users/Muhammad/.trae/Grantfox/pocketpay-mobile/src/hooks/useTheme.ts) | appStore (theme, init), walletStore (secret export, clear), appLockStore (lock flag, biometrics authenticateAsync), vaultStore (locks purge, intro reset) | stellar.ts/vault.ts read only (isVaultConfigured); AsyncStorage + SecureStore via stores | AsyncActionButton, ConfirmModal, LockScreen | +1. `appStore.contacts`, used by the main Contacts screen and payment label resolution; +2. `contactStore.contacts`, used by Send for embedded contact creation and recent recipients. -### 3.2 Cross-Feature Boundary Rules (Hard Invariants) +Updating one does not update the other. Do not introduce a third source. Any contact-related change must identify which store is authoritative for that flow and account for migration or consolidation when crossing the boundary. -1. **No screen imports a `services/` module directly.** Always go through the owning store/hook. The single exception today is `getExplorerTxUrl()` (pure string formatter — may be relaxed). -2. **No store imports another store.** If store A needs store B's data, read it inline inside the screen/hook with two separate `useXxxStore(…)` calls and combine locally. -3. **`features/` modules never import from `app/` (screens).** Features are pure logic + hooks; dependency arrow must point from app→features, never reverse. -4. **`components/` never import a store.** Always pass props/callbacks from the screen. This keeps the 36-component library portable. +## 7. Shared components ---- +Shared UI lives under `src/components/`. -## 4. End-to-End Payment Flow — Navigation Path (Send → Review → Success) +| Category | Examples | Boundary | +| --- | --- | --- | +| Navigation/chrome | `ScreenHeader`, `NetworkStateBanner`, `OfflineBanner` | Route decisions remain in layouts/screens | +| Actions/forms | `Button`, `AsyncActionButton`, `FormField`, `Input`, `ConfirmModal`, `DirtyFormConfirm` | Service calls and validation remain with the owner | +| States/status | `LoadingState`, `EmptyState`, `WalletEmptyState`, `StatusBadge`, `FundingStatusBanner` | Preserve consistent loading, error, and accessibility behavior | +| Security/recovery | `LockScreen`, `SecretKeyReveal`, `WalletResetConfirmModal`, `BackupReminderModal` | Do not weaken authentication, masking, or destructive confirmation | +| Payments/contacts | `ReviewConfirm`, `QrScanner`, `ContactPicker`, `ContactForm`, `PaymentErrorBanner` | Keep signing and persistence outside presentation components | +| Vault | Vault modals, lists, details, and unavailable state | Shared UI must not decide whether data is mock or contract-confirmed | -Contributor onboarding reference: every step along the send/receive user journey with navigation events marked. +Preferred dependency direction: +```text +screen/layout -> feature hook or store action -> service/SDK +screen/layout -> shared component through props ``` - USER ACTIONS FILES / NAV EVENTS - ───────────────── ───────────────────────────────────────────────────────────────── - ▼ Taps [Send XLM] Home (index.tsx) → router.push('/send') - on Home tile - │ - ▼ Enters: dest + amount + /send.tsx - memo. [Scan] also ok → ├─ Validates address, amount, memo (inline) - prefills dest └─ All pass? → router.push('/review-transaction?dest=...&amt=...&memo=...') - │ - ▼ Review screen renders: /review-transaction.tsx - amount + fee + dest + memo ├─ signerStore.startReview() → phase = 'review' - "Sign & Send" CTA ├─ USER TAPS [Sign & Send] - │ │ ├─ useSignerHandoff.initiateSigning() - │ │ └─ phases: handoff → signing → submitting → completed - │ │ └─ service call: stellar.sendXlmTransaction - │ │ - │ └─ phase==='completed' → setTimeout 1500ms → - │ router.replace('/payment-success?hash=...') - ▼ "Payment successful" + /payment-success.tsx - hash + explorer link └─ [Back to Home] → router.replace('/(tabs)') - │ - User is back on Home. └─ walletStore.refreshWalletData(publicKey) has fired automatically - → new balance visible, top of Activity shows the new row + +This is a target boundary, not a fully enforced rule; some screens currently import services directly. + +## 8. Cross-feature dependencies + +```text +Root shell + -> appStore initialization + -> walletStore restoration + -> appLockStore through LockScreen + -> ErrorBoundary and globalErrorHandler + +Wallet/transactions + -> walletStore + -> services/stellar + -> SecureStore and Horizon/Friendbot + +Payment + -> walletStore + -> signerStore + -> appStore contacts for labels + -> contactStore for Send contacts/recent recipients + -> services/stellar + +Vault + -> walletStore identity/secret access + -> vaultStore + -> vault feature hooks + -> vault service or mock fallback + -> Soroban configuration + +Settings + -> appStore + -> appLockStore + -> walletStore + -> vaultStore + -> Contacts and Diagnostics routes ``` -**Why `replace` instead of `push` here?** If `payment-success` used `push`, the user could tap "Back" on the OS and land on Review Transaction *again*, with a stale amount already pre-signed and the Sign button still clickable — an invite for double-spend UX bugs. `replace` cleans the stack. +Changes to `walletStore`, `services/stellar.ts`, root auth logic, or storage utilities require regression checks across several features. ---- +## 9. Global error and recovery boundaries -## 5. Route Assumptions & Adding New Routes +| Layer | Owner | Handles | +| --- | --- | --- | +| Render boundary | `ErrorBoundary` | React render/lifecycle errors | +| Global JS handler | `globalErrorHandler.ts` | Exceptions outside render boundaries | +| Rejection tracking | `globalErrorHandler.ts` | Unhandled promises | +| Reporting/redaction | Error reporting and diagnostics utilities | Safe metadata without secrets | +| Wallet recovery | Root layout | SecureStore failure, retry, destructive reset | +| Network state | Tab layout and selected root screens | Offline/degraded state and write disabling | +| Feature recovery | Owning screen/store/hook | Validation, submission, vault, contact, transaction errors | -When you add a new route under `app/`, these assumptions are baked into the current architecture — **you are opting in to all of them** unless you explicitly modify the root layout or create a new route group. +Global handlers report and then preserve the platform’s default crash/RedBox behavior. They must not silently swallow fatal errors. -### 5.1 Assumption A — Every route gets the 3 safety wrappers automatically +## 10. High-risk caution zones -Every file under `app/` (including new ones you add) inherits, top-down: +### Secret access -1. **Polyfills loaded FIRST** via `shim.js` at `app/_layout.tsx:1` (Buffer, process, crypto.getRandomValues). If you add a new screen that imports StellarSdk before root renders (don't), it will throw. -2. **`ErrorBoundary` → `ErrorBoundaryFallback`** on render exceptions. (No partial crashes.) -3. **`GestureHandlerRootView`** → if your screen uses swipeable rows, gesture handlers work without re-wrapping. -4. **App Lock gate (`LockScreen` wrapper around ``)** — if `appLockStore.isLockEnabled && !isAuthenticated`, the screen is hidden behind biometrics regardless of how the user arrived (deep link, OS relaunch, or push notification). -5. **`NetworkStatusBanner` / `OfflineBanner`** — in `(tabs)/` sub-layout, banners render. Outside `(tabs)/`, you may need to include it yourself if offline behavior matters for the new screen. +- `walletStore.getSecretKey()` crosses the app’s most sensitive boundary. +- Never pass secrets through routes, AsyncStorage, logs, analytics, diagnostics, screenshots, or user-visible errors. +- Preserve confirmation and clear failure states for export/reset operations. -### 5.2 Assumption B — Auth redirection gate runs for EVERY route, not just group roots +### Polyfill order -The `useEffect` in `app/_layout.tsx` (lines 46–66) redirects to `/(auth)` or `/(tabs)` based on: +The root imports `shim` first for Stellar and cryptographic dependencies. Do not move it below other imports or import the Stellar SDK from an earlier startup module. -- `appStore.isInitialized === true` AND -- `walletStore.walletChecked === true` +### Auth and deep links -**If you add a new route in a new group, e.g. `(payments)/something.tsx`**, the root layout still knows nothing about it and will redirect based on the same two rules. If your new group is **meant to be reachable without a wallet** (e.g. a marketing onboarding flow), move it inside `(auth)/` — the gate redirects *any* wallet-absent route into the `(auth)` group. +- Route names are coupled to the root allowlist. +- Deep links can arrive before initialization completes. +- Validate dynamic parameters before network access. +- `push`, `replace`, and `back` are security and duplicate-action concerns, not cosmetic choices. -### 5.3 Assumption C — Deep links always work (file path = URL) +### Payments -Because we use `scheme: "stellar-pocketpay"` and Expo Router's filesystem-routing 1:1 mapping, any new file `/app/foo/bar.tsx` will instantly be reachable via `stellar-pocketpay:///foo/bar`. +- Scanning or choosing a contact must never sign automatically. +- Signing requires explicit confirmation. +- Client timeout does not prove network rejection. +- Success must remove stale signing screens from back navigation. -**Secure your new routes the same way the existing ones are.** Example safe patterns: +### Contacts -- Dynamic routes `/transaction/:id` and `/vault-lock/:id` should check `publicKey` on mount and redirect to `/(auth)` when missing (never render a partial screen with empty on-chain data). -- If the parameter references a non-existent ID (user hand-edited the scheme URL), show a user-friendly "Transaction not found" state inside the dynamic screen — not a crash. +- Two persisted stores overlap. +- Normalize addresses and preserve duplicate checks. +- Contact names are labels; the Stellar address remains authoritative. -### 5.4 Assumption D — The `(tabs)` root and `(auth)` root both mount at `/` +### Vault -Two files are both named `index.tsx` inside different route groups: `(auth)/index.tsx` and `(tabs)/index.tsx`. **Only one renders at a time**, decided by the root redirect gate. +- Confirm mock versus contract-backed mode. +- Do not infer finality from submission alone. +- Lock IDs, maturity calculations, and local persistence require tests. -This means: +### Diagnostics -- ❌ Do NOT add a top-level `app/index.tsx` alongside the two group index files — it will win the routing ambiguity and break the gate. -- ✅ When you want to add a *new unauthenticated screen*, always add it inside `(auth)/` as a sibling of `create.tsx`. -- ✅ When you want to add a *new authenticated tab* (e.g. `explore`), add it inside `(tabs)/` AND add its tab entry in `(tabs)/_layout.tsx` tabs config. +- Keep diagnostics read-only and redacted. +- Never expose secret seeds, authorization material, or raw storage. +- Keep synthetic errors separate from normal user flows. ---- +## 11. Contributor checklist -## 6. Cross-Reference — Other Docs Related to Navigation & Features +For a route change: -This document is one of several contributor references in `docs/`. +1. Update the route file and grouped navigator when applicable. +2. Check root auth and deep-link replay. +3. Decide whether the route needs network-state UI. +4. Preserve intentional `push`, `replace`, or `back`. +5. Validate route parameters. +6. Identify the owning store and avoid duplicate state. +7. Preserve secret and persistence boundaries. +8. Reuse shared components and UI-state patterns. +9. Update tests and the release checklist. +10. Update this map when ownership or navigation changes. -| Document | What It Covers / Cross-Reference | -| ------------------------------------------------------------ | --------------------------------- | -| [screen-inventory.md](./screen-inventory.md) | Per-screen UI state catalog (loading / empty / error / …). Complementary to this nav map: use it after you've found the right file. | -| [user-flows.md](./user-flows.md) | 9 detailed diagrams of user journeys, including onboarding, send, vault lock maturity. Read this before editing a flow you haven't touched. | -| [signer-handoff-design.md](./signer-handoff-design.md) | The 8-phase signer state machine's original design rationale, external signer/hardware wallet roadmap. | -| [vault-integration-assumptions.md](./vault-integration-assumptions.md) | Contract addresses, test accounts, mock-vs-real switch. Required context before modifying `/vault`, `/vault/:id`, or `/vault-lock/:id`. | -| [release-testing-checklist.md](./release-testing-checklist.md) | Manual QA that re-traces every route above. If you change a route or add a new one, **you MUST add corresponding boxes to this checklist.** | -| [accessibility.md](./accessibility.md) | A11y labels, touch targets, focus order, announcements — apply to *every* new route you add. | -| [contacts.md](./contacts.md) | Address book duplicate rules, data model, expected edge cases — specific to `/contacts`. | -| [qr-payment-requests.md](./qr-payment-requests.md) | QR format / BIP-21 style prefix plans, future extensions — specific to `/receive` + `/scan`. | +For a shared store/service change: ---- +1. Search all consumers. +2. Check cold start and restoration. +3. Test empty, loading, error, retry, and offline states. +4. Check direct navigation and deep links. +5. Verify persistence and malformed stored data. +6. Confirm logs and diagnostics remain redacted. -## 7. Quick Finder — "I Need To Change … Where?" +## 12. Quick ownership finder -Contributor cheat sheet. If you're about to make one of these common changes, open the left file first. +| Change | Start here | Also inspect | +| --- | --- | --- | +| Add pre-wallet screen | `app/(auth)/`, auth layout | Root redirect/startup | +| Add bottom tab | `app/(tabs)/`, tab layout | Network banner and auth redirect | +| Add root route | New `app/` file | Root ``, header/back, auth allowlist, deep links | +| Change wallet creation/import | Auth screens, `walletStore` | SecureStore, backup and recovery | +| Change balance/Activity | `walletStore`, Home, History | Horizon, pagination, pending transactions | +| Change Send/signing | Send, Sign Confirmation, Review, `signerStore` | Secret access, contacts, network, success replacement | +| Change Receive/QR | Receive, Scan, `QrScanner` | QR docs, permission states, query handling | +| Change contacts | Contacts, `appStore`, `contactStore` | Normalization and Send picker | +| Change vault | Vault screens, `vaultStore`, vault feature | Service, mock/live config, wallet secret | +| Change Settings | Settings and owning stores | Reset, app lock, theme, diagnostics | +| Change global errors | Root, ErrorBoundary, global handler | Redaction and platform handler | -| Change Goal | Open This First 🎯 | Also Check | -| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | -| Add a new bottom-tab screen | `(tabs)/_layout.tsx` → add Tabs.Screen entry, then create the file | root auth gate in `_layout.tsx` if it needs special auth rules | -| Add a new pre-auth onboarding step | Add under `(auth)/` as a sibling of `create.tsx`, register in `(auth)/_layout.tsx` stack | release-testing-checklist §2 wallet flows | -| Change the "what screen after sign-in / after sign-out" rule | Auth gate in `app/_layout.tsx` lines 46–66 | LockScreen component for app-lock behavior | -| Rename a route file | 1) Rename the file; 2) Update every `router.push('/old-path')` site-wide (grep for the string); 3) Add a redirect or note the deep-link breakage | deep-link table §2.2 (changing file names breaks scheme URLs) | -| Add a new Store slice (e.g. `nftStore`) | Create under `src/store/nftStore.ts`, follow slice pattern (no cross-store imports), initialize in its own module top scope. Never add to a single root combine store. | `shim.js` if new service/SDK requires an extra polyfill | -| Add a new feature section in Settings | `(tabs)/settings.tsx` as a new `` section + new sub-constants in `features/settings/useNetworkEnvironment.ts` if env-derived | release-testing-checklist §7 Settings/Persistence | -| Add a new payment / signing path (e.g. MUXED account, XLM path with base64 memo) | `/send.tsx` validators + `review-transaction.tsx` + `stellar.ts` send helper; route navigation stays the same | signer-handoff-design.md, user-flows.md § send payment flow | +Keep this document synchronized with the route tree. It maps current ownership and risk; it does not replace reading the code being changed.