Back to Blogs
Architecting a P2P Cryptocurrency Trading Platform: Atomic Transactions and Blockchain Integration

Architecting a P2P Cryptocurrency Trading Platform: Atomic Transactions and Blockchain Integration

12/18/202514 min
blockchainethereummongodbnodejstypescriptfintech

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:

  • User A's balance is debited

  • User B's balance is not credited

  • Or vice versa
  • 2. Blockchain Integration

    Integrating with Ethereum meant:

  • Managing private keys securely

  • Handling transaction confirmations

  • Dealing with network congestion

  • Managing gas fees

  • Handling failed transactions
  • 3. Real-Time Updates

    Users needed to see:

  • Order book updates in real-time

  • Trade executions immediately

  • Balance changes instantly

  • Market price movements
  • 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

  • Transaction Success Rate: 99.8%

  • Average Trade Execution Time: < 200ms

  • Zero Data Loss: All transactions atomic

  • Uptime: 99.9%

  • Users: 10,000+ registered users

  • Trade Volume: $2M+ processed
  • Technical Achievements

  • Atomic Transactions - Zero failed trades due to data inconsistency

  • Blockchain Integration - 100% successful deposit/withdrawal processing

  • Real-Time Updates - Sub-100ms latency for order book updates

  • Security - Zero security incidents

  • Scalability - Handled 1000+ concurrent users
  • Lessons Learned

  • Atomic operations are non-negotiable - In financial systems, partial failures are unacceptable

  • Blockchain integration is complex - Gas fees, network congestion, and confirmations add significant complexity

  • Real-time systems need careful design - WebSocket connections, message ordering, and state synchronization are critical

  • Security must be built-in - Private key management, encryption, and access control can't be afterthoughts

  • Monitoring is essential - Financial systems need comprehensive logging and alerting
  • 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!