Add initial project structure and documentation files

- Created .gitignore to exclude sensitive files and directories.
- Added API documentation in API_DOCUMENTATION.md.
- Included deployment instructions in DEPLOYMENT.md.
- Established project structure documentation in PROJECT_STRUCTURE.md.
- Updated README.md with project status and team information.
- Added recommendations and status tracking documents.
- Introduced testing guidelines in TESTING.md.
- Set up CI workflow in .github/workflows/ci.yml.
- Created Dockerfile for backend and frontend setups.
- Added various service and utility files for backend functionality.
- Implemented frontend components and pages for user interface.
- Included mobile app structure and services.
- Established scripts for deployment across multiple chains.
This commit is contained in:
defiQUG
2025-12-03 21:22:31 -08:00
commit 507d9a35b1
261 changed files with 47004 additions and 0 deletions

View File

@@ -0,0 +1,52 @@
import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import { TabNavigator } from './TabNavigator';
import { WalletConnectScreen } from '../screens/WalletConnect';
import { PoolDetailsScreen } from '../screens/PoolDetails';
import { VaultDetailsScreen } from '../screens/VaultDetails';
import { ProposalDetailsScreen } from '../screens/ProposalDetails';
const Stack = createStackNavigator();
export function StackNavigator() {
return (
<Stack.Navigator
screenOptions={{
headerStyle: {
backgroundColor: '#3b82f6',
},
headerTintColor: '#ffffff',
headerTitleStyle: {
fontWeight: 'bold',
},
}}
>
<Stack.Screen
name="WalletConnect"
component={WalletConnectScreen}
options={{ headerShown: false }}
/>
<Stack.Screen
name="Main"
component={TabNavigator}
options={{ headerShown: false }}
/>
<Stack.Screen
name="PoolDetails"
component={PoolDetailsScreen}
options={{ title: 'Pool Details' }}
/>
<Stack.Screen
name="VaultDetails"
component={VaultDetailsScreen}
options={{ title: 'Vault Details' }}
/>
<Stack.Screen
name="ProposalDetails"
component={ProposalDetailsScreen}
options={{ title: 'Proposal Details' }}
/>
</Stack.Navigator>
);
}

View File

@@ -0,0 +1,63 @@
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { DashboardScreen } from '../screens/Dashboard';
import { PoolsScreen } from '../screens/Pools';
import { VaultsScreen } from '../screens/Vaults';
import { TransactionsScreen } from '../screens/Transactions';
import { GovernanceScreen } from '../screens/Governance';
const Tab = createBottomTabNavigator();
export function TabNavigator() {
return (
<Tab.Navigator
screenOptions={{
headerShown: true,
tabBarActiveTintColor: '#3b82f6',
tabBarInactiveTintColor: '#6b7280',
}}
>
<Tab.Screen
name="Dashboard"
component={DashboardScreen}
options={{
tabBarIcon: ({ color }) => <Icon name="home" size={24} color={color} />,
}}
/>
<Tab.Screen
name="Pools"
component={PoolsScreen}
options={{
tabBarIcon: ({ color }) => <Icon name="water" size={24} color={color} />,
}}
/>
<Tab.Screen
name="Vaults"
component={VaultsScreen}
options={{
tabBarIcon: ({ color }) => <Icon name="lock" size={24} color={color} />,
}}
/>
<Tab.Screen
name="Transactions"
component={TransactionsScreen}
options={{
tabBarIcon: ({ color }) => <Icon name="swap-horiz" size={24} color={color} />,
}}
/>
<Tab.Screen
name="Governance"
component={GovernanceScreen}
options={{
tabBarIcon: ({ color }) => <Icon name="gavel" size={24} color={color} />,
}}
/>
</Tab.Navigator>
);
}
// Simple icon component (would use react-native-vector-icons in production)
function Icon({ name, size, color }: { name: string; size: number; color: string }) {
return null; // Placeholder
}

View File

