Introduction
For the SEBI hackathon, our team built a complete trading platform backend using modern serverless technologies. The project demonstrates how to create a secure, scalable financial application with proper authentication, KYC compliance, balance management, and trading capabilities.
This blog post walks through the technical implementation of our trading platform backend built with Hono.js and deployed on Cloudflare Workers.
Technology Stack
- Backend Framework: Hono.js with TypeScript
- Database: Cloudflare D1 (SQLite)
- Deployment: Cloudflare Workers
- Authentication: JWT with SHA-256 password hashing
Project Architecture
The application follows a clean REST API architecture with these core modules:
src/ ├── routes/ │ ├── auth.ts # User authentication │ ├── kyc.ts # KYC management │ ├── balance.ts # Balance operations │ └── trading.ts # Stock trading └── index.ts # Main application
Main Application Setup
import { Hono } from 'hono' import auth from './routes/auth' import kyc from './routes/kyc' import balance from './routes/balance' import trading from './routes/trading' type Bindings = { sebi_trading_db: D1Database } const app = new Hono<{ Bindings: Bindings }>() app.route('/auth', auth) app.route('/kyc', kyc) app.route('/balance', balance) app.route('/trading', trading)
Database Design
We implemented a relational database schema with four main tables:
Users Table
CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, email TEXT UNIQUE NOT NULL, phone TEXT NOT NULL, password_hash TEXT NOT NULL, name TEXT NOT NULL, kyc_status TEXT DEFAULT 'not_registered', created_at DATETIME DEFAULT CURRENT_TIMESTAMP );
KYC Table
CREATE TABLE kyc ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, pan TEXT NOT NULL, status TEXT DEFAULT 'pending', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, validated_at DATETIME, FOREIGN KEY (user_id) REFERENCES users(id) );
Balance Management
CREATE TABLE balance ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, amount DECIMAL(15,2) DEFAULT 0.00, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ); CREATE TABLE balance_transactions ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, type TEXT NOT NULL, -- 'credit' or 'debit' amount DECIMAL(15,2) NOT NULL, description TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) );
Core Features Implementation
1. Authentication System
We implemented secure user authentication with password hashing and JWT tokens:
// Password hashing using SHA-256 async function hashPassword(password: string): Promise<string> { const encoder = new TextEncoder() const data = encoder.encode(password) const hashBuffer = await crypto.subtle.digest('SHA-256', data) const hashArray = Array.from(new Uint8Array(hashBuffer)) return hashArray.map(b => b.toString(16).padStart(2, '0')).join('') } // JWT token generation const payload = { sub: result.id.toString(), username: result.username, exp: Math.floor(Date.now() / 1000) + 60 * 60 * 24, // 24 hours iat: Math.floor(Date.now() / 1000) } const secret = 'hackathon-secret-key-2024' const token = await sign(payload, secret)
Endpoints:
POST /auth/signup
- User registrationPOST /auth/login
- User authentication
2. KYC Management System
The KYC system handles user verification with PAN card validation:
// PAN format validation const panRegex = /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/ if (!panRegex.test(pan.toUpperCase())) { return c.json({ success: false, error: 'Invalid PAN format. PAN should be 10 characters (5 letters + 4 digits + 1 letter)' }, 400) } // Check for existing KYC const existingKyc = await checkKycExists(c.env.sebi_trading_db, userId) if (existingKyc) { return c.json({ success: false, error: 'KYC already registered for this user', existingKyc: { id: existingKyc.id, pan: existingKyc.pan, status: existingKyc.status } }, 400) }
Endpoints:
POST /kyc/register
- Register KYC with PANPOST /kyc/validate
- Validate KYC statusGET /kyc/status
- Check current KYC status
3. Balance Management
The balance system includes minimum balance enforcement and comprehensive transaction tracking:
const MINIMUM_BALANCE = 1000 // Balance addition with validation const currentBalance = await getOrCreateBalance(c.env.sebi_trading_db, userId) const currentAmount = safeParseAmount(currentBalance.amount) const newBalance = currentAmount + addBalance // Update balance and record transaction await c.env.sebi_trading_db.prepare(` UPDATE balance SET amount = ?, updated_at = ? WHERE user_id = ? `).bind(newBalance, new Date().toISOString(), userId).run() await c.env.sebi_trading_db.prepare(` INSERT INTO balance_transactions (user_id, type, amount, description) VALUES (?, ?, ?, ?) `).bind(userId, 'credit', addBalance, 'Balance added').run()
Low Balance Alert System:
const isLowBalance = currentAmount < MINIMUM_BALANCE const shortfall = isLowBalance ? MINIMUM_BALANCE - currentAmount : 0 return c.json({ success: true, alert: { isLowBalance: isLowBalance, shortfall: shortfall, message: isLowBalance ? `Your balance is ₹${currentAmount}. Minimum required is ₹${MINIMUM_BALANCE}. Please recharge ₹${shortfall}.` : 'Your balance is sufficient for trading.' } })
Endpoints:
POST /balance/add
- Add money to accountGET /balance/check
- Check current balanceGET /balance/check-low-balance
- Check if balance is lowGET /balance/alert
- Get balance alerts for frontendGET /balance/transactions
- Get transaction history with pagination
4. Trading System
The trading system handles stock purchases and sales with proper validation and portfolio management:
Stock Purchase
// Validate purchase requirements if (currentBalance < totalCost) { return c.json({ success: false, error: 'Insufficient balance for this purchase', details: { requiredAmount: totalCost, currentBalance: currentBalance, shortfall: totalCost - currentBalance } }, 400) } // Check minimum balance violation const remainingBalance = currentBalance - totalCost if (remainingBalance < MINIMUM_BALANCE) { return c.json({ success: false, error: 'Purchase would leave insufficient balance' }, 400) }
Portfolio Management
// Update or create portfolio entry const existingPortfolio = await c.env.sebi_trading_db.prepare(` SELECT * FROM portfolio WHERE user_id = ? AND stock_name = ? `).bind(userId, stockName.toUpperCase()).first() if (existingPortfolio) { // Update existing holding const newQuantity = Number(existingPortfolio.quantity) + quantity const newTotalInvestment = safeParseAmount(existingPortfolio.total_investment) + totalCost const newAveragePrice = newTotalInvestment / newQuantity await c.env.sebi_trading_db.prepare(` UPDATE portfolio SET quantity = ?, average_price = ?, total_investment = ? WHERE user_id = ? AND stock_name = ? `).bind(newQuantity, newAveragePrice, newTotalInvestment, userId, stockName).run() } else { // Create new portfolio entry await c.env.sebi_trading_db.prepare(` INSERT INTO portfolio (user_id, stock_name, quantity, average_price, total_investment) VALUES (?, ?, ?, ?, ?) `).bind(userId, stockName, quantity, price, totalCost).run() }
Stock Sale with Profit/Loss Calculation
// Calculate profit/loss const saleProceeds = price * quantity const averagePrice = safeParseAmount(portfolioEntry.average_price) const profitLoss = (price - averagePrice) * quantity const profitLossPercentage = ((price - averagePrice) / averagePrice) * 100 // Update portfolio or remove if fully sold const remainingQuantity = ownedQuantity - quantity if (remainingQuantity === 0) { await c.env.sebi_trading_db.prepare(` DELETE FROM portfolio WHERE user_id = ? AND stock_name = ? `).bind(userId, stockName).run() }
Endpoints:
POST /trading/buy
- Purchase stocksPOST /trading/sell
- Sell stocksGET /trading/portfolio
- View current portfolio
Security Implementation
Token Verification Middleware
async function verifyToken(c: any) { const authHeader = c.req.header('Authorization') if (!authHeader || !authHeader.startsWith('Bearer ')) { return c.json({ success: false, error: 'Authorization header required' }, 401) } const token = authHeader.substring(7) try { const secret = 'hackathon-secret-key-2024' const payload = await verify(token, secret) return payload } catch (error) { return c.json({ success: false, error: 'Invalid or expired token' }, 401) } }
Input Validation
All endpoints include comprehensive input validation:
- Data type checking
- Range validation (positive numbers for amounts)
- Format verification (PAN format validation)
- Business logic validation (sufficient balance, stock ownership)
Database Security
- Parameterized queries to prevent SQL injection
- Foreign key constraints for data integrity
- Proper indexing for performance
API Response Design
All endpoints follow a consistent response structure:
Success Response
{ "success": true, "message": "Operation completed successfully", "data": {...}, "metadata": {...} }
Error Response
{ "success": false, "error": "Detailed error message", "details": { "additionalInfo": "..." } }
Performance Optimizations
Database Indexing
CREATE INDEX idx_kyc_user_id ON kyc(user_id); CREATE INDEX idx_balance_user_id ON balance(user_id); CREATE INDEX idx_balance_transactions_user_id ON balance_transactions(user_id);
Safe Amount Parsing
function safeParseAmount(amount: any): number { if (amount === null || amount === undefined) return 0 const parsed = parseFloat(amount) return isNaN(parsed) ? 0 : parsed }
Efficient Balance Management
async function getOrCreateBalance(db: D1Database, userId: string) { let balanceRecord = await db.prepare(` SELECT * FROM balance WHERE user_id = ? `).bind(userId).first() if (!balanceRecord) { await db.prepare(` INSERT INTO balance (user_id, amount) VALUES (?, 0.00) `).bind(userId).run() balanceRecord = await db.prepare(` SELECT * FROM balance WHERE user_id = ? `).bind(userId).first() } return balanceRecord }
Testing
The project includes comprehensive test cases covering all endpoints:
- User registration and authentication flow
- KYC registration, validation, and status checking
- Balance operations and transaction history
- Stock trading operations (buy/sell)
- Portfolio management
- Error handling scenarios
All test cases are documented with curl commands and expected responses.
Deployment
The application is deployed on Cloudflare Workers with:
Configuration (wrangler.toml)
name = "sebi-hackathon" main = "src/index.ts" compatibility_date = "2024-01-01" [[d1_databases]] binding = "sebi_trading_db" database_name = "sebi-trading-db"
Deployment Commands
npm run dev # Development server npm run deploy # Production deployment
Key Features Implemented
Conclusion
This SEBI hackathon project demonstrates how modern serverless technologies can be used to build a comprehensive trading platform backend. The combination of Hono.js and Cloudflare Workers provides excellent performance, scalability, and developer experience.
The implementation covers all essential features of a trading platform including secure authentication, regulatory compliance through KYC, proper balance management, and complete trading operations with portfolio tracking.
The serverless architecture ensures global low latency, automatic scaling, and cost-effective operations, making it suitable for both development and production environments.
Project Links
Live Demo: https://sebi-hackathon.bkumar-be23.workers.dev/
Team: Yajat, Naman, Bhupesh, Simran, Akshat