Skip to content

Latest commit

 

History

History
925 lines (778 loc) · 26.2 KB

File metadata and controls

925 lines (778 loc) · 26.2 KB

Web3Unilag Onboard - Developer Documentation

Table of Contents

  1. Project Overview
  2. Architecture
  3. Dependencies
  4. Authentication & Wallet Setup
  5. In-App Wallet Implementation
  6. Gamification System
  7. Quest Engine
  8. Chain Integration
  9. Security Considerations
  10. Development Setup

Project Overview

Web3Unilag Onboard is a React Native mobile application that provides gamified Web3 onboarding for African university students. The app features chain-agnostic learning tracks, quest-based progression, and integrated wallet functionality.

Core Features (Must-Have)

  • Chain selector & goal-based onboarding tracks
  • Gamified progression & XP system
  • In-app wallet (Solana & SUI)
  • Social login (Google, Apple, EVM wallet)
  • Smart contract-validated quests
  • Ecosystem map & dApp directory
  • Mini-game engine for events
  • Chain dashboard for partners
  • Mentorship matching

Architecture

┌─────────────────────────────────────────┐
│              Frontend (React Native)    │
├─────────────────────────────────────────┤
│           Authentication Layer          │
│              (Privy SDK)                │
├─────────────────────────────────────────┤
│            Wallet Management            │
│        (In-App + External Wallets)     │
├─────────────────────────────────────────┤
│             State Management            │
│            (Redux Toolkit)              │
├─────────────────────────────────────────┤
│              API Layer                  │
│           (Supabase Client)             │
└─────────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────┐
│           Backend (Supabase)            │
├─────────────────────────────────────────┤
│          Database (PostgreSQL)          │
├─────────────────────────────────────────┤
│             Edge Functions              │
│        (Quest Validation, Rewards)      │
├─────────────────────────────────────────┤
│            Real-time Features           │
│         (Leaderboards, Chat)            │
└─────────────────────────────────────────┘
                      │
                      ▼
┌─────────────────────────────────────────┐
│           Blockchain Networks           │
│         (Solana, SUI, Ethereum)         │
└─────────────────────────────────────────┘

Dependencies

Core Dependencies

{
  "dependencies": {
    // React Native Core
    "react": "18.2.0",
    "react-native": "0.73.0",
    
    // Navigation
    "@react-navigation/native": "^6.1.9",
    "@react-navigation/stack": "^6.3.20",
    "@react-navigation/bottom-tabs": "^6.5.11",
    "react-native-screens": "^3.27.0",
    "react-native-safe-area-context": "^4.7.4",
    
    // Authentication
    "@privy-io/react-auth": "^1.52.0",
    "@privy-io/expo": "^0.4.0",
    
    // Backend
    "@supabase/supabase-js": "^2.38.4",
    
    // State Management
    "@reduxjs/toolkit": "^1.9.7",
    "react-redux": "^8.1.3",
    
    // Blockchain SDKs
    "@solana/web3.js": "^1.87.6",
    "@mysten/sui.js": "^0.50.1",
    "ethers": "^6.8.1",
    
    // Crypto & Security
    "react-native-keychain": "^8.1.3",
    "expo-crypto": "^12.6.0",
    "expo-secure-store": "^12.5.0",
    "tweetnacl": "^1.0.3",
    "ed25519-hd-key": "^1.3.0",
    "bip39": "^3.1.0",
    
    // UI & Animations
    "react-native-reanimated": "^3.6.0",
    "react-native-gesture-handler": "^2.14.0",
    "lottie-react-native": "^6.4.1",
    "react-native-svg": "^14.0.0",
    
    // Utils
    "react-native-mmkv": "^2.10.2",
    "react-native-device-info": "^10.11.0",
    "react-native-vector-icons": "^10.0.2"
  },
  "devDependencies": {
    "@types/react": "^18.2.45",
    "@types/react-native": "^0.72.8",
    "typescript": "^5.3.3"
  }
}

Platform-Specific Dependencies

# iOS
pod 'RNCAsyncStorage', :path => '../node_modules/@react-native-async-storage/async-storage'

