Up and Running

Understanding the project structure, layout, provider hierarchy, and data management.

Created: 1/17/2026

Project Structure

The native app follows a standard Expo Router structure. Here is a breakdown of the key files and directories to help you navigate and contribute.

apps/native/app/_layout.tsx

This file is the root layout of the application. It initializes the app, manages the splash screen, and sets up the Zustand store providers for state management.

export default function RootLayout() {
  return (
    <PrivyProvider
      appId={Config.PRIVY_APP_ID}
      clientId={Config.PRIVY_CLIENT_ID}
    >
      <ThemeStoreProvider>
        <BiometricStoreProvider>
          <NotificationsStoreProvider>
            <AuthStoreProvider>
              <DataStoreProvider>
                <NetworkStoreProvider>
                  <SafeAreaProvider>
                    <RootNavigator />
                    <Toast />
                  </SafeAreaProvider>
                </NetworkStoreProvider>
              </DataStoreProvider>
            </AuthStoreProvider>
          </NotificationsStoreProvider>
        </BiometricStoreProvider>
      </ThemeStoreProvider>
    </PrivyProvider>
  );
}

Store Provider Hierarchy

The Zustand store providers are nested in a specific order to ensure proper initialization and functionality:

  1. PrivyProvider: Wraps the application to provide authentication and wallet functionality
  2. ThemeStoreProvider: Initializes the theme store with device color scheme detection and automatic theme switching
  3. BiometricStoreProvider: Manages biometric authentication state and device capability detection
  4. NotificationsStoreProvider: Handles push notification permissions and token registration
  5. AuthStoreProvider: Manages authentication state, wallet management, and biometric authentication
  6. DataStoreProvider: Handles database connections, encryption, and sync status
  7. NetworkStoreProvider: Monitors network connectivity and internet reachability
  8. SafeAreaProvider: Handles safe area insets for notches and system UI
  9. RootNavigator: Contains the Expo Router stack with authentication guards
  10. Toast: Global toast notification system
graph TD
    PrivyProvider --> ThemeStoreProvider
    ThemeStoreProvider --> BiometricStoreProvider
    BiometricStoreProvider --> NotificationsStoreProvider
    NotificationsStoreProvider --> AuthStoreProvider
    AuthStoreProvider --> DataStoreProvider
    DataStoreProvider --> NetworkStoreProvider
    NetworkStoreProvider --> SafeAreaProvider
    SafeAreaProvider --> RootNavigator

State Management with Zustand

The app uses Zustand for lightweight, scalable state management across all features:

Key Benefits

  • Type-Safe: Full TypeScript support with proper type inference
  • Minimal Boilerplate: No reducers, actions, or dispatch needed
  • Reactive: Automatic UI updates when state changes
  • Persistent: Stores persist data across app restarts where needed
  • Composable: Stores can subscribe to each other and share state
  • DevTools Ready: Built-in support for debugging and time-travel

Store Architecture

Each store is organized by domain and provides:

  • State: Current values and computed properties
  • Actions: Functions to update state
  • Selectors: Memoized computed values for performance
  • Effects: Side effects and async operations

Available Stores

Theme Store (useThemeStore)

Manages color schemes, theme colors, and UI theming:

import { useThemeColors, useIsDark, useToggleColorScheme } from '@/stores';

function MyComponent() {
  const colors = useThemeColors();
  const isDark = useIsDark();
  const toggleTheme = useToggleColorScheme();

  return (
    <View style={{ backgroundColor: colors.background }}>
      <Text style={{ color: colors.foreground }}>
        Theme: {isDark ? 'Dark' : 'Light'}
      </Text>
      <Button title="Toggle Theme" onPress={toggleTheme} />
    </View>
  );
}

Auth Store (useAuthStore)

Handles wallets, authentication, and biometric features:

import { useMovementWallets, useActiveWallet, useAuthReady } from '@/stores';

function WalletComponent() {
  const wallets = useMovementWallets();
  const activeWallet = useActiveWallet();
  const isReady = useAuthReady();

  if (!isReady) return <LoadingSpinner />;

  return (
    <View>
      {wallets.map(wallet => (
        <WalletCard key={wallet.address} wallet={wallet} />
      ))}
    </View>
  );
}

Data Store (useDataStore)

Manages database connections and synchronization:

import { useDatabaseReady, useSyncStatus } from '@/stores';