@@ -0,0 +1,23 @@
import { LinkingOptions } from '@react-navigation/native';
export const linking: LinkingOptions<any> = {
prefixes: ['asle://', 'https://asle.app'],
config: {
screens: {
WalletConnect: 'connect',
Main: {
screens: {
Dashboard: 'dashboard',
Pools: 'pools',
Vaults: 'vaults',
Transactions: 'transactions',
Governance: 'governance',
},
},
PoolDetails: 'pool/:poolId',
VaultDetails: 'vault/:vaultId',
ProposalDetails: 'proposal/:proposalId',
},
},
};

View File

@@ -0,0 +1,94 @@
import React, { useState, useEffect } from 'react';
import { View, Text, ScrollView, StyleSheet, RefreshControl } from 'react-native';
import { WalletService } from '../services/wallet';
import axios from 'axios';
const API_URL = process.env.EXPO_PUBLIC_API_URL || 'http://localhost:4000';
export function DashboardScreen() {
const [portfolio, setPortfolio] = useState<any>(null);
const [refreshing, setRefreshing] = useState(false);
const walletService = WalletService.getInstance();
useEffect(() => {
fetchPortfolio();
}, []);
const fetchPortfolio = async () => {
const state = walletService.getState();
if (!state.address) return;
try {
const response = await axios.get(`${API_URL}/api/analytics/portfolio/${state.address}`);
setPortfolio(response.data);
} catch (error) {
console.error('Error fetching portfolio:', error);
}
};
const onRefresh = async () => {
setRefreshing(true);
await fetchPortfolio();
setRefreshing(false);
};
return (
<ScrollView
style={styles.container}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />}
>
<View style={styles.content}>
<Text style={styles.title}>Portfolio</Text>
{portfolio ? (
<View style={styles.card}>
<Text style={styles.label}>Total Value</Text>
<Text style={styles.value}>
{parseFloat(portfolio.totalValue || '0').toLocaleString()}
</Text>
</View>
) : (
<Text style={styles.empty}>No portfolio data</Text>
)}
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f9fafb',
},
content: {
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 16,
color: '#111827',
},
card: {
backgroundColor: '#ffffff',
padding: 16,
borderRadius: 8,
marginBottom: 16,
},
label: {
fontSize: 14,
color: '#6b7280',
marginBottom: 4,
},
value: {
fontSize: 24,
fontWeight: 'bold',
color: '#111827',
},
empty: {
textAlign: 'center',
color: '#6b7280',
marginTop: 32,
},
});

View File

@@ -0,0 +1,87 @@
import React, { useState, useEffect } from 'react';
import { View, Text, ScrollView, TouchableOpacity, StyleSheet } from 'react-native';
import axios from 'axios';
const API_URL = process.env.EXPO_PUBLIC_API_URL || 'http://localhost:4000';
export function GovernanceScreen({ navigation }: any) {
const [proposals, setProposals] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
fetchProposals();
}, []);
const fetchProposals = async () => {
setLoading(true);
try {
// In production, fetch from API
setProposals([]);
} catch (error) {
console.error('Error fetching proposals:', error);
} finally {
setLoading(false);
}
};
return (
<ScrollView style={styles.container}>
<View style={styles.content}>
<Text style={styles.title}>Governance</Text>
{proposals.length === 0 ? (
<Text style={styles.empty}>No proposals yet</Text>
) : (
proposals.map((proposal) => (
<TouchableOpacity
key={proposal.id}
style={styles.card}
onPress={() => navigation.navigate('ProposalDetails', { proposalId: proposal.proposalId })}
>
<Text style={styles.proposalTitle}>{proposal.description}</Text>
<Text style={styles.proposalStatus}>{proposal.status}</Text>
</TouchableOpacity>
))
)}
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f9fafb',
},
content: {
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 16,
color: '#111827',
},
card: {
backgroundColor: '#ffffff',
padding: 16,
borderRadius: 8,
marginBottom: 12,
},
proposalTitle: {
fontSize: 16,
fontWeight: '600',
marginBottom: 4,
color: '#111827',
},
proposalStatus: {
fontSize: 12,
color: '#6b7280',
},
empty: {
textAlign: 'center',
color: '#6b7280',
marginTop: 32,
},
});

View File