# Android
implementation 'androidx.biometric:biometric:1.1.0'

Authentication & Wallet Setup

Privy Configuration

// src/config/privy.ts
import { PrivyProvider } from '@privy-io/react-auth';

export const privyConfig = {
  appId: process.env.EXPO_PUBLIC_PRIVY_APP_ID!,
  config: {
    loginMethods: ['google', 'apple', 'wallet'],
    appearance: {
      theme: 'dark',
      accentColor: '#6366F1',
      logo: 'https://your-logo-url.com/logo.png',
    },
    embeddedWallets: {
      createOnLogin: 'users-without-wallets',
      requireUserPasswordOnCreate: false,
    },
    externalWallets: {
      metamask: true,
      walletConnect: true,
      coinbaseWallet: true,
    },
  },
};

Authentication Hook

// src/hooks/useAuth.ts
import { usePrivy } from '@privy-io/react-auth';
import { useEffect } from 'react';
import { useAppDispatch } from '../store/hooks';
import { setUser, clearUser } from '../store/slices/authSlice';

export const useAuth = () => {
  const { user, login, logout, authenticated } = usePrivy();
  const dispatch = useAppDispatch();

  useEffect(() => {
    if (authenticated && user) {
      dispatch(setUser({
        id: user.id,
        email: user.email?.address,
        walletAddress: user.wallet?.address,
        linkedAccounts: user.linkedAccounts,
      }));
    } else {
      dispatch(clearUser());
    }
  }, [authenticated, user, dispatch]);

  return {
    user,
    login,
    logout,
    authenticated,
  };
};

In-App Wallet Implementation

Wallet Generation & Storage

// src/services/WalletService.ts
import * as SecureStore from 'expo-secure-store';
import { generateMnemonic, mnemonicToSeedSync } from 'bip39';
import { derivePath } from 'ed25519-hd-key';
import { Keypair } from '@solana/web3.js';
import { Ed25519Keypair } from '@mysten/sui.js/keypairs/ed25519';
import nacl from 'tweetnacl';

export class WalletService {
  private static MNEMONIC_KEY = 'user_mnemonic';
  private static ENCRYPTION_KEY = 'wallet_encryption_key';

  // Generate new wallet for user
  static async generateWallet(userId: string): Promise<{
    solanaKeypair: Keypair;
    suiKeypair: Ed25519Keypair;
    mnemonic: string;
  }> {
    try {
      // Generate mnemonic
      const mnemonic = generateMnemonic();
      const seed = mnemonicToSeedSync(mnemonic);

      // Derive Solana keypair (BIP44 path: m/44'/501'/0'/0')
      const solanaPath = "m/44'/501'/0'/0'";
      const solanaDerived = derivePath(solanaPath, seed.toString('hex'));
      const solanaKeypair = Keypair.fromSeed(solanaDerived.key);

      // Derive SUI keypair (BIP44 path: m/44'/784'/0'/0'/0')
      const suiPath = "m/44'/784'/0'/0'/0'";
      const suiDerived = derivePath(suiPath, seed.toString('hex'));
      const suiKeypair = Ed25519Keypair.fromSeed(suiDerived.key);

      // Encrypt and store mnemonic
      await this.storeMnemonic(userId, mnemonic);

      return {
        solanaKeypair,
        suiKeypair,
        mnemonic,
      };
    } catch (error) {
      throw new Error(`Failed to generate wallet: ${error}`);
    }
  }

  // Store encrypted mnemonic
  private static async storeMnemonic(userId: string, mnemonic: string): Promise<void> {
    const key = `${this.MNEMONIC_KEY}_${userId}`;
    await SecureStore.setItemAsync(key, mnemonic, {
      requireAuthentication: true,
      authenticationPrompt: 'Authenticate to access your wallet',
    });
  }

  // Retrieve and decrypt mnemonic
  static async getMnemonic(userId: string): Promise<string | null> {
    try {
      const key = `${this.MNEMONIC_KEY}_${userId}`;
      return await SecureStore.getItemAsync(key, {
        requireAuthentication: true,
        authenticationPrompt: 'Authenticate to access your wallet',
      });
    } catch (error) {
      console.error('Failed to retrieve mnemonic:', error);
      return null;
    }
  }

