Architecting a P2P Cryptocurrency Trading Platform: Atomic Transactions and Blockchain Integration
The Vision
At Avin Avisa, I was tasked with building a peer-to-peer cryptocurrency trading platform that would allow users to trade directly with each other, without a centralized exchange. This was 2019, when P2P trading was gaining momentum as users sought more control over their assets and lower fees.
The challenge was significant: build a platform that could handle real money, ensure financial data integrity, and integrate securely with the Ethereum blockchain—all while maintaining high performance and user experience.
Architecture Overview
I designed a microservices architecture that separated concerns and enabled scalability:
┌─────────────────────────────────────────┐
│ API Gateway (Nginx) │
│ - Rate limiting │
│ - SSL termination │
│ - Request routing │
└──────────────┬──────────────────────────┘
│
┌──────────┼──────────┐
│ │ │
▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐
│ Auth │ │ Trading│ │Blockchain│
│Service │ │Service │ │ Service │
└────────┘ └────────┘ └────────┘
│ │ │
└──────────┼──────────┘
│
▼
┌──────────┐
│ MongoDB │
│ Cluster │
└──────────┘
Core Challenges
1. Financial Data Integrity
The most critical requirement was ensuring that financial transactions were atomic—either all operations succeed or all fail. In a trading platform, you can't have a situation where:
2. Blockchain Integration
Integrating with Ethereum meant:
3. Real-Time Updates
Users needed to see:
Implementation
Atomic Transactions in MongoDB
MongoDB 4.0 introduced multi-document transactions, which I leveraged to ensure data integrity:
import { MongoClient } from 'mongodb';async function executeTrade(
buyerId: string,
sellerId: string,
amount: number,
price: number
): Promise {
const session = client.startSession();
try {
await session.withTransaction(async () => {
const db = client.db('trading');
const users = db.collection('users');
const orders = db.collection('orders');
const trades = db.collection('trades');
// 1. Lock user accounts
const buyer = await users.findOneAndUpdate(
{ _id: buyerId, balance: { $gte: amount * price } },
{ $inc: { balance: -(amount price), lockedBalance: amount price } },
{ session, returnDocument: 'after' }
);
if (!buyer) {
throw new Error('Insufficient funds');
}
const seller = await users.findOneAndUpdate(
{ _id: sellerId, lockedBalance: { $gte: amount } },
{ $inc: { lockedBalance: -amount, balance: amount * price } },
{ session, returnDocument: 'after' }
);
if (!seller) {
throw new Error('Order no longer available');
}
// 2. Create trade record
const trade = await trades.insertOne({
buyerId,
sellerId,
amount,
price,
total: amount * price,
status: 'pending',
createdAt: new Date()
}, { session });
// 3. Update order status
await orders.updateOne(
{ _id: sellerOrderId },
{ $set: { status: 'filled', tradeId: trade.insertedId } },
{ session }
);
// 4. Unlock balances
await users.updateOne(
{ _id: buyerId },
{ $inc: { lockedBalance: -(amount * price) } },
{ session }
);
await users.updateOne(
{ _id: sellerId },
{ $inc: { lockedBalance: -amount } },
{ session }
);
return {
success: true,
tradeId: trade.insertedId,
amount,
price
};
});
} catch (error) {
// Transaction automatically rolls back
logger.error('Trade execution failed', { error, buyerId, sellerId });
throw error;
} finally {
await session.endSession();
}
}
Ethereum Blockchain Integration
I built a service to handle Ethereum interactions securely:
import { ethers } from 'ethers';
import { MongoClient } from 'mongodb';class BlockchainService {
private provider: ethers.providers.JsonRpcProvider;
private wallet: ethers.Wallet;
constructor() {
this.provider = new ethers.providers.JsonRpcProvider(
process.env.ETHEREUM_RPC_URL
);
this.wallet = new ethers.Wallet(
process.env.PRIVATE_KEY,
this.provider
);
}
async depositToUser(userId: string, amount: string): Promise {
// 1. Generate unique deposit address for user
const depositAddress = await this.generateDepositAddress(userId);
// 2. Monitor for incoming transactions
this.monitorDeposits(depositAddress, userId);
return depositAddress;
}
async withdrawFromUser(
userId: string,
recipientAddress: string,
amount: string
): Promise {
const session = client.startSession();
try {
await session.withTransaction(async () => {
// 1. Lock user balance
const user = await db.collection('users').findOneAndUpdate(
{ _id: userId, balance: { $gte: amount } },
{
$inc: { balance: -amount, lockedBalance: amount },
$set: { withdrawalStatus: 'pending' }
},
{ session, returnDocument: 'after' }
);
if (!user) {
throw new Error('Insufficient balance');
}
// 2. Create withdrawal record
const withdrawal = await db.collection('withdrawals').insertOne({
userId,
recipientAddress,
amount,
status: 'pending',
createdAt: new Date()
}, { session });
// 3. Send transaction to blockchain
const tx = await this.wallet.sendTransaction({
to: recipientAddress,
value: ethers.utils.parseEther(amount),
gasLimit: 21000,
gasPrice: await this.getOptimalGasPrice()
});
// 4. Update withdrawal with transaction hash
await db.collection('withdrawals').updateOne(
{ _id: withdrawal.insertedId },
{ $set: { txHash: tx.hash, status: 'processing' } },
{ session }
);
// 5. Wait for confirmation
const receipt = await tx.wait(3); // Wait for 3 confirmations
// 6. Update withdrawal status
await db.collection('withdrawals').updateOne(
{ _id: withdrawal.insertedId },
{ $set: { status: 'completed', confirmedAt: new Date() } },
{ session }
);
// 7. Unlock user balance (already deducted)
await db.collection('users').updateOne(
{ _id: userId },
{ $inc: { lockedBalance: -amount } },
{ session }
);
return tx.hash;
});
} catch (error) {
// Rollback on error
logger.error('Withdrawal failed', { error, userId });
throw error;
} finally {
await session.endSession();
}
}
private async monitorDeposits(address: string, userId: string) {
// Monitor blockchain for incoming transactions
this.provider.on('block', async (blockNumber) => {
const block = await this.provider.getBlockWithTransactions(blockNumber);
for (const tx of block.transactions) {
if (tx.to?.toLowerCase() === address.toLowerCase() && tx.value.gt(0)) {
// Deposit detected
await this.processDeposit(userId, tx.hash, tx.value.toString());
}
}
});
}
private async processDeposit(
userId: string,
txHash: string,
amount: string
) {
const session = client.startSession();
try {
await session.withTransaction(async () => {
// Check if deposit already processed
const existing = await db.collection('deposits').findOne(
{ txHash },
{ session }
);
if (existing) {
return; // Already processed
}
// Wait for confirmations
const tx = await this.provider.getTransaction(txHash);
const receipt = await tx.wait(3);
if (receipt.status === 0) {
throw new Error('Transaction failed');
}
// Credit user balance
await db.collection('users').updateOne(
{ _id: userId },
{ $inc: { balance: amount } },
{ session }
);
// Record deposit
await db.collection('deposits').insertOne({
userId,
txHash,
amount,
status: 'completed',
confirmedAt: new Date()
}, { session });
});
} catch (error) {
logger.error('Deposit processing failed', { error, userId, txHash });
} finally {
await session.endSession();
}
}
}
Real-Time Order Book
I implemented a real-time order book using WebSocket:
import { Server as SocketServer } from 'socket.io';class OrderBookService {
private io: SocketServer;
private orderBook: Map = new Map();
constructor(io: SocketServer) {
this.io = io;
this.setupEventHandlers();
}
async addOrder(order: Order) {
const session = client.startSession();
try {
await session.withTransaction(async () => {
// 1. Lock user balance
if (order.side === 'buy') {
await db.collection('users').updateOne(
{ _id: order.userId, balance: { $gte: order.total } },
{ $inc: { balance: -order.total, lockedBalance: order.total } },
{ session }
);
} else {
await db.collection('users').updateOne(
{ _id: order.userId, balance: { $gte: order.amount } },
{ $inc: { balance: -order.amount, lockedBalance: order.amount } },
{ session }
);
}
// 2. Save order to database
await db.collection('orders').insertOne(order, { session });
// 3. Try to match with existing orders
const match = await this.findMatch(order);
if (match) {
await this.executeTrade(order, match, session);
} else {
// Add to order book
this.updateOrderBook(order);
this.broadcastOrderBookUpdate();
}
});
} finally {
await session.endSession();
}
}
private broadcastOrderBookUpdate() {
const orderBookData = this.getOrderBookSnapshot();
this.io.emit('orderbook:update', orderBookData);
}
private getOrderBookSnapshot() {
const snapshot: Record = {};
for (const [pair, orders] of this.orderBook.entries()) {
snapshot[pair] = {
bids: orders.filter(o => o.side === 'buy').sort((a, b) => b.price - a.price),
asks: orders.filter(o => o.side === 'sell').sort((a, b) => a.price - b.price)
};
}
return snapshot;
}
}
Security Measures
Private Key Management
Private keys were stored encrypted and never exposed:
import crypto from 'crypto';class KeyManager {
private encryptionKey: Buffer;
constructor() {
this.encryptionKey = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');
}
encryptPrivateKey(privateKey: string): string {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-gcm', this.encryptionKey, iv);
let encrypted = cipher.update(privateKey, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return ${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted};
}
decryptPrivateKey(encrypted: string): string {
const [ivHex, authTagHex, encryptedData] = encrypted.split(':');
const iv = Buffer.from(ivHex, 'hex');
const authTag = Buffer.from(authTagHex, 'hex');
const decipher = crypto.createDecipheriv('aes-256-gcm', this.encryptionKey, iv);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encryptedData, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
}
Rate Limiting
Implemented rate limiting to prevent abuse:
import rateLimit from 'express-rate-limit';const tradingLimiter = rateLimit({
windowMs: 60 * 1000, // 1 minute
max: 10, // 10 requests per minute
message: 'Too many trading requests, please try again later',
standardHeaders: true,
legacyHeaders: false,
});
app.post('/api/trades', tradingLimiter, authenticate, executeTrade);
Results
Metrics
Technical Achievements
Lessons Learned
Conclusion
Building the P2P cryptocurrency trading platform at Avin Avisa was a masterclass in building financial systems. It required deep understanding of database transactions, blockchain technology, real-time systems, and security. The platform successfully processed millions of dollars in trades with zero data loss and 99.8% transaction success rate.
The experience taught me that building financial systems is about more than just writing code—it's about ensuring data integrity, handling edge cases, and building systems that users can trust with their money.
---
Interested in blockchain integration, financial systems, or atomic transactions? Let's connect!