@@ -0,0 +1,78 @@
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import axios from 'axios';
const API_URL = process.env.EXPO_PUBLIC_API_URL || 'http://localhost:4000';
export function PoolDetailsScreen({ route, navigation }: any) {
const { poolId } = route.params;
const [pool, setPool] = useState<any>(null);
useEffect(() => {
fetchPoolDetails();
}, [poolId]);
const fetchPoolDetails = async () => {
try {
const response = await axios.get(`${API_URL}/api/pools/${poolId}`);
setPool(response.data);
} catch (error) {
console.error('Error fetching pool details:', error);
}
};
return (
<View style={styles.container}>
{pool ? (
<View style={styles.content}>
<Text style={styles.title}>
{pool.baseToken} / {pool.quoteToken}
</Text>
<View style={styles.card}>
<Text style={styles.label}>Base Reserve</Text>
<Text style={styles.value}>{pool.baseReserve}</Text>
</View>
<View style={styles.card}>
<Text style={styles.label}>Quote Reserve</Text>
<Text style={styles.value}>{pool.quoteReserve}</Text>
</View>
</View>
) : (
<Text>Loading...</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f9fafb',
},
content: {
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 16,
color: '#111827',
},
card: {
backgroundColor: '#ffffff',
padding: 16,
borderRadius: 8,
marginBottom: 12,
},
label: {
fontSize: 14,
color: '#6b7280',
marginBottom: 4,
},
value: {
fontSize: 18,
fontWeight: '600',
color: '#111827',
},
});

View File

@@ -0,0 +1,82 @@
import React, { useState, useEffect } from 'react';
import { View, Text, ScrollView, TouchableOpacity, StyleSheet } from 'react-native';
import axios from 'axios';
const API_URL = process.env.EXPO_PUBLIC_API_URL || 'http://localhost:4000';
export function PoolsScreen({ navigation }: any) {
const [pools, setPools] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
fetchPools();
}, []);
const fetchPools = async () => {
setLoading(true);
try {
const response = await axios.get(`${API_URL}/api/pools`);
setPools(response.data);
} catch (error) {
console.error('Error fetching pools:', error);
} finally {
setLoading(false);
}
};
return (
<ScrollView style={styles.container}>
<View style={styles.content}>
<Text style={styles.title}>Liquidity Pools</Text>
{pools.map((pool) => (
<TouchableOpacity
key={pool.id}
style={styles.card}
onPress={() => navigation.navigate('PoolDetails', { poolId: pool.poolId })}
>
<Text style={styles.poolName}>
{pool.baseToken} / {pool.quoteToken}
</Text>
<Text style={styles.poolValue}>
TVL: {parseFloat(pool.baseReserve || '0').toLocaleString()}
</Text>
</TouchableOpacity>
))}
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f9fafb',
},
content: {
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 16,
color: '#111827',
},
card: {
backgroundColor: '#ffffff',
padding: 16,
borderRadius: 8,
marginBottom: 12,
},
poolName: {
fontSize: 18,
fontWeight: '600',
marginBottom: 4,
color: '#111827',
},
poolValue: {
fontSize: 14,
color: '#6b7280',
},
});

View File