  // Restore wallet from mnemonic
  static async restoreWallet(userId: string): Promise<{
    solanaKeypair: Keypair;
    suiKeypair: Ed25519Keypair;
  } | null> {
    try {
      const mnemonic = await this.getMnemonic(userId);
      if (!mnemonic) return null;

      const seed = mnemonicToSeedSync(mnemonic);

      // Restore Solana keypair
      const solanaPath = "m/44'/501'/0'/0'";
      const solanaDerived = derivePath(solanaPath, seed.toString('hex'));
      const solanaKeypair = Keypair.fromSeed(solanaDerived.key);

      // Restore SUI keypair
      const suiPath = "m/44'/784'/0'/0'/0'";
      const suiDerived = derivePath(suiPath, seed.toString('hex'));
      const suiKeypair = Ed25519Keypair.fromSeed(suiDerived.key);

      return {
        solanaKeypair,
        suiKeypair,
      };
    } catch (error) {
      console.error('Failed to restore wallet:', error);
      return null;
    }
  }

  // Get wallet addresses
  static async getWalletAddresses(userId: string): Promise<{
    solana: string;
    sui: string;
  } | null> {
    const wallet = await this.restoreWallet(userId);
    if (!wallet) return null;

    return {
      solana: wallet.solanaKeypair.publicKey.toString(),
      sui: wallet.suiKeypair.getPublicKey().toSuiAddress(),
    };
  }
}

Transaction Signing

// src/services/TransactionService.ts
import { Connection, PublicKey, Transaction } from '@solana/web3.js';
import { SuiClient, getFullnodeUrl } from '@mysten/sui.js/client';
import { TransactionBlock } from '@mysten/sui.js/transactions';
import { WalletService } from './WalletService';

export class TransactionService {
  private static solanaConnection = new Connection(getFullnodeUrl('devnet'));
  private static suiClient = new SuiClient({ url: getFullnodeUrl('devnet') });

  // Sign Solana transaction
  static async signSolanaTransaction(
    userId: string,
    transaction: Transaction
  ): Promise<string> {
    const wallet = await WalletService.restoreWallet(userId);
    if (!wallet) throw new Error('Wallet not found');

    // Sign transaction
    transaction.sign(wallet.solanaKeypair);
    
    // Send transaction
    const signature = await this.solanaConnection.sendRawTransaction(
      transaction.serialize()
    );
    
    return signature;
  }

  // Sign SUI transaction
  static async signSuiTransaction(
    userId: string,
    transactionBlock: TransactionBlock
  ): Promise<string> {
    const wallet = await WalletService.restoreWallet(userId);
    if (!wallet) throw new Error('Wallet not found');

    // Execute transaction
    const result = await this.suiClient.signAndExecuteTransactionBlock({
      signer: wallet.suiKeypair,
      transactionBlock,
    });
    
    return result.digest;
  }
}

Gamification System

XP & Level Management

// src/services/GameificationService.ts
import { supabase } from '../config/supabase';

export interface UserProgress {
  id: string;
  user_id: string;
  total_xp: number;
  level: number;
  current_streak: number;
  badges: string[];
  completed_quests: string[];
  chain_progress: Record<string, number>;
}

export class GamificationService {
  // Calculate level from XP
  static calculateLevel(xp: number): number {
    return Math.floor(Math.sqrt(xp / 100)) + 1;
  }

  // Calculate XP needed for next level
  static xpForNextLevel(currentLevel: number): number {
    return (currentLevel * currentLevel) * 100;
  }

  // Award XP to user
  static async awardXP(userId: string, amount: number, reason: string): Promise<void> {
    const { data: progress, error } = await supabase
      .from('user_progress')
      .select('*')
      .eq('user_id', userId)
      .single();

    if (error) throw error;

    const newXP = progress.total_xp + amount;
    const newLevel = this.calculateLevel(newXP);
    
    // Check for level up
    const leveledUp = newLevel > progress.level;

    await supabase
      .from('user_progress')
      .update({
        total_xp: newXP,
        level: newLevel,
      })
      .eq('user_id', userId);

    // Record XP transaction
    await supabase
      .from('xp_transactions')
      .insert({
        user_id: userId,
        amount,
        reason,
        timestamp: new Date().toISOString(),
      });

    // Handle level up rewards
    if (leveledUp) {
      await this.handleLevelUp(userId, newLevel);
    }
  }