function DataComponent() {
  const isReady = useDatabaseReady();
  const syncStatus = useSyncStatus();

  return (
    <View>
      <Text>Database: {isReady ? 'Ready' : 'Initializing'}</Text>
      <Text>Sync: {syncStatus}</Text>
    </View>
  );
}

Network Store (useNetworkStore)

Tracks connectivity and network status:

import { useIsConnected, useIsOnline } from '@/stores';

function NetworkComponent() {
  const isConnected = useIsConnected();
  const isOnline = useIsOnline();

  return (
    <View>
      <Text>Connected: {isConnected ? 'Yes' : 'No'}</Text>
      <Text>Online: {isOnline ? 'Yes' : 'No'}</Text>
    </View>
  );
}

Biometric Store (useBiometricStore)

Manages biometric authentication (Touch ID, Face ID) and device capabilities:

import { useBiometricEnabled, useBiometricAvailable, useBiometricEnrolled } from '@/stores';

function BiometricSettings() {
  const isEnabled = useBiometricEnabled();
  const isAvailable = useBiometricAvailable();
  const isEnrolled = useBiometricEnrolled();

  return (
    <View>
      <Text>Biometrics Available: {isAvailable ? 'Yes' : 'No'}</Text>
      <Text>Biometrics Enrolled: {isEnrolled ? 'Yes' : 'No'}</Text>
      <Text>Biometrics Enabled: {isEnabled ? 'Yes' : 'No'}</Text>
    </View>
  );
}

Notifications Store (useNotificationsStore)

Handles push notification permissions and backend registration:

import { useNotificationsEnabled, useNotificationsGranted, usePushToken } from '@/stores';

function NotificationsSettings() {
  const isEnabled = useNotificationsEnabled();
  const isGranted = useNotificationsGranted();
  const pushToken = usePushToken();

  return (
    <View>
      <Text>Notifications Enabled: {isEnabled ? 'Yes' : 'No'}</Text>
      <Text>Permissions Granted: {isGranted ? 'Yes' : 'No'}</Text>
      <Text>Push Token: {pushToken ? 'Registered' : 'Not registered'}</Text>
    </View>
  );
}

Toast Store (useToastStore)

Manages global notifications and alerts:

import { useShowToast } from '@/stores';

function NotificationButton() {
  const showToast = useShowToast();

  const handlePress = () => {
    showToast({
      message: 'Operation completed!',
      type: 'success'
    });
  };

  return <Button title="Show Success" onPress={handlePress} />;
}

Data Management with TanStack Query

The app uses TanStack Query (React Query) for efficient data fetching and state management:

Key Benefits

  • Smart Caching: Blockchain data is cached to reduce redundant network requests
  • Background Refetching: Data automatically refreshes in the background to stay up-to-date
  • Optimistic Updates: UI updates immediately while changes sync with the blockchain
  • Persistent Cache: Query cache is persisted to AsyncStorage for offline access
  • Automatic Retries: Failed requests are automatically retried with exponential backoff

Configuration

The QueryProvider in apps/native/src/providers/query-provider.tsx configures:

const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      staleTime: 1000 * 60 * 5, // 5 minutes
      gcTime: 1000 * 60 * 60 * 24, // 24 hours
      retry: 2,
    },
  },
});

This ensures blockchain data is cached for 5 minutes before being considered stale, and persisted for 24 hours.

Theming System

The app uses a centralized theming system that provides consistent styling and automatic light/dark mode support.

Basic Usage with useThemeColors()

The simplest way to access theme colors in your components:

import { View, Text, StyleSheet } from 'react-native';
import { useThemeColors } from '@/stores';

export default function MyComponent() {
  const colors = useThemeColors();

  const styles = StyleSheet.create({
    container: {
      backgroundColor: colors.background,
      borderColor: colors.border,
      borderWidth: 1,
    },
    text: {
      color: colors.foreground,
    },
  });

  return (
    <View style={styles.container}>
      <Text style={styles.text}>Hello World</Text>
    </View>
  );
}

Advanced Usage with Theme Selectors

Access full theme state including theme switching:

import { View, Text, Button, StyleSheet } from 'react-native';
import { useThemeColors, useIsDark, useToggleColorScheme } from '@/stores';