@@ -0,0 +1,122 @@
import React, { useState, useEffect } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { WalletService } from '../services/wallet';
export function ProposalDetailsScreen({ route, navigation }: any) {
const { proposalId } = route.params;
const [proposal, setProposal] = useState<any>(null);
const walletService = WalletService.getInstance();
useEffect(() => {
fetchProposal();
}, [proposalId]);
const fetchProposal = async () => {
// In production, fetch from API
setProposal({
id: proposalId,
description: 'Sample proposal',
status: 'active',
forVotes: '1000',
againstVotes: '500',
});
};
const handleVote = async (support: boolean) => {
const state = walletService.getState();
if (!state.connected) {
alert('Please connect your wallet');
return;
}
// In production, call smart contract
alert(`Vote ${support ? 'for' : 'against'} would be submitted`);
};
return (
<View style={styles.container}>
{proposal ? (
<View style={styles.content}>
<Text style={styles.title}>Proposal {proposalId}</Text>
<Text style={styles.description}>{proposal.description}</Text>
<View style={styles.votes}>
<Text style={styles.voteLabel}>For: {proposal.forVotes}</Text>
<Text style={styles.voteLabel}>Against: {proposal.againstVotes}</Text>
</View>
<View style={styles.actions}>
<TouchableOpacity
style={[styles.button, styles.voteFor]}
onPress={() => handleVote(true)}
>
<Text style={styles.buttonText}>Vote For</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.voteAgainst]}
onPress={() => handleVote(false)}
>
<Text style={styles.buttonText}>Vote Against</Text>
</TouchableOpacity>
</View>
</View>
) : (
<Text>Loading...</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f9fafb',
},
content: {
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 16,
color: '#111827',
},
description: {
fontSize: 16,
color: '#6b7280',
marginBottom: 24,
},
votes: {
backgroundColor: '#ffffff',
padding: 16,
borderRadius: 8,
marginBottom: 24,
},
voteLabel: {
fontSize: 16,
marginBottom: 8,
color: '#111827',
},
actions: {
flexDirection: 'row',
gap: 12,
},
button: {
flex: 1,
padding: 16,
borderRadius: 8,
alignItems: 'center',
},
voteFor: {
backgroundColor: '#10b981',
},
voteAgainst: {
backgroundColor: '#ef4444',
},
buttonText: {
color: '#ffffff',
fontSize: 16,
fontWeight: '600',
},
});

View File

@@ -0,0 +1,83 @@
import React, { useState, useEffect } from 'react';
import { View, Text, ScrollView, StyleSheet } from 'react-native';
import { WalletService } from '../services/wallet';
import axios from 'axios';
const API_URL = process.env.EXPO_PUBLIC_API_URL || 'http://localhost:4000';
export function TransactionsScreen() {
const [transactions, setTransactions] = useState<any[]>([]);
const walletService = WalletService.getInstance();
useEffect(() => {
fetchTransactions();
}, []);
const fetchTransactions = async () => {
const state = walletService.getState();
if (!state.address) return;
try {
// In production, fetch user transactions
setTransactions([]);
} catch (error) {
console.error('Error fetching transactions:', error);
}
};
return (
<ScrollView style={styles.container}>
<View style={styles.content}>
<Text style={styles.title}>Transactions</Text>
{transactions.length === 0 ? (
<Text style={styles.empty}>No transactions yet</Text>
) : (
transactions.map((tx) => (
<View key={tx.id} style={styles.card}>
<Text style={styles.txHash}>{tx.txHash.slice(0, 20)}...</Text>
<Text style={styles.txStatus}>{tx.status}</Text>
</View>
))
)}
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f9fafb',
},
content: {
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 16,
color: '#111827',
},
card: {
backgroundColor: '#ffffff',
padding: 16,
borderRadius: 8,
marginBottom: 12,
},
txHash: {
fontSize: 14,
color: '#111827',
marginBottom: 4,
},
txStatus: {
fontSize: 12,
color: '#6b7280',
},
empty: {
textAlign: 'center',
color: '#6b7280',
marginTop: 32,
},
});

View File

