Skip to content

feat(wallet): primitives + wallet tab rebuild (A+B)#4

Merged
Magicred-1 merged 16 commits into
anonmesh:v3from
epicexcelsior:epic/wallet-ui-port
Apr 23, 2026
Merged

feat(wallet): primitives + wallet tab rebuild (A+B)#4
Magicred-1 merged 16 commits into
anonmesh:v3from
epicexcelsior:epic/wallet-ui-port

Conversation

@epicexcelsior
Copy link
Copy Markdown
Collaborator

@epicexcelsior epicexcelsior commented Apr 23, 2026

Summary

Rebuilds the wallet tab with a custom primitive + haptic layer on top of the existing theme/useGlass system, and wires a token-first send flow with real devnet SOL transfers (local + MWA signing). Receive gets a scannable QR with logo + swipe-to-dismiss. Also includes a gesture-handler root wrap fix that unblocks the nodes MeshMap crash.

Lane agreement: wallet tab + send/receive routes + primitives + haptics. Zero touches to onboarding, messages, settings, nodes, or shared contexts.

Visual system: consumes the existing theme/colors.ts, theme/typography.ts, theme/spacing.ts, and hooks/useGlass. No parallel color or typography system added.

What ships

  • Primitives: DepthButton, PressSurface, IconButton, SlideToConfirm, NumericKeypad, SegmentedControl, Pill, Icon, TokenLogo, SwipeDismissHandle.
  • Design-system layer: haptics.ts with 10 interaction tiers, motion tokens (press springs, durations, easing), state tokens (press washes, disabled opacity).
  • Wallet tab rewrite (app/(tabs)/wallet.tsx): hero + balance card (long-press hide, AsyncStorage persistent) + 4-tile action row (Send / Receive / Swap-Soon / Yield-Soon) + nearby-peers card + recent activity + pull-to-refresh + staggered entrance animations.
  • Send flow (/send/recipient → /send/amount → /send/review → /send/success): token-first picker on recipient, dynamic per-token decimal precision (SOL=9, USDC=6), slide-to-confirm, devnet signing on confirm.
  • Receive (/receive): standard/stealth segmented toggle, real scannable QR (react-native-qrcode-svg) with logo center, copy/share action bar with pulse animation, swipe-down dismiss.
  • Real devnet SOL transfer: src/services/sendTransaction.ts — local wallets sign in-app via exportSecretKey → Keypair → sendRawTransaction (biometric-gated, zeroed after signing); MWA wallets use transact → signAndSendTransactions with session-pubkey match check. Devnet hardcoded for safety.
  • Root-layout fix: <GestureHandlerRootView> now wraps the Stack, unblocking MeshMap which was crashing on the nodes tab.
  • Build fix: scripts/fix-lxmf-gradle.mjs postinstall patch for @magicred-1/react-native-lxmf gradle config.

Dependencies added

  • @react-native-async-storage/async-storage@2.2.0 (hide-balance persistence)
  • react-native-qrcode-svg (scannable QR)

Known deferred work

Honest list of follow-ups, in priority order:

  • Real USDC SPL transfer (@solana/spl-token + associated token accounts) — USDC currently routes to a simulated receipt.
  • Live wallet token discovery via getParsedTokenAccountsByOwner.
  • Stealth meta-address derivation wire — /receive stealth mode shows a preview string only.
  • Dynamic fee via getFeeForMessage — Review shows a static ~0.000005 SOL which matches the actual simple-transfer fee on devnet.
  • Route selector (on-chain vs mesh) on Review — currently a static On-chain pill.
  • Swipe-dismiss on /send/recipient (currently only on /receive).
  • Upgrade Success screen Share / Explorer buttons to primary weight.
  • Replace mock activity fixture in src/__fixtures__/mockActivity.ts with real tx history once the wallet layer emits it.
  • Pull-to-refresh is currently a visual stub (700ms + haptic), not a real refetch.

Orphaned after this merge

The previous inline wallet tab panel files are no longer referenced anywhere; safe to delete in a follow-up cleanup commit if you want:

  • components/wallet/SendPanel.tsx
  • components/wallet/ReceivePanel.tsx
  • components/wallet/SwapPanel.tsx
  • components/wallet/YieldPanel.tsx
  • components/wallet/SwapRow.tsx
  • components/wallet/WalletTabs.tsx
  • components/wallet/AssetDot.tsx

The ASSETS + TOTAL_USD constants in components/wallet/constants.ts are still consumed by the new BalanceCard and TokenPicker until real balance wires.