export default function SettingsScreen() {
  const colors = useThemeColors();
  const isDark = useIsDark();
  const toggleTheme = useToggleColorScheme();

  const styles = StyleSheet.create({
    container: {
      backgroundColor: colors.background,
    },
    text: {
      color: colors.foreground,
    },
  });

  return (
    <View style={styles.container}>
      <Text style={styles.text}>
        Current theme: {isDark ? 'Dark' : 'Light'}
      </Text>
      <Button title="Toggle Theme" onPress={toggleTheme} />
    </View>
  );
}

Available Colors

The theme provides semantic color tokens:

  • Base: background, foreground
  • Components: card, cardForeground, popover, popoverForeground
  • Actions: primary, primaryForeground, secondary, secondaryForeground
  • States: muted, mutedForeground, accent, accentForeground, destructive, destructiveForeground
  • UI Elements: border, input, ring
  • Charts: chart1 through chart5

Design Tokens

Import spacing, radius, fonts, and font sizes for consistent styling:

import { useThemeColors } from '@/stores';
import { Spacing, Radius, Fonts, FontSizes } from '@/constants/Colors';

const colors = useThemeColors();
const styles = StyleSheet.create({
  card: {
    backgroundColor: colors.card,
    borderRadius: Radius['2xl'],      // 8
    padding: Spacing.md,               // 16
    margin: Spacing.sm,                // 8
  },
  title: {
    color: colors.cardForeground,
    fontFamily: Fonts.semibold,        // 'Inter_600SemiBold'
    fontSize: FontSizes.xl,            // 20
    marginBottom: Spacing.sm,
  },
});

Available Design Tokens:

  • Spacing: xs (4), sm (8), md (16), lg (24), xl (32), 2xl (48), 3xl (64)
  • Radius: sm (0), md (0), lg (0), xl (4), 2xl (8), 3xl (12), 4xl (16)
  • Fonts: regular, medium, semibold
  • FontSizes: xs (12), sm (14), md (16), lg (18), xl (20), 2xl (24), 3xl (30), 4xl (36)

Best Practices

  1. Always use useColors() or useTheme() - Never hardcode colors
  2. Create StyleSheet inside component - Styles need to update when theme changes
  3. Use semantic color names - Use colors.primary instead of specific color values
  4. Leverage design tokens - Use Spacing, Radius, Fonts for consistency
  5. Test both themes - Always verify your UI works in light and dark mode

Working with Assets

Creating New Assets

  1. Add Images: Place your image files (PNG, JPG, SVG) into apps/native/assets/images/.
  2. Usage: Import them directly in your components or reference them via require.
import { Image } from 'expo-image';

<Image source={require('@/assets/images/my-icon.png')} style={{ width: 50, height: 50 }} />

For icons, we typically use the assets/images directory or an icon library compatible with React Native.

Blockchain Interaction Hooks

The app provides several hooks for interacting with Move contracts:

useGig() - Standard Gig Management

Located in apps/native/src/hooks/use-gig.ts, this hook provides functions for creating, applying to, and managing standard gigs.

Key Functions:

  • createGig(description, budget, contractIpfsHash) - Create a new gig
  • applyBySigning(gigAddress) - Apply to a gig by signing
  • assignApplicant(gigAddress, applicantAddress) - Assign a worker
  • completeGig(gigAddress) - Complete and release payment
  • getAllGigs() - Fetch all gigs from the contract
  • getUserRating(userAddress) - Get a user's average rating

Example:

const { createGig, getAllGigs } = useGig();

// Create a new gig
await createGig("Build a website", 1000, "ipfs://...");

// Fetch all gigs
const gigs = await getAllGigs();

useLocationBasedGig() - Location-Based Gigs

Located in apps/native/src/hooks/use-location-based-gig.ts, this hook manages location-based gig opportunities.

Key Functions:

  • createLocationGig(description, totalAmount, maxOpportunities, lat, long) - Create a location gig
  • claimCompletion(gigAddress, evidenceHash) - Claim completion of a location task
  • getAllLocationGigs() - Fetch all location-based gigs

Example:

const { createLocationGig, getAllLocationGigs } = useLocationBasedGig();

// Create a location-based gig
await createLocationGig(
  "Deliver package",
  500,
  10,
  -33.8688,
  151.2093
);

// Fetch all location gigs
const locationGigs = await getAllLocationGigs();

These hooks abstract the complexity of blockchain interactions and provide a clean API for the UI layer.