Skip to main content
The transaction listener is a background service that monitors pending TRON transactions and updates order statuses when transactions are confirmed on the blockchain.

How It Works

The listener runs on a polling interval, checking the status of all pending transactions in the database:

Configuration

The listener is configured in /workspace/source/src/services/transactionListener.js:16-17:
const CONFIRMATIONS_REQUIRED = 21;
const CHECK_INTERVAL = 15000; // 15 seconds
ConstantValueDescription
CONFIRMATIONS_REQUIRED21Number of block confirmations needed
CHECK_INTERVAL15000msPolling interval (15 seconds)
TRON produces blocks every ~3 seconds. With 21 confirmations required, transactions are finalized in approximately 1 minute.

Starting the Listener

The listener is started when the server boots:
import { startTransactionListener } from './services/transactionListener.js';

// Start the listener
startTransactionListener();
Implementation in /workspace/source/src/services/transactionListener.js:102-107:
export const startTransactionListener = () => {
  syncPendingTransactions(); // Run immediately
  setInterval(() => {
    syncPendingTransactions();
  }, CHECK_INTERVAL); // Then every 15 seconds
};

Transaction Status Check

The listener queries the TRON blockchain to check transaction status:
~/workspace/source/src/services/transactionListener.js:19-34
export const getTransactionStatus = async (txHash) => {
  try {
    const tx = await tronWeb.trx.getTransactionInfo(txHash);
    
    if (!tx || !tx.blockNumber) {
      return null; // Transaction not found or not yet mined
    }
    
    return {
      confirmed: true,
      confirmations: 21
    };
  } catch (error) {
    return null;
  }
};

Processing Pending Transactions

The syncPendingTransactions function processes all pending transactions:

Step 1: Query Pending Transactions

~/workspace/source/src/services/transactionListener.js:36-41
export const syncPendingTransactions = async () => {
  try {
    const pendingTransactions = await Transaction.find({
      status: 'pending',
      transactionHash: { $ne: null } // Must have a transaction hash
    });

    console.log(`[Listener] Found ${pendingTransactions.length} pending transactions`);
    // ...
  }
};

Step 2: Check Blockchain Status

~/workspace/source/src/services/transactionListener.js:45-54
for (const transaction of pendingTransactions) {
  try {
    const status = await getTransactionStatus(transaction.transactionHash);

    if (status && status.confirmed) {
      console.log(`[Listener] Transaction confirmed: ${transaction.transactionHash}`);
      transaction.status = 'confirmed';
      transaction.confirmations = CONFIRMATIONS_REQUIRED;
      transaction.updatedAt = Date.now();
      await transaction.save();
      // ...
    }
  }
}

Step 3: Update Order Status

When a transaction is confirmed, the listener updates the associated order:
// ~/workspace/source/src/services/transactionListener.js:56-77
if (transaction.orderId) {
  const order = await Order.findById(transaction.orderId);
  if (order) {
    if (transaction.type === 'purchase' && order.status === 'pending') {
      order.status = 'completed';
      order.updatedAt = Date.now();
      await order.save();

      // Deduct stock for sold products
      for (const item of order.products) {
        await Product.findByIdAndUpdate(
          item.productId,
          { $inc: { stock: -item.quantity } }
        );
      }

      // Emit Socket.io event to notify user
      emitTransactionConfirmed(
        transaction.userId.toString(),
        transaction.orderId.toString(),
        transaction.transactionHash
      );
    }
  }
}

Transaction Lifecycle

Real-time Notifications

When a transaction is confirmed, users receive real-time notifications via Socket.io:
~/workspace/source/src/services/transactionListener.js:72-76
emitTransactionConfirmed(
  transaction.userId.toString(),
  transaction.orderId.toString(),
  transaction.transactionHash
);
See Socket.io Events for payload details.

Stock Management

The listener automatically manages product inventory:

Purchase Confirmation

When a purchase is confirmed, stock is decremented:
for (const item of order.products) {
  await Product.findByIdAndUpdate(
    item.productId,
    { $inc: { stock: -item.quantity } } // Decrease stock
  );
}

Refund Processing

When a refund is confirmed, stock is restored:
for (const item of order.products) {
  await Product.findByIdAndUpdate(
    item.productId,
    { $inc: { stock: item.quantity } } // Increase stock
  );
}

Error Handling

The listener uses try-catch blocks to handle errors gracefully:
for (const transaction of pendingTransactions) {
  try {
    const status = await getTransactionStatus(transaction.transactionHash);
    // Process transaction...
  } catch (error) {
    // Silent error handling - continues to next transaction
  }
}
Errors are caught silently to prevent one failed transaction from stopping the entire listener. Monitor your application logs for transaction processing issues.

Monitoring

The listener logs important events:
[Listener] Found 3 pending transactions
[Listener] Transaction confirmed: 7f3e8d9c2b1a5f4e3d2c1b0a9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e
[Socket] User 507f1f77bcf86cd799439011 joined room user:507f1f77bcf86cd799439011

Performance Considerations

Polling Interval: The 15-second interval balances responsiveness with API rate limits. Adjust CHECK_INTERVAL based on your needs:
  • Faster: 10 seconds (more responsive, higher API usage)
  • Slower: 30 seconds (lower API usage, less responsive)

Next Steps

Socket.io Events

Learn about real-time event payloads

TRON Setup

Configure TRON network connection

Build docs developers (and LLMs) love