  // Handle level up rewards
  private static async handleLevelUp(userId: string, newLevel: number): Promise<void> {
    // Award level up badge
    await this.awardBadge(userId, `level_${newLevel}`);
    
    // Send level up notification
    await supabase
      .from('notifications')
      .insert({
        user_id: userId,
        type: 'level_up',
        title: 'Level Up!',
        message: `Congratulations! You've reached level ${newLevel}`,
        data: { level: newLevel },
      });
  }

  // Award badge to user
  static async awardBadge(userId: string, badgeId: string): Promise<void> {
    const { data: progress } = await supabase
      .from('user_progress')
      .select('badges')
      .eq('user_id', userId)
      .single();

    if (!progress?.badges.includes(badgeId)) {
      const updatedBadges = [...progress.badges, badgeId];
      
      await supabase
        .from('user_progress')
        .update({ badges: updatedBadges })
        .eq('user_id', userId);
    }
  }
}

Quest System

// src/services/QuestService.ts
import { supabase } from '../config/supabase';
import { GamificationService } from './GamificationService';

export interface Quest {
  id: string;
  title: string;
  description: string;
  chain: string;
  type: 'transaction' | 'quiz' | 'social' | 'content';
  requirements: Record<string, any>;
  rewards: {
    xp: number;
    badges?: string[];
    nfts?: string[];
  };
  validation_contract?: string;
  is_active: boolean;
}

export class QuestService {
  // Get available quests for user
  static async getAvailableQuests(userId: string, chain?: string): Promise<Quest[]> {
    let query = supabase
      .from('quests')
      .select('*')
      .eq('is_active', true);

    if (chain) {
      query = query.eq('chain', chain);
    }

    // Exclude completed quests
    const { data: completedQuests } = await supabase
      .from('quest_completions')
      .select('quest_id')
      .eq('user_id', userId);

    const completedQuestIds = completedQuests?.map(q => q.quest_id) || [];

    const { data: quests, error } = await query.not('id', 'in', `(${completedQuestIds.join(',')})`);

    if (error) throw error;
    return quests || [];
  }

  // Validate quest completion
  static async validateQuest(userId: string, questId: string, proof: any): Promise<boolean> {
    const { data: quest, error } = await supabase
      .from('quests')
      .select('*')
      .eq('id', questId)
      .single();

    if (error) throw error;

    let isValid = false;

    switch (quest.type) {
      case 'transaction':
        isValid = await this.validateTransactionQuest(quest, proof);
        break;
      case 'quiz':
        isValid = await this.validateQuizQuest(quest, proof);
        break;
      case 'social':
        isValid = await this.validateSocialQuest(quest, proof);
        break;
      case 'content':
        isValid = await this.validateContentQuest(quest, proof);
        break;
    }

    if (isValid) {
      await this.completeQuest(userId, questId, quest.rewards);
    }

    return isValid;
  }

  // Complete quest and award rewards
  private static async completeQuest(
    userId: string, 
    questId: string, 
    rewards: Quest['rewards']
  ): Promise<void> {
    // Record completion
    await supabase
      .from('quest_completions')
      .insert({
        user_id: userId,
        quest_id: questId,
        completed_at: new Date().toISOString(),
      });

    // Award XP
    await GamificationService.awardXP(userId, rewards.xp, `Quest completion: ${questId}`);

    // Award badges
    if (rewards.badges) {
      for (const badge of rewards.badges) {
        await GamificationService.awardBadge(userId, badge);
      }
    }

    // Mint NFTs (if applicable)
    if (rewards.nfts) {
      await this.mintRewardNFTs(userId, rewards.nfts);
    }
  }