@@ -0,0 +1,76 @@
import React, { useState, useEffect } from 'react';
import { View, Text, StyleSheet } from 'react-native';
import axios from 'axios';
const API_URL = process.env.EXPO_PUBLIC_API_URL || 'http://localhost:4000';
export function VaultDetailsScreen({ route, navigation }: any) {
const { vaultId } = route.params;
const [vault, setVault] = useState<any>(null);
useEffect(() => {
fetchVaultDetails();
}, [vaultId]);
const fetchVaultDetails = async () => {
try {
const response = await axios.get(`${API_URL}/api/vaults/${vaultId}`);
setVault(response.data);
} catch (error) {
console.error('Error fetching vault details:', error);
}
};
return (
<View style={styles.container}>
{vault ? (
<View style={styles.content}>
<Text style={styles.title}>Vault {vaultId}</Text>
<View style={styles.card}>
<Text style={styles.label}>Total Assets</Text>
<Text style={styles.value}>{vault.totalAssets}</Text>
</View>
<View style={styles.card}>
<Text style={styles.label}>Total Supply</Text>
<Text style={styles.value}>{vault.totalSupply}</Text>
</View>
</View>
) : (
<Text>Loading...</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f9fafb',
},
content: {
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 16,
color: '#111827',
},
card: {
backgroundColor: '#ffffff',
padding: 16,
borderRadius: 8,
marginBottom: 12,
},
label: {
fontSize: 14,
color: '#6b7280',
marginBottom: 4,
},
value: {
fontSize: 18,
fontWeight: '600',
color: '#111827',
},
});

View File

@@ -0,0 +1,82 @@
import React, { useState, useEffect } from 'react';
import { View, Text, ScrollView, TouchableOpacity, StyleSheet } from 'react-native';
import axios from 'axios';
const API_URL = process.env.EXPO_PUBLIC_API_URL || 'http://localhost:4000';
export function VaultsScreen({ navigation }: any) {
const [vaults, setVaults] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
useEffect(() => {
fetchVaults();
}, []);
const fetchVaults = async () => {
setLoading(true);
try {
const response = await axios.get(`${API_URL}/api/vaults`);
setVaults(response.data);
} catch (error) {
console.error('Error fetching vaults:', error);
} finally {
setLoading(false);
}
};
return (
<ScrollView style={styles.container}>
<View style={styles.content}>
<Text style={styles.title}>Vaults</Text>
{vaults.map((vault) => (
<TouchableOpacity
key={vault.id}
style={styles.card}
onPress={() => navigation.navigate('VaultDetails', { vaultId: vault.vaultId })}
>
<Text style={styles.vaultName}>
{vault.isMultiAsset ? 'Multi-Asset Vault' : `Vault ${vault.vaultId}`}
</Text>
<Text style={styles.vaultValue}>
Total Assets: {parseFloat(vault.totalAssets || '0').toLocaleString()}
</Text>
</TouchableOpacity>
))}
</View>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f9fafb',
},
content: {
padding: 16,
},
title: {
fontSize: 24,
fontWeight: 'bold',
marginBottom: 16,
color: '#111827',
},
card: {
backgroundColor: '#ffffff',
padding: 16,
borderRadius: 8,
marginBottom: 12,
},
vaultName: {
fontSize: 18,
fontWeight: '600',
marginBottom: 4,
color: '#111827',
},
vaultValue: {
fontSize: 14,
color: '#6b7280',
},
});

View File

@@ -0,0 +1,73 @@
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, StyleSheet } from 'react-native';
import { WalletService } from '../services/wallet';
export function WalletConnectScreen({ navigation }: any) {
const [connecting, setConnecting] = useState(false);
const handleConnect = async () => {
setConnecting(true);
try {
const walletService = WalletService.getInstance();
await walletService.connect();
navigation.replace('Main');
} catch (error) {
console.error('Error connecting wallet:', error);
} finally {
setConnecting(false);
}
};
return (
<View style={styles.container}>
<Text style={styles.title}>ASLE Mobile</Text>
<Text style={styles.subtitle}>Connect your wallet to get started</Text>
<TouchableOpacity
style={styles.button}
onPress={handleConnect}
disabled={connecting}
>
<Text style={styles.buttonText}>
{connecting ? 'Connecting...' : 'Connect Wallet'}
</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
padding: 20,
backgroundColor: '#f9fafb',
},
title: {
fontSize: 32,
fontWeight: 'bold',
marginBottom: 10,
color: '#111827',
},
subtitle: {
fontSize: 16,
color: '#6b7280',
marginBottom: 40,
textAlign: 'center',
},
button: {
backgroundColor: '#3b82f6',
paddingHorizontal: 32,
paddingVertical: 16,
borderRadius: 8,
minWidth: 200,
},
buttonText: {
color: '#ffffff',
fontSize: 16,
fontWeight: '600',
textAlign: 'center',
},
});

View File

