Back to blogDesigning an Event-Driven Web3 Bot: Fastify, BullMQ, and MetaMask
Web3NodeJSRedisArchitecture

Designing an Event-Driven Web3 Bot: Fastify, BullMQ, and MetaMask

July 20, 20263 min read

"Architecting a robust, non-custodial NFT sniper bot with real-time WebSocket dashboard and distributed job queues."

Trading bots on low-latency blockchains need to process real-time market streams, evaluate trade strategies, and execute blockchain transactions with sub-second response times. Building such a system requires a modular, event-driven architecture that separates fast market monitoring from heavy write database actions and wallet execution.

Here is how we designed a non-custodial NFT Sniper Bot dashboard running on the Monad network.

The Architecture Layout

Our system uses an event-driven microservices setup:

[Market Websocket API] 

         ▼ (Real-time feed)
[Fastify API / Webhook] ──> [Redis/BullMQ Queue] ──> [Worker Pool]

                                                          ▼ (Prepare Tx)
[Client Browser] <─── WebSocket ─── [Realtime UI] <─── [MetaMask Sign]
  1. Market Feed: A listener service polls or connects to marketplace WebSocket feeds (like OpenSea or Magic Eden) and immediately dispatches new listings to a high-speed message queue.
  2. Queueing (BullMQ): Because database updates or transaction compilation can delay event handling, we offload processing to BullMQ backed by Redis.
  3. Transaction Preparation: The worker processes listings, checks active user filters, and compiles an unsigned EVM transaction.
  4. Non-Custodial Signing: To keep keys secure, we push the transaction to the user’s browser via WebSockets. The user signs and submits the transaction locally using MetaMask.

Implementing the Transaction Lifecycle

Below is a simplified Fastify route demonstrating how the backend handles transaction registration and monitors status updates over EVM RPC:

import Fastify from 'fastify';
import Queue from 'bull';
import { ethers } from 'ethers';

const app = Fastify();
const tradeQueue = new Queue('trades', 'redis://127.0.0.1:6379');
const provider = new ethers.JsonRpcProvider("https://testnet-rpc.monad.xyz");

// 1. Endpoint to register a new snipe target candidate
app.post('/api/snipe', async (request, reply) => {
  const { listingId, price, targetToken } = request.body as any;

  // Add job to processing queue immediately to prevent endpoint blocking
  const job = await tradeQueue.add({
    listingId,
    price,
    targetToken,
    status: 'PREPARING'
  });

  return { jobId: job.id, status: 'QUEUED' };
});

// 2. Background RPC scanner checks if a sent transaction has been mined
async function checkTransactionStatus(txHash: string) {
  try {
    const receipt = await provider.getTransactionReceipt(txHash);
    if (receipt) {
      const status = receipt.status === 1 ? 'SUCCESS' : 'FAILED';
      console.log(`Transaction ${txHash} mined with status: ${status}`);
      // Trigger database and socket updates here
    }
  } catch (error) {
    console.error("RPC Error:", error);
  }
}

Scaling the Queue

Using BullMQ guarantees that if the transaction execution node experiences high latency, listing events are buffered safely in Redis.

Component Technology Role
API Layer Fastify Handles incoming listing events and client requests with minimal overhead.
Broker Redis (BullMQ) Handles task routing, strategy matching, and retry operations.
State Store PostgreSQL Persists user credentials, historical trades, and performance metrics.
EVM Client MetaMask (Frontend) Secures private keys locally, executing non-custodial transactions.

Conclusion

By isolating market event flows from database operations and blockchain latency, you can build low-latency bots that scale under stress. Pushing unsigned transactions to the client browser via WebSockets keeps user funds secure under a non-custodial model without losing high-frequency automation capabilities.