Skip to main content

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?

FeatureBenefit
TypeScript-firstType safety across entire stack
Built-in InfrastructureDatabase, caching, pub/sub included
Auto-generated APIsOpenAPI/REST documentation
ObservabilityBuilt-in monitoring dashboard
Cloud DeploymentOne-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:

MethodDescription
OTPEmail/phone one-time password
Web3Wallet signature verification (Solana)
Google OAuthFirebase Admin integration
TelegramBot 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

TablePurpose
usersUser accounts
user_statsPoints, rewards tracking
user_walletsWallet addresses
depositsDeposit records
withdrawalsWithdrawal requests
positionsEarn positions
ftoken_holdingsfToken balances
yield_claimsYield claim history
questsQuest definitions
user_questsUser quest progress
notificationsUser notifications
email_queuePending emails
referralsReferral 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

EventTriggerSubscribers
userCreatedNew user signupStats, Referral
depositCompletedDeposit confirmedStats, Notification
withdrawalCompletedWithdrawal fulfilledStats, Notification
questClaimedQuest reward claimedStats, Referral
yieldClaimedYield claimedStats, 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)

JobSchedulePurpose
processEmailQueueEvery 1 minSend pending emails
retryFailedEmailsEvery 5 minRetry failed emails
calculateYieldsWeeklyCalculate user yields
syncBlockchainEventsEvery 1 minSync on-chain events
cleanupExpiredSessionsDailyRemove 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

ServicePurposeLibrary
SupabaseDatabase hosting@supabase/supabase-js
Zoho SMTPEmail deliverynodemailer
FirebaseGoogle OAuthfirebase-admin
TelegramBot & Mini AppCustom API
Base RPCBlockchainethers.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