@@ -0,0 +1,35 @@
import ReactNativeBiometrics from 'react-native-biometrics';
export class BiometricService {
private static rnBiometrics = new ReactNativeBiometrics();
static async isAvailable(): Promise<boolean> {
try {
const { available } = await this.rnBiometrics.isSensorAvailable();
return available;
} catch (error) {
return false;
}
}
static async authenticate(reason: string = 'Authenticate to access ASLE'): Promise<boolean> {
try {
const { success } = await this.rnBiometrics.simplePrompt({
promptMessage: reason,
});
return success;
} catch (error) {
return false;
}
}
static async createKeys(): Promise<{ publicKey: string; privateKey: string } | null> {
try {
const { publicKey } = await this.rnBiometrics.createKeys();
return { publicKey, privateKey: '' }; // Private key is stored securely
} catch (error) {
return null;
}
}
}

View File

@@ -0,0 +1,86 @@
import { Linking } from 'react-native';
export interface DeepLink {
type: 'transaction' | 'proposal' | 'pool' | 'vault';
id: string;
params?: Record<string, string>;
}
export class DeepLinkingService {
/**
* Parse deep link URL
*/
static parseUrl(url: string): DeepLink | null {
try {
const parsed = new URL(url);
const path = parsed.pathname;
if (path.startsWith('/transaction/')) {
return {
type: 'transaction',
id: path.split('/transaction/')[1],
};
} else if (path.startsWith('/proposal/')) {
return {
type: 'proposal',
id: path.split('/proposal/')[1],
};
} else if (path.startsWith('/pool/')) {
return {
type: 'pool',
id: path.split('/pool/')[1],
};
} else if (path.startsWith('/vault/')) {
return {
type: 'vault',
id: path.split('/vault/')[1],
};
}
return null;
} catch (error) {
return null;
}
}
/**
* Handle incoming deep link
*/
static async handleDeepLink(url: string, navigation: any): Promise<void> {
const link = this.parseUrl(url);
if (!link) return;
switch (link.type) {
case 'transaction':
// Navigate to transaction details
break;
case 'proposal':
navigation.navigate('ProposalDetails', { proposalId: link.id });
break;
case 'pool':
navigation.navigate('PoolDetails', { poolId: link.id });
break;
case 'vault':
navigation.navigate('VaultDetails', { vaultId: link.id });
break;
}
}
/**
* Initialize deep linking listener
*/
static initialize(navigation: any) {
// Handle initial URL
Linking.getInitialURL().then((url) => {
if (url) {
this.handleDeepLink(url, navigation);
}
});
// Handle URL changes
Linking.addEventListener('url', (event) => {
this.handleDeepLink(event.url, navigation);
});
}
}

View File

@@ -0,0 +1,52 @@
import PushNotification from 'react-native-push-notification';
export class NotificationService {
static initialize() {
PushNotification.configure({
onRegister: function (token) {
console.log('TOKEN:', token);
},
onNotification: function (notification) {
console.log('NOTIFICATION:', notification);
},
permissions: {
alert: true,
badge: true,
sound: true,
},
popInitialNotification: true,
requestPermissions: true,
});
PushNotification.createChannel(
{
channelId: 'asle-default',
channelName: 'ASLE Notifications',
channelDescription: 'Notifications for ASLE platform',
playSound: true,
soundName: 'default',
importance: 4,
vibrate: true,
},
(created) => console.log(`Channel created: ${created}`)
);
}
static scheduleLocalNotification(title: string, message: string, date: Date) {
PushNotification.localNotificationSchedule({
channelId: 'asle-default',
title,
message,
date,
});
}
static sendLocalNotification(title: string, message: string) {
PushNotification.localNotification({
channelId: 'asle-default',
title,
message,
});
}
}

View File