Test plan

  • npx tsc --noEmit clean (pre-existing errors in MeshMap / nodes / ThemeContext are unchanged; no new errors added)
  • Reload on Seeker / Android: wallet tab renders hero + 4 action tiles + peers + activity
  • Long-press balance hides amount; persists across app restart
  • Send tile: token chip (SOL) at top → picker opens → select USDC → chip updates with balance + max decimals
  • Paste address → Continue → amount keypad respects token decimals → Review → Slide to confirm
  • SOL path: real biometric prompt → real devnet signature → Explorer link opens tx on explorer.solana.com
  • USDC path: simulated receipt (flagged by simulated: "1" query param)
  • Receive tile or header QR icon: opens receive screen → QR scans from another device → Copy / Share work → grab-handle swipe down dismisses
  • Nodes tab: MeshMap renders without the old GestureDetector crash
  • No regressions on onboarding / messages / settings

Needed for local Android build. Upstream lxmf 0.1.6 has versionCode/
versionName as literals in build.gradle.kts that break on some gradle
setups. Postinstall script rewrites them to reflection calls.
Crash in MeshMap.tsx:387 on nodes tab — GestureDetector requires
GestureHandlerRootView ancestor per react-native-gesture-handler install
docs. Standard one-line wrap.

Also prerequisite for incoming wallet-ui-port work (SlideToConfirm +
PressSurface use GestureDetector).
npm install after 038364a added hasInstallScript: true flag to the
lockfile. Committing so downstream installs reflect the postinstall
hook without a dirty tree on every clone.
Additive layer on top of teammate's existing theme/useGlass system.
No color or typography tokens added — his theme/colors.ts,
theme/typography.ts, and hooks/useGlass own those. All primitives
consume useTheme() + stateTokens (press washes, disabled opacity).

