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:
35
mobile/src/services/biometric.ts
Normal file
35
mobile/src/services/biometric.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
86
mobile/src/services/deep-linking.ts
Normal file
86
mobile/src/services/deep-linking.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
52
mobile/src/services/notifications.ts
Normal file
52
mobile/src/services/notifications.ts
Normal 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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
74
mobile/src/services/offline.ts
Normal file
74
mobile/src/services/offline.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
|
||||
67
mobile/src/services/wallet.ts
Normal file
67
mobile/src/services/wallet.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user