  // Validate transaction quest
  private static async validateTransactionQuest(quest: Quest, proof: any): Promise<boolean> {
    // Implementation depends on specific requirements
    // This would typically involve checking blockchain transactions
    return true; // Placeholder
  }

  // Other validation methods...
  private static async validateQuizQuest(quest: Quest, proof: any): Promise<boolean> {
    // Validate quiz answers
    return true; // Placeholder
  }

  private static async validateSocialQuest(quest: Quest, proof: any): Promise<boolean> {
    // Validate social media interactions
    return true; // Placeholder
  }

  private static async validateContentQuest(quest: Quest, proof: any): Promise<boolean> {
    // Validate content creation
    return true; // Placeholder
  }

  private static async mintRewardNFTs(userId: string, nftIds: string[]): Promise<void> {
    // Implement NFT minting logic
  }
}

Chain Integration

Solana Integration

// src/services/SolanaService.ts
import { 
  Connection, 
  PublicKey, 
  Transaction,
  SystemProgram,
  LAMPORTS_PER_SOL
} from '@solana/web3.js';
import { WalletService } from './WalletService';

export class SolanaService {
  private static connection = new Connection('https://api.devnet.solana.com');

  // Get balance
  static async getBalance(address: string): Promise<number> {
    const publicKey = new PublicKey(address);
    const balance = await this.connection.getBalance(publicKey);
    return balance / LAMPORTS_PER_SOL;
  }

  // Send SOL
  static async sendSOL(
    userId: string,
    toAddress: string,
    amount: number
  ): Promise<string> {
    const wallet = await WalletService.restoreWallet(userId);
    if (!wallet) throw new Error('Wallet not found');

    const transaction = new Transaction().add(
      SystemProgram.transfer({
        fromPubkey: wallet.solanaKeypair.publicKey,
        toPubkey: new PublicKey(toAddress),
        lamports: amount * LAMPORTS_PER_SOL,
      })
    );

    const signature = await this.connection.sendTransaction(
      transaction,
      [wallet.solanaKeypair]
    );

    return signature;
  }

  // Get transaction history
  static async getTransactionHistory(address: string): Promise<any[]> {
    const publicKey = new PublicKey(address);
    const signatures = await this.connection.getSignaturesForAddress(publicKey);
    
    const transactions = await Promise.all(
      signatures.map(sig => 
        this.connection.getTransaction(sig.signature, {
          maxSupportedTransactionVersion: 0
        })
      )
    );

    return transactions.filter(tx => tx !== null);
  }
}

SUI Integration

// src/services/SuiService.ts
import { SuiClient, getFullnodeUrl } from '@mysten/sui.js/client';
import { TransactionBlock } from '@mysten/sui.js/transactions';
import { WalletService } from './WalletService';

export class SuiService {
  private static client = new SuiClient({ url: getFullnodeUrl('devnet') });

  // Get balance
  static async getBalance(address: string): Promise<string> {
    const balance = await this.client.getBalance({
      owner: address,
    });
    return balance.totalBalance;
  }

  // Send SUI
  static async sendSUI(
    userId: string,
    toAddress: string,
    amount: number
  ): Promise<string> {
    const wallet = await WalletService.restoreWallet(userId);
    if (!wallet) throw new Error('Wallet not found');

    const txb = new TransactionBlock();
    const [coin] = txb.splitCoins(txb.gas, [txb.pure(amount)]);
    txb.transferObjects([coin], txb.pure(toAddress));

    const result = await this.client.signAndExecuteTransactionBlock({
      signer: wallet.suiKeypair,
      transactionBlock: txb,
    });

    return result.digest;
  }

  // Get owned objects
  static async getOwnedObjects(address: string): Promise<any[]> {
    const objects = await this.client.getOwnedObjects({
      owner: address,
      options: {
        showType: true,
        showContent: true,
        showDisplay: true,
      },
    });

    return objects.data;
  }
}

Security Considerations

