mirror of
https://github.com/arkorty/B.Tech-Project-III.git
synced 2026-04-19 12:41:48 +00:00
init
This commit is contained in:
98
dmtp/client/hooks/useAuth.ts
Normal file
98
dmtp/client/hooks/useAuth.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
'use client';
|
||||
|
||||
import { AuthService } from '@/lib/auth';
|
||||
import { useWalletConnection } from './useWalletConnection';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { api } from '@/lib/api';
|
||||
|
||||
export function useAuth() {
|
||||
const { address, isConnected, signMessage } = useWalletConnection();
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const [isAuthenticating, setIsAuthenticating] = useState(false);
|
||||
const [authError, setAuthError] = useState<string | null>(null);
|
||||
|
||||
// Check authentication status on mount and when wallet changes
|
||||
useEffect(() => {
|
||||
const checkAuth = () => {
|
||||
const authenticated = AuthService.isAuthenticated();
|
||||
setIsAuthenticated(authenticated);
|
||||
|
||||
// If wallet is connected but not authenticated, show warning
|
||||
if (isConnected && !authenticated) {
|
||||
console.warn('⚠️ Wallet connected but not authenticated');
|
||||
}
|
||||
};
|
||||
|
||||
checkAuth();
|
||||
|
||||
// Check auth status every 30 seconds
|
||||
const interval = setInterval(checkAuth, 30000);
|
||||
return () => clearInterval(interval);
|
||||
}, [isConnected, address]);
|
||||
|
||||
const authenticate = async () => {
|
||||
if (!isConnected || !address) {
|
||||
setAuthError('Please connect your wallet first');
|
||||
return false;
|
||||
}
|
||||
|
||||
setIsAuthenticating(true);
|
||||
setAuthError(null);
|
||||
|
||||
try {
|
||||
// Clear any old auth before registering to avoid sending expired credentials
|
||||
const oldAuthExists = AuthService.isAuthenticated();
|
||||
if (!oldAuthExists) {
|
||||
AuthService.clearAuth();
|
||||
}
|
||||
|
||||
// Step 1: Register user if needed
|
||||
try {
|
||||
await api.users.register({
|
||||
walletAddress: address,
|
||||
role: 'worker',
|
||||
});
|
||||
console.log('✅ User registered successfully');
|
||||
} catch (error: any) {
|
||||
// User might already exist, which is fine
|
||||
if (error.response?.status !== 409) {
|
||||
console.log('Registration note:', error.response?.data?.message || error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Generate and sign authentication message
|
||||
const timestamp = Date.now();
|
||||
const message = AuthService.generateAuthMessage(address, timestamp);
|
||||
const signature = await signMessage(message);
|
||||
|
||||
// Step 3: Store authentication
|
||||
AuthService.storeAuth(address, signature, message, timestamp);
|
||||
setIsAuthenticated(true);
|
||||
|
||||
console.log('✅ Authentication successful');
|
||||
return true;
|
||||
} catch (error: any) {
|
||||
console.error('Authentication error:', error);
|
||||
setAuthError(error.message || 'Authentication failed');
|
||||
AuthService.clearAuth();
|
||||
setIsAuthenticated(false);
|
||||
return false;
|
||||
} finally {
|
||||
setIsAuthenticating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const clearAuth = () => {
|
||||
AuthService.clearAuth();
|
||||
setIsAuthenticated(false);
|
||||
setAuthError(null);
|
||||
};
|
||||
|
||||
return {
|
||||
isAuthenticated,
|
||||
isAuthenticating,
|
||||
authError,
|
||||
authenticate,
|
||||
clearAuth,
|
||||
};
|
||||
}
|
||||
38
dmtp/client/hooks/useCUSDBalance.ts
Normal file
38
dmtp/client/hooks/useCUSDBalance.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
'use client';
|
||||
|
||||
import { getCUSDContract, getProvider } from '@/lib/contracts';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { ethers } from 'ethers';
|
||||
|
||||
export function useCUSDBalance(address: string | null) {
|
||||
return useQuery({
|
||||
queryKey: ['cusd-balance', address],
|
||||
queryFn: async () => {
|
||||
if (!address) return '0';
|
||||
|
||||
const provider = getProvider();
|
||||
const cUSDContract = getCUSDContract(provider);
|
||||
|
||||
const balance = await cUSDContract.balanceOf(address);
|
||||
return ethers.formatEther(balance);
|
||||
},
|
||||
enabled: !!address,
|
||||
refetchInterval: 10000, // Refetch every 10 seconds
|
||||
});
|
||||
}
|
||||
|
||||
export function useCUSDAllowance(owner: string | null, spender: string) {
|
||||
return useQuery({
|
||||
queryKey: ['cusd-allowance', owner, spender],
|
||||
queryFn: async () => {
|
||||
if (!owner) return '0';
|
||||
|
||||
const provider = getProvider();
|
||||
const cUSDContract = getCUSDContract(provider);
|
||||
|
||||
const allowance = await cUSDContract.allowance(owner, spender);
|
||||
return ethers.formatEther(allowance);
|
||||
},
|
||||
enabled: !!owner,
|
||||
});
|
||||
}
|
||||
123
dmtp/client/hooks/useTaskContract.ts
Normal file
123
dmtp/client/hooks/useTaskContract.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
'use client';
|
||||
|
||||
import { parseErrorMessage } from '@/lib/celo';
|
||||
import { getCUSDContract, getTaskEscrowContract } from '@/lib/contracts';
|
||||
import { ethers } from 'ethers';
|
||||
import { useState } from 'react';
|
||||
import { useWalletConnection } from './useWalletConnection';
|
||||
|
||||
export function useTaskContract() {
|
||||
const { signer, address } = useWalletConnection();
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
/**
|
||||
* Create task on blockchain
|
||||
*/
|
||||
const createTask = async (paymentAmount: number, durationInDays: number) => {
|
||||
if (!signer || !address) {
|
||||
throw new Error('Wallet not connected');
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const contractAddress = process.env.NEXT_PUBLIC_CONTRACT_ADDRESS;
|
||||
if (!contractAddress) {
|
||||
throw new Error('Contract address not configured');
|
||||
}
|
||||
|
||||
// Step 1: Approve cUSD spending
|
||||
console.log('📝 Approving cUSD spending...');
|
||||
const cUSDContract = getCUSDContract(signer);
|
||||
const amount = ethers.parseEther(paymentAmount.toString());
|
||||
|
||||
const approveTx = await cUSDContract.approve(contractAddress, amount);
|
||||
await approveTx.wait();
|
||||
console.log('✅ cUSD approved');
|
||||
|
||||
// Step 2: Create task
|
||||
console.log('📝 Creating task on blockchain...');
|
||||
const taskContract = getTaskEscrowContract(signer);
|
||||
const createTx = await taskContract.createTask(amount, durationInDays);
|
||||
|
||||
console.log('⏳ Waiting for confirmation...');
|
||||
const receipt = await createTx.wait();
|
||||
|
||||
// Parse event to get taskId
|
||||
const event = receipt.logs.find((log: any) => {
|
||||
try {
|
||||
const parsed = taskContract.interface.parseLog(log);
|
||||
return parsed?.name === 'TaskCreated';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
let taskId = 0;
|
||||
if (event) {
|
||||
const parsedEvent = taskContract.interface.parseLog(event);
|
||||
taskId = Number(parsedEvent?.args[0]);
|
||||
}
|
||||
|
||||
console.log('✅ Task created! Task ID:', taskId);
|
||||
|
||||
setIsLoading(false);
|
||||
return {
|
||||
taskId,
|
||||
txHash: receipt.hash,
|
||||
};
|
||||
} catch (err: any) {
|
||||
const errorMessage = parseErrorMessage(err);
|
||||
setError(errorMessage);
|
||||
setIsLoading(false);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Check task status on blockchain
|
||||
*/
|
||||
const checkTaskStatus = async (taskId: number) => {
|
||||
if (!signer) {
|
||||
throw new Error('Wallet not connected');
|
||||
}
|
||||
|
||||
try {
|
||||
const taskContract = getTaskEscrowContract(signer);
|
||||
const task = await taskContract.getTask(taskId);
|
||||
|
||||
return {
|
||||
taskId: Number(task.taskId),
|
||||
requester: task.requester,
|
||||
worker: task.worker,
|
||||
paymentAmount: ethers.formatEther(task.paymentAmount),
|
||||
status: Number(task.status),
|
||||
createdAt: Number(task.createdAt),
|
||||
expiresAt: Number(task.expiresAt),
|
||||
};
|
||||
} catch (err: any) {
|
||||
const errorMessage = parseErrorMessage(err);
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Get current task counter
|
||||
*/
|
||||
const getTaskCounter = async () => {
|
||||
const provider = new ethers.JsonRpcProvider(process.env.NEXT_PUBLIC_CELO_RPC_URL);
|
||||
const taskContract = getTaskEscrowContract(provider);
|
||||
const counter = await taskContract.taskCounter();
|
||||
return Number(counter);
|
||||
};
|
||||
|
||||
return {
|
||||
createTask,
|
||||
checkTaskStatus,
|
||||
getTaskCounter,
|
||||
isLoading,
|
||||
error,
|
||||
};
|
||||
}
|
||||
52
dmtp/client/hooks/useTransactions.ts
Normal file
52
dmtp/client/hooks/useTransactions.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
'use client';
|
||||
|
||||
import { create } from 'zustand';
|
||||
|
||||
export interface Transaction {
|
||||
hash: string;
|
||||
status: 'pending' | 'success' | 'failed';
|
||||
description: string;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
interface TransactionState {
|
||||
transactions: Transaction[];
|
||||
currentTx: Transaction | null;
|
||||
|
||||
addTransaction: (tx: Omit<Transaction, 'timestamp'>) => void;
|
||||
updateTransaction: (hash: string, updates: Partial<Transaction>) => void;
|
||||
clearTransactions: () => void;
|
||||
}
|
||||
|
||||
export const useTransactions = create<TransactionState>((set) => ({
|
||||
transactions: [],
|
||||
currentTx: null,
|
||||
|
||||
addTransaction: (tx) => {
|
||||
const transaction: Transaction = {
|
||||
...tx,
|
||||
timestamp: Date.now(),
|
||||
};
|
||||
|
||||
set((state) => ({
|
||||
transactions: [transaction, ...state.transactions],
|
||||
currentTx: transaction,
|
||||
}));
|
||||
},
|
||||
|
||||
updateTransaction: (hash, updates) => {
|
||||
set((state) => ({
|
||||
transactions: state.transactions.map((tx) =>
|
||||
tx.hash === hash ? { ...tx, ...updates } : tx
|
||||
),
|
||||
currentTx:
|
||||
state.currentTx?.hash === hash
|
||||
? { ...state.currentTx, ...updates }
|
||||
: state.currentTx,
|
||||
}));
|
||||
},
|
||||
|
||||
clearTransactions: () => {
|
||||
set({ transactions: [], currentTx: null });
|
||||
},
|
||||
}));
|
||||
5
dmtp/client/hooks/useWallet.ts
Normal file
5
dmtp/client/hooks/useWallet.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
'use client';
|
||||
|
||||
// Re-export useWalletConnection as useWallet for backwards compatibility
|
||||
export { useWalletConnection as useWallet } from './useWalletConnection';
|
||||
|
||||
189
dmtp/client/hooks/useWalletConnection.ts
Normal file
189
dmtp/client/hooks/useWalletConnection.ts
Normal file
@@ -0,0 +1,189 @@
|
||||
'use client';
|
||||
|
||||
import { getCurrentNetwork, parseErrorMessage } from '@/lib/celo';
|
||||
import { getWalletProvider, isWalletAvailable } from '@/lib/minipay';
|
||||
import { ethers } from 'ethers';
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface WalletState {
|
||||
address: string | null;
|
||||
chainId: number | null;
|
||||
isConnected: boolean;
|
||||
isConnecting: boolean;
|
||||
provider: ethers.BrowserProvider | null;
|
||||
signer: ethers.Signer | null;
|
||||
error: string | null;
|
||||
walletType: string | null;
|
||||
|
||||
// Actions
|
||||
connect: () => Promise<void>;
|
||||
disconnect: () => void;
|
||||
switchNetwork: () => Promise<void>;
|
||||
signMessage: (message: string) => Promise<string>;
|
||||
initialize: () => Promise<void>; // Add this
|
||||
}
|
||||
|
||||
export const useWalletConnection = create<WalletState>((set, get) => ({
|
||||
address: null,
|
||||
chainId: null,
|
||||
isConnected: false,
|
||||
isConnecting: false,
|
||||
provider: null,
|
||||
signer: null,
|
||||
error: null,
|
||||
walletType: null,
|
||||
|
||||
initialize: async () => {
|
||||
// Check if wallet was previously connected
|
||||
if (typeof window === 'undefined') return;
|
||||
|
||||
if (!isWalletAvailable()) return;
|
||||
|
||||
try {
|
||||
const provider = new ethers.BrowserProvider(window.ethereum);
|
||||
const accounts = await provider.listAccounts();
|
||||
|
||||
if (accounts.length > 0) {
|
||||
// Auto-connect if previously connected
|
||||
await get().connect();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Not previously connected');
|
||||
}
|
||||
},
|
||||
|
||||
connect: async () => {
|
||||
set({ isConnecting: true, error: null });
|
||||
|
||||
try {
|
||||
if (!isWalletAvailable()) {
|
||||
throw new Error('No wallet detected. Please install MiniPay or MetaMask.');
|
||||
}
|
||||
|
||||
const provider = new ethers.BrowserProvider(window.ethereum);
|
||||
|
||||
const accounts = await provider.send('eth_requestAccounts', []);
|
||||
const address = accounts[0];
|
||||
|
||||
const network = await provider.getNetwork();
|
||||
const chainId = Number(network.chainId);
|
||||
|
||||
const expectedChainId = getCurrentNetwork().chainId;
|
||||
if (chainId !== expectedChainId) {
|
||||
await get().switchNetwork();
|
||||
return;
|
||||
}
|
||||
|
||||
const signer = await provider.getSigner();
|
||||
const walletType = getWalletProvider();
|
||||
|
||||
set({
|
||||
address,
|
||||
chainId,
|
||||
provider,
|
||||
signer,
|
||||
isConnected: true,
|
||||
isConnecting: false,
|
||||
walletType,
|
||||
});
|
||||
|
||||
console.log(`✅ Connected to ${walletType}:`, address);
|
||||
} catch (error: any) {
|
||||
const errorMessage = parseErrorMessage(error);
|
||||
set({
|
||||
error: errorMessage,
|
||||
isConnecting: false,
|
||||
});
|
||||
console.error('Wallet connection error:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
disconnect: () => {
|
||||
set({
|
||||
address: null,
|
||||
chainId: null,
|
||||
provider: null,
|
||||
signer: null,
|
||||
isConnected: false,
|
||||
walletType: null,
|
||||
});
|
||||
console.log('🔌 Wallet disconnected');
|
||||
},
|
||||
|
||||
switchNetwork: async () => {
|
||||
try {
|
||||
const targetNetwork = getCurrentNetwork();
|
||||
|
||||
try {
|
||||
await window.ethereum.request({
|
||||
method: 'wallet_switchEthereumChain',
|
||||
params: [{ chainId: `0x${targetNetwork.chainId.toString(16)}` }],
|
||||
});
|
||||
} catch (switchError: any) {
|
||||
if (switchError.code === 4902) {
|
||||
await window.ethereum.request({
|
||||
method: 'wallet_addEthereumChain',
|
||||
params: [
|
||||
{
|
||||
chainId: `0x${targetNetwork.chainId.toString(16)}`,
|
||||
chainName: targetNetwork.name,
|
||||
nativeCurrency: {
|
||||
name: 'CELO',
|
||||
symbol: 'CELO',
|
||||
decimals: 18,
|
||||
},
|
||||
rpcUrls: [targetNetwork.rpcUrl],
|
||||
blockExplorerUrls: [targetNetwork.blockExplorer],
|
||||
},
|
||||
],
|
||||
});
|
||||
} else {
|
||||
throw switchError;
|
||||
}
|
||||
}
|
||||
|
||||
await get().connect();
|
||||
} catch (error: any) {
|
||||
const errorMessage = parseErrorMessage(error);
|
||||
set({ error: errorMessage });
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
signMessage: async (message: string) => {
|
||||
const { signer } = get();
|
||||
|
||||
if (!signer) {
|
||||
throw new Error('Wallet not connected');
|
||||
}
|
||||
|
||||
try {
|
||||
const signature = await signer.signMessage(message);
|
||||
return signature;
|
||||
} catch (error: any) {
|
||||
throw new Error(parseErrorMessage(error));
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
// Initialize on client side
|
||||
if (typeof window !== 'undefined') {
|
||||
// Initialize wallet connection on mount
|
||||
useWalletConnection.getState().initialize();
|
||||
|
||||
// Listen for account changes
|
||||
if (window.ethereum) {
|
||||
window.ethereum.on('accountsChanged', (accounts: string[]) => {
|
||||
if (accounts.length === 0) {
|
||||
useWalletConnection.getState().disconnect();
|
||||
} else {
|
||||
useWalletConnection.getState().connect();
|
||||
}
|
||||
});
|
||||
|
||||
window.ethereum.on('chainChanged', () => {
|
||||
window.location.reload();
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user