Backend Architecture
Overview
The Karpous backend is built on Encore.dev, a TypeScript-first backend framework that provides built-in infrastructure management, API generation, and observability.
Framework: Encore.dev
Why Encore?
| Feature | Benefit |
|---|---|
| TypeScript-first | Type safety across entire stack |
| Built-in Infrastructure | Database, caching, pub/sub included |
| Auto-generated APIs | OpenAPI/REST documentation |
| Observability | Built-in monitoring dashboard |
| Cloud Deployment | One-click deployment via Encore Cloud |
Key Features Used
- API Framework: Automatic endpoint generation
- Pub/Sub: Native event system for async processing
- CronJobs: Scheduled task execution
- Secrets: Secure configuration management
- Gateway: Centralized authentication
Service Modules
Module Structure
Each module follows a consistent pattern:
modules/
├── [module]/
│ ├── [module].ts # API endpoints
│ ├── types.ts # TypeScript interfaces
│ ├── services/
│ │ └── [module].service.ts
│ ├── repositories/
│ │ └── [module].repository.ts
│ └── utils/ # Helper functions
Core Modules
Authentication Module
Handles multiple authentication methods:
| Method | Description |
|---|---|
| OTP | Email/phone one-time password |
| Web3 | Wallet signature verification (Solana) |
| Google OAuth | Firebase Admin integration |
| Telegram | Bot and Mini App authentication |
Endpoints:
POST /auth/signup // Create account with OTP
POST /auth/login // OTP passwordless login
POST /auth/otp/generate // Generate OTP code
POST /auth/otp/verify // Verify OTP code
POST /auth/google/login // Google OAuth
POST /auth/telegram/login // Telegram authentication
POST /auth/refresh // Refresh JWT token
PUT /auth/complete-profile // Update profile
JWT Token Flow:
User Module
Manages user profiles and data:
GET /user/profile // Get user profile
PUT /user/profile // Update profile
GET /user/wallets // Get wallet addresses
GET /user/stats // Get user statistics
Deposit Module
Handles deposit processing:
GET /deposit/address // Get deposit address
GET /deposit/history // Get deposit history
POST /deposit/webhook // Process deposit webhook
Integration with Smart Contracts:
Withdrawal Module
Manages withdrawal requests:
POST /withdrawal/request // Request withdrawal
GET /withdrawal/history // Get withdrawal history
GET /withdrawal/status // Check withdrawal status
Yield Module
Processes yield calculations and distributions:
GET /yield/claimable // Get claimable yield
POST /yield/claim // Request yield claim
GET /yield/history // Get claim history
Weekly Yield Processing:
Referral Module
Multi-level referral system:
GET /referral/info // Get referral info
GET /referral/code // Get referral code
GET /referral/leaderboard // Top referrers
GET /referral/earnings // Referral earnings
Referral Structure:
Quest Module (Gamification)
User engagement through quests:
GET /quests // Get available quests
GET /quests/progress // Get quest progress
POST /quests/claim // Claim quest rewards
Notification Module
User notifications:
GET /notifications // Get notifications
PUT /notifications/read // Mark as read
POST /notifications/push // Send push notification
Email Module
Queue-based email processing:
Database Architecture
PostgreSQL (Supabase)
Connection Configuration:
{
max: 20, // Max connections
idleTimeout: 20, // Seconds
connectTimeout: 10, // Seconds
maxLifetime: 1800 // 30 minutes
}
Core Tables
| Table | Purpose |
|---|---|
users | User accounts |
user_stats | Points, rewards tracking |
user_wallets | Wallet addresses |
deposits | Deposit records |
withdrawals | Withdrawal requests |
positions | Earn positions |
ftoken_holdings | fToken balances |
yield_claims | Yield claim history |
quests | Quest definitions |
user_quests | User quest progress |
notifications | User notifications |
email_queue | Pending emails |
referrals | Referral relationships |
ORM: Drizzle
Schema Definition:
export const users = pgTable('users', {
id: uuid('id').primaryKey().defaultRandom(),
email: varchar('email', { length: 255 }),
walletAddress: varchar('wallet_address', { length: 255 }),
username: varchar('username', { length: 50 }),
referralCode: varchar('referral_code', { length: 20 }),
createdAt: timestamp('created_at').defaultNow(),
updatedAt: timestamp('updated_at').defaultNow(),
});
Event System (Pub/Sub)
Event-Driven Architecture
Events
| Event | Trigger | Subscribers |
|---|---|---|
userCreated | New user signup | Stats, Referral |
depositCompleted | Deposit confirmed | Stats, Notification |
withdrawalCompleted | Withdrawal fulfilled | Stats, Notification |
questClaimed | Quest reward claimed | Stats, Referral |
yieldClaimed | Yield claimed | Stats, Notification |
Implementation
// Publishing an event
import { events } from '../shared/pubsub';
await events.publish({
type: 'userCreated',
userId: user.id,
referralCode: user.referralCode,
timestamp: Date.now(),
});
// Subscribing to events
export const handleUserCreated = events.subscribe(
'userCreated',
async (event) => {
await statsService.initializeUserStats(event.userId);
await referralService.processReferral(event.userId, event.referralCode);
}
);
Scheduled Jobs (CronJobs)
| Job | Schedule | Purpose |
|---|---|---|
processEmailQueue | Every 1 min | Send pending emails |
retryFailedEmails | Every 5 min | Retry failed emails |
calculateYields | Weekly | Calculate user yields |
syncBlockchainEvents | Every 1 min | Sync on-chain events |
cleanupExpiredSessions | Daily | Remove old sessions |
API Design
Response Format
interface ApiResponse<T> {
data: T;
success: boolean;
error?: string;
message?: string;
}
Error Handling
// Custom errors with i18n support
throw new APIError(
ErrCode.Unauthorized,
t('auth.invalid_credentials')
);
Validation
Using Encore decorators:
interface SignupRequest {
@IsEmail()
email: string;
@MinLen(3)
@MaxLen(50)
username: string;
@MatchesRegexp(/^[A-Z0-9]{6}$/)
referralCode?: string;
}
Authentication Flow
JWT Authentication
Web3 Authentication
// Solana wallet signature verification
function verifySolanaSignature(
message: string,
signature: string,
walletAddress: string
): boolean {
const messageBytes = new TextEncoder().encode(message);
const signatureBytes = bs58.decode(signature);
const publicKey = new PublicKey(walletAddress);
return nacl.sign.detached.verify(
messageBytes,
signatureBytes,
publicKey.toBytes()
);
}
Blockchain Integration
Smart Contract Interaction
// Event listener for deposits
const depositFilter = vault.filters.Passthrough();
vault.on(depositFilter, async (from, token, amount, depositId, product) => {
await depositService.processDeposit({
user: from,
token,
amount: amount.toString(),
depositId,
product,
});
});
RPC Configuration
const provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
const vault = new ethers.Contract(VAULT_ADDRESS, VAULT_ABI, provider);
External Integrations
| Service | Purpose | Library |
|---|---|---|
| Supabase | Database hosting | @supabase/supabase-js |
| Zoho SMTP | Email delivery | nodemailer |
| Firebase | Google OAuth | firebase-admin |
| Telegram | Bot & Mini App | Custom API |
| Base RPC | Blockchain | ethers.js |
Development Setup
Environment Variables
# Database
DATABASE_URL=postgresql://...
SUPABASE_URL=https://...
SUPABASE_ANON_KEY=...
# Authentication
JWT_SECRET=...
SESSION_SECRET=...
# Blockchain
RPC_URL=https://mainnet.base.org
VAULT_ADDRESS=0x...
ACCOUNTANT_ADDRESS=0x...
# Email
EMAIL_HOST=smtp.zoho.com
EMAIL_PORT=465
EMAIL_USER=...
EMAIL_PASS=...
# External
TELEGRAM_BOT_TOKEN=...
FIREBASE_PROJECT_ID=...
Local Development
# Install dependencies
yarn install
# Start development server
yarn dev
# Database migrations
yarn db:generate
yarn db:push
# Database studio
yarn db:studio
API Endpoints
- API Server:
http://localhost:4000 - Dashboard:
http://localhost:9400