Key Security Measures

  1. Secure Storage

    • Use expo-secure-store for mnemonic storage
    • Require biometric authentication for wallet access
    • Never store private keys in plain text
  2. Encryption

    • Encrypt sensitive data before storage
    • Use device-specific encryption keys
    • Implement key rotation for long-term storage
  3. Network Security

    • Use HTTPS for all API communications
    • Implement certificate pinning
    • Validate all server responses
  4. Input Validation

    • Sanitize all user inputs
    • Validate transaction parameters
    • Implement rate limiting

Security Implementation

// src/utils/Security.ts
import * as Crypto from 'expo-crypto';
import * as SecureStore from 'expo-secure-store';

export class SecurityUtils {
  // Generate device-specific encryption key
  static async generateDeviceKey(): Promise<string> {
    const randomBytes = await Crypto.getRandomBytesAsync(32);
    return Array.from(randomBytes)
      .map(b => b.toString(16).padStart(2, '0'))
      .join('');
  }

  // Encrypt data
  static async encryptData(data: string, key: string): Promise<string> {
    // Implementation using a crypto library
    // This is a simplified example
    return Buffer.from(data).toString('base64');
  }

  // Decrypt data
  static async decryptData(encryptedData: string, key: string): Promise<string> {
    // Implementation using a crypto library
    // This is a simplified example
    return Buffer.from(encryptedData, 'base64').toString();
  }

  // Validate transaction parameters
  static validateTransactionParams(params: any): boolean {
    // Implement validation logic
    if (!params.to || !params.amount) return false;
    if (params.amount <= 0) return false;
    return true;
  }
}

Development Setup

Environment Configuration

# .env
EXPO_PUBLIC_PRIVY_APP_ID=your_privy_app_id
EXPO_PUBLIC_SUPABASE_URL=your_supabase_url
EXPO_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
EXPO_PUBLIC_ENVIRONMENT=development

Development Scripts

{
  "scripts": {
    "start": "expo start",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web",
    "build:android": "eas build --platform android",
    "build:ios": "eas build --platform ios",
    "test": "jest",
    "type-check": "tsc --noEmit",
    "lint": "eslint . --ext .ts,.tsx"
  }
}

Project Structure

src/
├── components/          # Reusable UI components
│   ├── common/         # Common components
│   ├── gamification/   # XP, badges, progress bars
│   └── wallet/         # Wallet-related components
├── screens/            # App screens
│   ├── auth/          # Authentication screens
│   ├── onboarding/    # Onboarding flow
│   ├── quests/        # Quest screens
│   └── profile/       # User profile
├── services/          # Business logic services
├── hooks/             # Custom React hooks
├── store/             # Redux store configuration
├── utils/             # Utility functions
├── types/             # TypeScript type definitions
└── config/            # App configuration

Database Schema (Supabase)

-- Users table (extends Privy user data)
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  privy_id TEXT UNIQUE NOT NULL,
  email TEXT,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- User progress
CREATE TABLE user_progress (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES users(id) ON DELETE CASCADE,
  total_xp INTEGER DEFAULT 0,
  level INTEGER DEFAULT 1,
  current_streak INTEGER DEFAULT 0,
  badges TEXT[] DEFAULT '{}',
  completed_quests TEXT[] DEFAULT '{}',
  chain_progress JSONB DEFAULT '{}',
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Quests
CREATE TABLE quests (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  title TEXT NOT NULL,
  description TEXT,
  chain TEXT NOT NULL,
  type TEXT NOT NULL CHECK (type IN ('transaction', 'quiz', 'social', 'content')),
  requirements JSONB DEFAULT '{}',
  rewards JSONB DEFAULT '{}',
  validation_contract TEXT,
  is_active BOOLEAN DEFAULT true,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

-- Quest completions
CREATE TABLE quest_completions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES users(id) ON DELETE CASCADE,
  quest_id UUID REFERENCES quests(id) ON DELETE CASCADE,
  completed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  UNIQUE(user_id, quest_id)
);

-- XP transactions
CREATE TABLE xp_transactions (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES users(id) ON DELETE CASCADE,
  amount INTEGER NOT NULL,
  reason TEXT,
  timestamp TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

This comprehensive documentation provides the foundation for building the Web3Unilag Onboard mobile application with all the specified features, security considerations, and development best practices.