Primitives ported from archive/hunter-v3-full, rewired to use
mobile_app/theme/* (spacing[1..8] numeric scale, radii, fontSize,
fontFamily, and anonme.sh palette via colors.primary/accent/success).

- DepthButton: morphic gradient button, 5 variants × 5 tones, press
  compression + highlight/bottom-inset motion. Legacy 'purple' tone
  routes through colors.accent (neon-green) until stealth palette
  lands.
- PressSurface: generic press wrapper with row/strip/card variants,
  press-in wash + spring release with caller-matched border radius.
- IconButton: 36/44/52 hit targets, tint tones, plain + contained.
- SlideToConfirm: pan-to-confirm slider with magnet zone, resistance,
  threshold haptic, and color interpolation cyan → neon on confirm.
  Phosphor ArrowRight swapped for Feather arrow-right.
- NumericKeypad: amount input with token chip, fiat label, use-max
  chip. Phosphor Backspace/CaretDown swapped for Feather delete and
  chevron-down. SolanaIcon/USDCIcon replaced with TokenLogo primitive.
- SegmentedControl: tone-able toggle (cyan/purple/stealth-via-neon).
- Pill, Icon, TokenLogo: leaf primitives (Feather wrapper, inline SVG).

Design-system layer:
- haptics.ts: 10 tiers (tap/select/lightPress/mediumPress/confirm/
  warning/error/dragStart/dragCross/releaseHeavy) backed by expo-haptics.
- motion.ts + tokens/motion.ts: easing scales, duration tokens,
  press compression, spring presets (direct/settle/sheet/pressRelease/
  iconRelease), travel amounts.
- tokens/state.ts: depth (press insets, highlights) + feedback (press
  washes, disabled opacity, icon tint).

Hooks + utils:
- useHideBalance: React context + provider. NOTE: in-memory only until
  @react-native-async-storage/async-storage is added to deps.
- blePermissions: Android 12+ runtime permission helpers (SCAN,
  CONNECT, ADVERTISE, FINE_LOCATION).

Deliberately skipped:
- GlassSurface primitive (his useGlass wins)
- appTheme, tokens/foundation, tokens/semantic, tokens/component
  (rewired inline or replaced by useTheme())
- SwipeDismissView (never worked on Android)
- devReset (depends on LocalWallet infra not yet reconciled with his
  wallet context)
- sound.ts (sound system deferred; haptics carry interaction feedback)

tsc --noEmit clean. Zero appTheme imports remaining. Pre-existing
upstream/v3 errors in MeshMap/nodes/ThemeContext are teammate's lane.
Replaces the 200-line inline tabs-with-panels wallet with the Home
composition ported from archive/hunter-v3-full, rewired to teammate's
theme + data contexts.

Layout (top → bottom):
- HomeHero — his ANONMESH kicker + 'wallet' title + eye toggle + QR icon
- BalanceCard — $USD hero + SOL balance below, long-press to hide
- ActionRow — Send / Receive / Swap-Soon / Yield-Soon (4 tiles)
- NearbyPeersCard — peer count + avatar stack, taps to Nodes tab
- Recent section header
- RecentActivity — honest empty state ('no activity yet')

Data wiring:
- displayName + peers from LxmfContext (his)
- publicKey from WalletContext (his) — consumed by QRModal
- ASSETS + TOTAL_USD from his components/wallet/constants fixture
  (matches current visible behavior; real balance wires in Phase 7)
- Hide state from useHideBalance (ours, now AsyncStorage-persistent)

Integrations:
- QR icon in HomeHero opens his existing QRModal via local state —
  no new route needed, reuses his wallet/anonmesh QR view.
- NearbyPeersCard tap navigates to /(tabs)/nodes — his MeshMap owns
  peer visualization; we don't duplicate.
- Send + Receive actions push to /send/recipient and /receive
  (routes land in Commits C + D). Until then Expo Router shows
  'Unmatched route' — acceptable stub.

Persistence:
- Added @react-native-async-storage/async-storage@2.2.0.
- useHideBalance hydrates from + persists to storage under
  anonmesh:hide-balance key.

Root layout:
- HideBalanceProvider wraps inside WalletProvider so the eye toggle
  state is available across the wallet tab without prop drilling.

No touches to onboarding, messages, settings, nodes, or teammate's
contexts. His SendPanel / ReceivePanel / SwapPanel / YieldPanel and
WalletTabs files remain on disk but are no longer imported; safe to
delete in a follow-up cleanup pass.

tsc --noEmit clean. Pre-existing upstream errors in MeshMap / nodes /
ThemeContext remain (teammate's lane, unrelated).
Addresses device review feedback:

- BalanceCard: hide dots now 6 × 22px (HIDDEN_MASK) instead of 3 × 14px
  so the mask reads at the same weight as the amount itself. Glued
  the '$' sign into the amount line so it inherits the 46px hero
  size. Wrapped the hero in a subtle accent-glass surface with a cyan
  shadow glow (22% opacity) for visual weight and brand anchor.
- HomeHero: no copy change; entrance animation added below.
- NearbyPeersCard: peer-count flicker fixed via useMemo on filtered
  lists. State labels adapt: 'Mesh offline' / 'Scanning for peers…' /
  'N peers nearby' with a subtitle showing all-time seen count.
- RecentActivity: replaced honest empty state with 6-row mock fixture
  (MOCK_ACTIVITY under src/__fixtures__/) showing Settled / Handed to
  mesh / Queued on device statuses across SOL + USDC transfers. Rows
  use PressSurface with direction badges and tonal pills. Amount is
  masked to ••• when hide-balance is on.

wallet.tsx: staggered FadeInDown entrance (420ms duration, 90ms step)
across all 6 sections so the screen composes intentionally on mount.

Separation rule: src/__fixtures__/mockActivity.ts imported only by
RecentActivity in dev/demo. Prod tx history replaces it in Phase 7.

tsc --noEmit clean.
Fixes the 'Unmatched route' state when tapping the Send action tile
on the wallet tab. Four new routes under `app/send/`:

- /send/recipient — paste / scan (stub) / dev-mock address fill
- /send/amount — NumericKeypad with fiat equivalent, Use-max chip,
  balance-exceeded validation; reads SOL balance from his ASSETS
  fixture until the wallet layer emits real balance
- /send/review — amount hero + To/Route/Fee detail rows + stealth
  toggle + SlideToConfirm
- /send/success — big success check mark + status + reference with
  copy-to-clipboard, share, explorer link buttons

Shared scaffolding in `components/send/SendScaffold.tsx` (back
button, eyebrow, title, content, footer pattern).

Stubbed for Phase 7:
- Real tx broadcast — `ReviewCard.handleConfirm` currently generates
  a fake 44-char base58 tx id, waits 1.2s, then routes to success.
  Replace with `WalletAdapter.signTransaction` +
  `Connection.sendRawTransaction` when available.
- Peer picker — archive included a nearby-peers list in
  RecipientPicker, but his `LxmfPeer` shape has no publicKey. Mesh
  handle → Solana address mapping is Phase 7 territory; for now only
  paste / scan-stub / dev-mock are offered.
- QR scanner — archive used a QrScannerModal primitive not yet ported.
  Tap shows an Alert until Commit D delivers the scan surface.

All screens use ported primitives (DepthButton, SlideToConfirm,
NumericKeypad, IconButton, Pill, Icon) and consume teammate's
`useTheme` + `useGlass` so colors stay on-brand.

Send tile on wallet ActionRow now opens this flow — no more
'Unmatched route' red screen.

tsc --noEmit clean.
Fixes 'Unmatched route' when tapping the Receive action tile.
Also replaces HomeHero's legacy QRModal opener with navigation to
the new /receive route, so the QR icon in the wallet header now
leads to a proper stealth-capable surface instead of the small
modal.

Layout:
- Header: 'Receive' title + X close button
- SegmentedControl: Standard / Stealth (cyan / neon accent tone)
- When Stealth selected: preview pill labeled
  'Preview — stealth wiring lands in Phase 7' (honest staging)
- Identity alias from LxmfContext.displayName or short wallet address
- QR card (220px) with border colored by active mode
  — cyan for standard, neon accent for stealth
- Supported-networks row: SOL + USDC TokenLogo marks
- Non-selectable short address below QR
- Action bar: Share + Copy with copy-pulse animation
  (cyan halo expands 0→1.12 scale then decays; icon morphs copy →
  check for 1.4s; haptics.confirm fires on copy)

Data:
- walletAddress from WalletContext.publicKey?.toBase58()
- displayName from LxmfContext
- Stealth address is still a preview placeholder
  (previewStealthAddress format); real derivation is Phase 7 and
  lives in worktrees/anon0mesh-fork-ui/lib/stealth/.

QR rendering uses teammate's components/settings/QRCode.tsx which is
a hash-based visual faux-QR — not actually scannable across devices.
Real QR lib (react-native-qrcode-svg) can replace in a follow-up once
scanning is a real path.

HomeHero update: QR icon on the wallet header now navigates to
/receive instead of opening the legacy QRModal. Modal remains in
settings for his tab; no longer referenced from Home.

tsc --noEmit clean.
…button

Addresses round-2 device review:

Root layout — gray bar at top of send/receive screens fixed by
registering each route with headerShown: false. Previously Expo
Router was using its native default header for the unregistered
routes. Also: send/success now has gestureEnabled: false so
swipe-back doesn't escape the receipt; receive opens as a modal with
slide-from-bottom animation.

BalanceCard — removed the visible border. Subtle cyan shadow glow
remains for depth. Cleaner, closer to how the hero read in archive.

NearbyPeersCard — 558-peers bug fixed. LxmfContext accumulates every
peer ever heard; most have stale lastSeen timestamps. Filter to
peers heard within the last 120 seconds (FRESH_WINDOW_SEC). Count
formats with toLocaleString for readability. Dropped the 'seen'
subtitle (it was the 558 number — misleading 'presence' signal).

ReceiveScreen — removed the 'Preview — stealth wiring lands in Phase 7'
pill. Stealth mode now just shows the segmented toggle + a discreet
'(stealth)' qualifier in the supported-networks row. QR card slimmed
down: no border, 8px padding, 200px QR instead of 220px — reads
cleaner and closer to archive weight. Action bar lifted away from
the bottom edge (paddingBottom 7 / paddingTop 3) so Copy ripple
doesn't get clipped by system nav.

ReviewCard — the To row now has a copy icon next to the address.
Tap copies full address + haptic tap.

RecipientPicker — 'Continue to amount' → 'Continue'. Simpler.

SuccessCard — showBack={false}. No back arrow on receipt; Done
button is the only exit. Combined with gestureEnabled: false in
layout, the transfer can't be dismissed mid-receipt by accident.

wallet.tsx — pull-to-refresh wired. 700ms stub + confirm haptic.
Real balance refetch lands in Phase 7.

tsc --noEmit clean.
Three major fixes in one commit, per device review:

1. Real devnet SOL transfer (was faked):
   - New src/services/sendTransaction.ts with devnet Connection
     ('https://api.devnet.solana.com', 'confirmed' commitment)
   - For local wallets: exportSecretKey → Keypair.fromSecretKey →
     sign → sendRawTransaction. Biometric prompt fires inside
     exportSecretKey. Secret is zeroed with .fill(0) immediately
     after signing.
   - MWA path throws 'not yet wired' — Seeker signing lands later.
   - Hardcoded devnet; mainnet wiring is deliberately not in this
     commit so dev builds can't accidentally drain real funds.
   - ReviewCard.handleConfirm now calls sendSolTransfer; error surface
     shows real RPC error messages instead of silently succeeding.
   - Matches parity with archive LocalWalletAdapter.send behavior.

2. Token picker:
   - New components/send/TokenPicker.tsx — modal bottom sheet with
     SOL + USDC catalog entries showing balance + USD value + check
     mark on selected.
   - Wired to AmountKeypad via onPressCurrency — tapping the SOL chip
     now opens the picker. Selecting a token resets amount to 0 and
     updates the keypad's max decimals.
   - Info row at bottom: 'Jupiter integration brings the full token
     list' — honest about what's next.
   - Non-SOL selections (USDC today, Jupiter tokens later) get a
     simulated receipt path in ReviewCard so UX flow still demos.
     Success screen can flag simulated via the 'simulated' param.

3. Dynamic decimal precision:
   - NumericKeypad gains maxDecimals prop. Default 6. SOL passes 9,
     USDC passes 6. Keypad now rejects decimal entries beyond the
     token's configured precision (was hardcoded 2).
   - MAX_DIGITS bumped 10 → 18 to cover SOL at 9 decimals plus large
     integer parts.

tsc --noEmit clean.
…ow + swipe dismiss

Big polish bundle addressing device review round 3:

1. Real scannable QR on /receive:
   - Installed react-native-qrcode-svg (pure JS, no native rebuild).
   - Replaces the hash-based faux-QR with a real QR that cross-device
     scanners can actually read.
   - anonmesh logo embedded in the center (from assets/images/logos/
     anonmesh_logo.png) with ecl='H' (high error correction) so the
     logo doesn't break the scan.
   - Color adapts: void-navy for standard, neon-dark for stealth.

2. Phantom-lite token-first send flow:
   - Token picker moved from amount step to the TOP of the recipient
     screen as a prominent tappable card ('Sending SOL · Solana').
     Users see + choose the token BEFORE entering the address —
     matches Phantom/Base pattern.
   - Amount step no longer has its own token picker. Token flows
     via /send/amount?symbol=SOL URL param. Display-only on amount,
     so pre-chosen token context carries through.
   - User can still backtrack to change token before confirming.

3. Swipe-to-dismiss on receive screen:
   - New SwipeDismissHandle primitive — top-of-screen grab handle
     that drives a shared translateY value via Gesture.Pan with
     tuned thresholds (activeOffsetY: 8, failOffsetY: -10).
   - Deliberate by design: only the handle captures the gesture, so
     child ScrollViews + Pressables aren't blocked (fixes the
     regression from the old SwipeDismissView primitive that killed
     child taps).
   - Release past 120px or 800px/s velocity animates out + calls
     router.back(); otherwise springs back.
   - Haptic tap on dismiss.
   - Applied to /receive only for now. /send/recipient still uses
     the X button; adding a handle there would conflict with the
     native stack back gesture on Android.

4. MWA signing ported from archive:
   - sendTransaction now branches on adapter.getMode(). Local keeps
     the existing in-app signing via exportSecretKey → Keypair.sign.
   - New MWA branch uses transact() from
     @solana-mobile/mobile-wallet-adapter-protocol-web3js. Reauthorizes
     using the cached 'anon_mwa_auth_token_v1' SecureStore token,
     verifies session pubkey matches feePayer (MWA can return a
     different selected account — archive flagged this as incident I5),
     and submits via signAndSendTransactions.
   - Devnet remains hardcoded. MWA identity: anonmesh / anonme.sh.

5. Peer count semantics fixed:
   - Previously counted ALL online+fresh peers including TCP/Reticulum
     hub connections, which explode to hundreds because g00n.cloud is
     a public hub.
   - Now 'peers nearby' filters specifically for via === 'ble' (real
     physical proximity). When no BLE peers but hub is live, falls
     back to 'Connected via hub · N reachable' so the user still sees
     mesh activity without conflating it with proximity.
   - Live pill stays green if either BLE or hub has fresh peers.

tsc --noEmit clean.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Rebuilds the mobile wallet experience by introducing a small design-system layer (motion/state tokens + haptics), new wallet/send/receive primitives, and a rewritten Wallet tab with new Send/Receive routes—including real devnet SOL transfers and a QR-based receive screen—plus a gesture-handler root wrapping fix.

Changes:

  • Added design-system tokens + haptics utilities and a suite of UI primitives used by the wallet/send/receive flows.
  • Implemented new Send flow screens/components and a devnet SOL transfer service (local + MWA signing).
  • Rewrote the Wallet tab UI (hero, actions, nearby peers, recent activity) and added a Receive modal screen.

Reviewed changes

Copilot reviewed 42 out of 43 changed files in this pull request and generated 14 comments.

Show a summary per file
File Description
mobile_app/src/utils/blePermissions.ts New Android BLE permission helper for scan/connect/advertise flows
mobile_app/src/services/sendTransaction.ts Devnet SOL transfer implementation for local + MWA adapters
mobile_app/src/hooks/useHideBalance.tsx AsyncStorage-backed hide-balance provider + hook
mobile_app/src/design-system/tokens/state.ts New state tokens (press washes, disabled opacity, depth colors)
mobile_app/src/design-system/tokens/motion.ts New motion tokens (easing, durations, springs)
mobile_app/src/design-system/tokens/index.ts Barrel export for design-system tokens
mobile_app/src/design-system/motion.ts Motion scale helpers + re-exported motion tokens
mobile_app/src/design-system/haptics.ts Haptics wrapper with mute support and interaction tiers
mobile_app/src/fixtures/mockActivity.ts Mock recent-activity fixture for wallet UI
mobile_app/scripts/fix-lxmf-gradle.mjs Postinstall patch script for lxmf gradle config
mobile_app/package.json Adds postinstall script + AsyncStorage + QR dependencies
mobile_app/package-lock.json Lockfile updates for new dependencies and scripts
mobile_app/components/send/index.ts Barrel export for send-flow components
mobile_app/components/send/TokenPicker.tsx Token picker modal + hardcoded token catalog
mobile_app/components/send/SuccessCard.tsx Send success/receipt UI with share/copy/explorer actions
mobile_app/components/send/SendScaffold.tsx Shared scaffold/layout for send-flow screens
mobile_app/components/send/ReviewCard.tsx Review screen + slide-to-confirm + SOL send invocation
mobile_app/components/send/RecipientPicker.tsx Recipient entry screen with token-first selection
mobile_app/components/send/AmountKeypad.tsx Amount entry screen with numeric keypad + max decimals
mobile_app/components/primitives/index.ts Barrel export for new primitives
mobile_app/components/primitives/TokenLogo.tsx Inline SVG token marks for SOL/USDC
mobile_app/components/primitives/SwipeDismissHandle.tsx Gesture-handler based swipe-to-dismiss handle
mobile_app/components/primitives/SlideToConfirm.tsx Slide-to-confirm control using reanimated + gestures
mobile_app/components/primitives/SegmentedControl.tsx Animated segmented control primitive
mobile_app/components/primitives/PressSurface.tsx Pressable surface primitive with press animations
mobile_app/components/primitives/Pill.tsx Pill/badge primitive with tone mapping
mobile_app/components/primitives/NumericKeypad.tsx Numeric keypad with max-decimals + “Use max”
mobile_app/components/primitives/IconButton.tsx Animated icon button with tone/variant support
mobile_app/components/primitives/Icon.tsx Unified icon component (Feather + custom SVG icons)
mobile_app/components/primitives/DepthButton.tsx Gradient “depth” button with animated press states
mobile_app/components/home/index.ts Barrel export for wallet-home components
mobile_app/components/home/RecentActivity.tsx Recent activity list UI (currently mock-driven)
mobile_app/components/home/NearbyPeersCard.tsx Nearby peers card driven by LxmfContext state
mobile_app/components/home/HomeHero.tsx Wallet header with hide-balance + receive shortcut
mobile_app/components/home/BalanceCard.tsx Balance hero card with long-press hide toggle
mobile_app/components/home/ActionRow.tsx Wallet action tiles (Send/Receive + “Soon” stubs)
mobile_app/app/send/success.tsx Route for send success screen
mobile_app/app/send/review.tsx Route for send review screen
mobile_app/app/send/recipient.tsx Route for send recipient screen
mobile_app/app/send/amount.tsx Route for send amount screen
mobile_app/app/receive.tsx Receive modal with QR + copy/share + swipe-to-dismiss
mobile_app/app/_layout.tsx Root layout wraps Stack in GestureHandlerRootView + providers
mobile_app/app/(tabs)/wallet.tsx Wallet tab rewrite with staggered entrances + pull-to-refresh stub
Files not reviewed (1)
  • mobile_app/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mobile_app/app/receive.tsx Outdated
Comment on lines +213 to +217
withTiming(1, { duration: 180 }),
withTiming(1, { duration: 900 }),
withTiming(0, { duration: 280 }),
);
setTimeout(() => setCopied(false), 1400);
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

setTimeout(() => setCopied(false), 1400) isn't cleared on unmount. If the user dismisses the modal quickly, this can call setCopied on an unmounted component. Consider storing the timeout id in a ref and clearing it in a useEffect cleanup.

Copilot uses AI. Check for mistakes.
Comment thread mobile_app/components/send/SuccessCard.tsx Outdated
Comment on lines +50 to +52
* MWA mode → not yet wired. Throws a clear error until the MWA
* signing flow is ported (Seeker Seed Vault path).
*
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

The header comment says the MWA signing flow is "not yet wired" and will throw, but the implementation below does perform transact → reauthorize → signAndSendTransactions. Consider updating the comment to match the actual behavior.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +53
if (result === PermissionsAndroid.RESULTS.GRANTED) continue;
if (result === PermissionsAndroid.RESULTS.NEVER_ASK_AGAIN) {
anyNeverAsk = true;
}
return anyNeverAsk ? "never_ask_again" : "denied";
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

requestBLEPermissions() returns on the first non-granted permission, so it may return "denied" even if a later permission is NEVER_ASK_AGAIN (and anyNeverAsk never gets a chance to reflect any permission). Consider scanning all results first, then returning never_ask_again if any permission is NEVER_ASK_AGAIN, otherwise denied.

Copilot uses AI. Check for mistakes.
Comment on lines +73 to +76
const lamports = Math.round(amountSOL * LAMPORTS_PER_SOL);
if (!Number.isFinite(lamports) || lamports <= 0) {
throw new Error("Invalid amount");
}
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

Converting SOL to lamports via Math.round(amountSOL * LAMPORTS_PER_SOL) can round up and send more than the user entered (floating-point + rounding). Consider using a safer conversion (e.g., validate decimal precision and use Math.floor, or use a decimal/BN conversion) so the transfer amount is deterministic and never exceeds the requested amount.

Copilot uses AI. Check for mistakes.
Comment on lines +35 to +39
icon: "arrow-up-right",
tone: "primary",
// Route created in Commit C; until then Expo Router will show
// "Unmatched route" which is an acceptable stub for the port.
onPress: () => router.push("/send/recipient" as RouteHref),
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

The comment says the Send/Receive routes aren't created yet and Expo Router will show "Unmatched route", but this PR adds /send/* and /receive screens in app/. Consider updating/removing this comment so it doesn't mislead future readers.

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +23
PermissionsAndroid.PERMISSIONS.BLUETOOTH_SCAN,
PermissionsAndroid.PERMISSIONS.BLUETOOTH_CONNECT,
PermissionsAndroid.PERMISSIONS.BLUETOOTH_ADVERTISE,
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
];
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

getAndroidPermissions() includes ACCESS_FINE_LOCATION for API 31+, but existing BLE permission requests in the app only ask for BLUETOOTH_SCAN/ADVERTISE/CONNECT on 31+ (no location). Requesting location here will trigger an unnecessary permission prompt and diverges from the current in-app behavior; consider dropping location for 31+ and keeping it only for <31.

Copilot uses AI. Check for mistakes.
Comment thread mobile_app/components/home/RecentActivity.tsx Outdated
Comment on lines +113 to +117
const auth = await mwaWallet.reauthorize({
auth_token: cachedToken,
identity: APP_IDENTITY,
});
const sessionPubkey = new PublicKey(Buffer.from(auth.accounts[0].address, "base64"));
Copy link

Copilot AI Apr 23, 2026

Choose a reason for hiding this comment

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

MWA path assumes auth.accounts[0] exists and uses it without validation. If the wallet returns no accounts (or an empty list), this will throw a cryptic error. Also, reauthorize() may return a refreshed auth_token; consider persisting it back to SecureStore to keep the cache valid.

Copilot uses AI. Check for mistakes.
epicexcelsior and others added 5 commits April 23, 2026 00:06
Prevent accidental commit of .env / .env.* variants at any level.

- New root .gitignore: catches root-level env files + OS/editor cruft +
  backup files from repo reorganisation.
- mobile_app/.gitignore: replaces narrow '.env*.local' (Next/Vite
  convention) with '.env' + '.env.*' + '!.env.example' (Expo default
  env filename is bare .env, which the old pattern missed).

No content changes to any tracked source. Safety-only.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@Magicred-1 Magicred-1 merged commit b47191a into anonmesh:v3 Apr 23, 2026
Magicred-1 added a commit that referenced this pull request Apr 25, 2026
feat(wallet): primitives + wallet tab rebuild (A+B)
epicexcelsior added a commit that referenced this pull request May 15, 2026
* refactor(wallet): move dead Send/Swap/Yield panels out of barrel

These four files (WalletTabs, SendPanel, SwapPanel, YieldPanel) are roadmap-preview surfaces with no live behavior — grep confirms zero consumers outside the barrel. The barrel re-export keeps them one casual import away from showing up in the wallet bento with theatre copy (MPC 3/3, JITO_RATE, dead Deposit/Withdraw).

Removes the four exports from components/wallet/index.ts and adds a FUTURE header comment to each panel pointing back to AUDIT A6 / ROADMAP § 0.A.8. Files stay so the design isn't lost — re-export when each is wired and wrapped in <PreviewBadge>.

* refactor(network): extract solanaConnection singleton

Connection construction lived inside sendTransaction.ts, which made the singleton's existence a side-effect of importing the send service. Three hooks already reached past it. Pulls the RPC_URL resolution + new Connection into src/infrastructure/network/connection.ts; sendTransaction.ts now imports and re-exports it so existing callers don't churn. useNetworkMode + useWalletBalance read from the new module directly. ARCH P2 #1.

* refactor: consolidate sliceNewEvents to single util

The 'detect newly-prepended LXMF events given prev head + prev count' helper had drifted into three copies — LxmfContext (most defensive: handles old anchor falling off the end), MessagesScreen, useMessageNotifications. The notification + screen copies returned [] when the previous head disappeared from the (capped) buffer, silently dropping a window of events on restart.

Pulls the LxmfContext semantics into src/utils/sliceNewEvents.ts and points all three callers at it. ARCH P2 #4.

* refactor: consolidate relTime formatter

WalletScreen + PendingCosigns each carried their own 'X ago' helper. PendingCosigns was missing the days branch so cosign requests over 24h stale would say '36h ago' instead of '1d ago'. Hoists the WalletScreen version (with the days branch) into src/utils/relTime.ts.

Spec said WalletScreen + RecentActivity, but RecentActivity actually uses Intl.DateTimeFormat — the duplication is in PendingCosigns. Logged the drift in WORK_LOG.

* refactor(network): promote useNetworkMode to context

Five active call sites each got their own NetInfo subscription + a fresh MeshRpcAdapter — adapter has a stateful pending-request map and the rpcResponse routing useEffect ran in only one of them, so simultaneous mounts could mute responses for the others (T23).

Lifts the entire hook logic into context/NetworkModeContext.tsx with a Provider mounted in app/_layout.tsx inside LxmfProvider (depends on it) and outside WalletBalanceProvider (consumer). src/hooks/useNetworkMode.ts is now a 4-line re-export shim so existing imports stay valid.

ROADMAP § 2.6 / ARCH P2 #3.
@epicexcelsior epicexcelsior deleted the epic/wallet-ui-port branch May 15, 2026 09:09
Magicred-1 pushed a commit that referenced this pull request May 16, 2026
* refactor(wallet): move dead Send/Swap/Yield panels out of barrel

These four files (WalletTabs, SendPanel, SwapPanel, YieldPanel) are roadmap-preview surfaces with no live behavior — grep confirms zero consumers outside the barrel. The barrel re-export keeps them one casual import away from showing up in the wallet bento with theatre copy (MPC 3/3, JITO_RATE, dead Deposit/Withdraw).

Removes the four exports from components/wallet/index.ts and adds a FUTURE header comment to each panel pointing back to AUDIT A6 / ROADMAP § 0.A.8. Files stay so the design isn't lost — re-export when each is wired and wrapped in <PreviewBadge>.

* refactor(network): extract solanaConnection singleton

Connection construction lived inside sendTransaction.ts, which made the singleton's existence a side-effect of importing the send service. Three hooks already reached past it. Pulls the RPC_URL resolution + new Connection into src/infrastructure/network/connection.ts; sendTransaction.ts now imports and re-exports it so existing callers don't churn. useNetworkMode + useWalletBalance read from the new module directly. ARCH P2 #1.

* refactor: consolidate sliceNewEvents to single util

The 'detect newly-prepended LXMF events given prev head + prev count' helper had drifted into three copies — LxmfContext (most defensive: handles old anchor falling off the end), MessagesScreen, useMessageNotifications. The notification + screen copies returned [] when the previous head disappeared from the (capped) buffer, silently dropping a window of events on restart.

Pulls the LxmfContext semantics into src/utils/sliceNewEvents.ts and points all three callers at it. ARCH P2 #4.

* refactor: consolidate relTime formatter

WalletScreen + PendingCosigns each carried their own 'X ago' helper. PendingCosigns was missing the days branch so cosign requests over 24h stale would say '36h ago' instead of '1d ago'. Hoists the WalletScreen version (with the days branch) into src/utils/relTime.ts.

Spec said WalletScreen + RecentActivity, but RecentActivity actually uses Intl.DateTimeFormat — the duplication is in PendingCosigns. Logged the drift in WORK_LOG.

* refactor(network): promote useNetworkMode to context

Five active call sites each got their own NetInfo subscription + a fresh MeshRpcAdapter — adapter has a stateful pending-request map and the rpcResponse routing useEffect ran in only one of them, so simultaneous mounts could mute responses for the others (T23).

Lifts the entire hook logic into context/NetworkModeContext.tsx with a Provider mounted in app/_layout.tsx inside LxmfProvider (depends on it) and outside WalletBalanceProvider (consumer). src/hooks/useNetworkMode.ts is now a 4-line re-export shim so existing imports stay valid.

ROADMAP § 2.6 / ARCH P2 #3.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants