
Complete rewrite from Python/FastAPI to Node.js stack: Features implemented: - User authentication with JWT tokens and role-based access (DEVELOPER/BUYER) - Blockchain wallet linking and management with Ethereum integration - Carbon project creation and management for developers - Marketplace for browsing and purchasing carbon offsets - Transaction tracking with blockchain integration - Comprehensive input validation with Joi - Advanced security with Helmet, CORS, and rate limiting - Error handling and logging middleware - Health check endpoint with service monitoring Technical stack: - Node.js with Express.js and TypeScript - Prisma ORM with SQLite database - Web3.js and Ethers.js for blockchain integration - JWT authentication with bcrypt password hashing - Comprehensive validation and security middleware - Production-ready error handling and logging Database schema: - Users with wallet linking capabilities - Carbon projects with verification status - Carbon offsets with blockchain token tracking - Transactions with confirmation details Environment variables required: - JWT_SECRET (required) - DATABASE_URL (optional, defaults to SQLite) - BLOCKCHAIN_RPC_URL (optional, defaults to localhost) - NODE_ENV, PORT, CORS_ORIGIN (optional) Run with: npm install && npm run db:generate && npm run db:migrate && npm run dev
179 lines
4.4 KiB
TypeScript
179 lines
4.4 KiB
TypeScript
import { prisma } from '@/utils/database';
|
|
import { blockchainService } from './blockchain';
|
|
import { WalletResponse, WalletInfo } from '@/types';
|
|
|
|
export class WalletService {
|
|
public async linkWallet(userId: number, walletAddress: string): Promise<WalletResponse> {
|
|
try {
|
|
// Validate wallet address format
|
|
if (!blockchainService.validateWalletAddress(walletAddress)) {
|
|
return {
|
|
success: false,
|
|
walletLinked: false,
|
|
message: 'Invalid wallet address format'
|
|
};
|
|
}
|
|
|
|
// Check if wallet is already linked to another user
|
|
const existingUser = await prisma.user.findFirst({
|
|
where: {
|
|
walletAddress,
|
|
id: { not: userId }
|
|
}
|
|
});
|
|
|
|
if (existingUser) {
|
|
return {
|
|
success: false,
|
|
walletLinked: false,
|
|
message: 'Wallet address is already linked to another account'
|
|
};
|
|
}
|
|
|
|
// Get user
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: userId }
|
|
});
|
|
|
|
if (!user) {
|
|
return {
|
|
success: false,
|
|
walletLinked: false,
|
|
message: 'User not found'
|
|
};
|
|
}
|
|
|
|
// Update user wallet information
|
|
await prisma.user.update({
|
|
where: { id: userId },
|
|
data: {
|
|
walletAddress,
|
|
walletPublicKey: walletAddress // In Ethereum, address is derived from public key
|
|
}
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
walletLinked: true,
|
|
walletAddress,
|
|
message: 'Wallet linked successfully'
|
|
};
|
|
} catch (error) {
|
|
console.error('Error linking wallet:', error);
|
|
return {
|
|
success: false,
|
|
walletLinked: false,
|
|
message: `Database error: ${error}`
|
|
};
|
|
}
|
|
}
|
|
|
|
public async unlinkWallet(userId: number): Promise<WalletResponse> {
|
|
try {
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: userId }
|
|
});
|
|
|
|
if (!user) {
|
|
return {
|
|
success: false,
|
|
walletLinked: false,
|
|
message: 'User not found'
|
|
};
|
|
}
|
|
|
|
if (!user.walletAddress) {
|
|
return {
|
|
success: false,
|
|
walletLinked: false,
|
|
message: 'No wallet linked to this account'
|
|
};
|
|
}
|
|
|
|
// Clear wallet information
|
|
await prisma.user.update({
|
|
where: { id: userId },
|
|
data: {
|
|
walletAddress: null,
|
|
walletPublicKey: null
|
|
}
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
walletLinked: false,
|
|
message: 'Wallet unlinked successfully'
|
|
};
|
|
} catch (error) {
|
|
console.error('Error unlinking wallet:', error);
|
|
return {
|
|
success: false,
|
|
walletLinked: false,
|
|
message: `Database error: ${error}`
|
|
};
|
|
}
|
|
}
|
|
|
|
public async getWalletInfo(userId: number): Promise<WalletResponse> {
|
|
try {
|
|
const user = await prisma.user.findUnique({
|
|
where: { id: userId }
|
|
});
|
|
|
|
if (!user) {
|
|
return {
|
|
success: false,
|
|
walletLinked: false,
|
|
message: 'User not found'
|
|
};
|
|
}
|
|
|
|
if (!user.walletAddress) {
|
|
return {
|
|
success: true,
|
|
walletLinked: false,
|
|
walletAddress: undefined,
|
|
balance: undefined
|
|
};
|
|
}
|
|
|
|
// Get wallet balance from blockchain
|
|
const balance = await blockchainService.getWalletBalance(user.walletAddress);
|
|
|
|
return {
|
|
success: true,
|
|
walletLinked: true,
|
|
walletAddress: user.walletAddress,
|
|
balance: balance || undefined
|
|
};
|
|
} catch (error) {
|
|
console.error('Error getting wallet info:', error);
|
|
return {
|
|
success: false,
|
|
walletLinked: false,
|
|
message: `Error retrieving wallet information: ${error}`
|
|
};
|
|
}
|
|
}
|
|
|
|
public generateTestWallet(): { success: boolean; message: string; walletData?: WalletInfo; warning?: string } {
|
|
try {
|
|
const walletData = blockchainService.generateWallet();
|
|
|
|
return {
|
|
success: true,
|
|
message: 'Test wallet generated successfully',
|
|
walletData,
|
|
warning: 'This is for testing only. Keep private key secure!'
|
|
};
|
|
} catch (error) {
|
|
console.error('Error generating test wallet:', error);
|
|
return {
|
|
success: false,
|
|
message: `Failed to generate test wallet: ${error}`
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
export const walletService = new WalletService(); |