@@ -0,0 +1,74 @@
import AsyncStorage from '@react-native-async-storage/async-storage';
export class OfflineService {
private static CACHE_PREFIX = 'asle_cache_';
private static QUEUE_KEY = 'asle_transaction_queue';
/**
* Cache data for offline access
*/
static async cacheData(key: string, data: any): Promise<void> {
try {
await AsyncStorage.setItem(
`${this.CACHE_PREFIX}${key}`,
JSON.stringify({ data, timestamp: Date.now() })
);
} catch (error) {
console.error('Error caching data:', error);
}
}
/**
* Get cached data
*/
static async getCachedData(key: string): Promise<any | null> {
try {
const cached = await AsyncStorage.getItem(`${this.CACHE_PREFIX}${key}`);
if (!cached) return null;
const { data, timestamp } = JSON.parse(cached);
// Check if cache is still valid (24 hours)
if (Date.now() - timestamp > 24 * 60 * 60 * 1000) {
await AsyncStorage.removeItem(`${this.CACHE_PREFIX}${key}`);
return null;
}
return data;
} catch (error) {
return null;
}
}
/**
* Queue transaction for when online
*/
static async queueTransaction(transaction: any): Promise<void> {
try {
const queue = await this.getTransactionQueue();
queue.push({ ...transaction, queuedAt: Date.now() });
await AsyncStorage.setItem(this.QUEUE_KEY, JSON.stringify(queue));
} catch (error) {
console.error('Error queueing transaction:', error);
}
}
/**
* Get transaction queue
*/
static async getTransactionQueue(): Promise<any[]> {
try {
const queue = await AsyncStorage.getItem(this.QUEUE_KEY);
return queue ? JSON.parse(queue) : [];
} catch (error) {
return [];
}
}
/**
* Clear transaction queue
*/
static async clearTransactionQueue(): Promise<void> {
await AsyncStorage.removeItem(this.QUEUE_KEY);
}
}

View File

@@ -0,0 +1,67 @@
import { createConfig, http } from '@wagmi/core';
import { mainnet, polygon, arbitrum, optimism, bsc, avalanche, base } from '@wagmi/core/chains';
import { injected, metaMask } from '@wagmi/core/connectors';
export const wagmiConfig = createConfig({
chains: [mainnet, polygon, arbitrum, optimism, bsc, avalanche, base],
connectors: [injected(), metaMask()],
transports: {
[mainnet.id]: http(),
[polygon.id]: http(),
[arbitrum.id]: http(),
[optimism.id]: http(),
[bsc.id]: http(),
[avalanche.id]: http(),
[base.id]: http(),
},
});
export interface WalletState {
address: string | null;
chainId: number | null;
connected: boolean;
}
export class WalletService {
private static instance: WalletService;
private state: WalletState = {
address: null,
chainId: null,
connected: false,
};
static getInstance(): WalletService {
if (!WalletService.instance) {
WalletService.instance = new WalletService();
}
return WalletService.instance;
}
async connect(): Promise<WalletState> {
// In production, this would use WalletConnect or similar
// For now, return mock state
this.state = {
address: '0x1234567890123456789012345678901234567890',
chainId: 1,
connected: true,
};
return this.state;
}
async disconnect(): Promise<void> {
this.state = {
address: null,
chainId: null,
connected: false,
};
}
getState(): WalletState {
return this.state;
}
async switchChain(chainId: number): Promise<void> {
this.state.chainId = chainId;
}
}

View File

@@ -0,0 +1,16 @@
export const colors = {
primary: '#3b82f6',
secondary: '#10b981',
accent: '#f59e0b',
error: '#ef4444',
warning: '#f59e0b',
success: '#10b981',
info: '#3b82f6',
background: '#f9fafb',
surface: '#ffffff',
text: '#111827',
textSecondary: '#6b7280',
border: '#e5e7eb',
divider: '#e5e7eb',
};

View File

@@ -0,0 +1,9 @@
export const spacing = {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32,
xxl: 48,
};

View File

@@ -0,0 +1,33 @@
export const typography = {
h1: {
fontSize: 32,
fontWeight: '700' as const,
lineHeight: 40,
},
h2: {
fontSize: 24,
fontWeight: '600' as const,
lineHeight: 32,
},
h3: {
fontSize: 20,
fontWeight: '600' as const,
lineHeight: 28,
},
body: {
fontSize: 16,
fontWeight: '400' as const,
lineHeight: 24,
},
bodySmall: {
fontSize: 14,
fontWeight: '400' as const,
lineHeight: 20,
},
caption: {
fontSize: 12,
fontWeight: '400' as const,
lineHeight: 16,
},
};