How to Listen For ERC20 Token Transfers To A Specific Wallet Using Moralis Streams and NodeJS

Introduction

Moralis is an organization whose products have been incredibly helpful to web3 developers and web2 developers who want to work with web3 concepts but one product that has particularly caught my eye is Moralis Streams. It is a tool for listening for activity on EVM blockchains and triggering actions off-chain based on those transactions. It can be used in implementing the receiving of payment in tokens for e-commerce websites, setting up email or text message alerts for your wallet etc.

In this article, I will be tutoring you on how to listen for USDT transfers into a particular wallet address on the Ethereum mainnet and how to extract commonly needed data from the request body of the Moralis stream call to your webhook like the value of the transfer, the time it occurred, the chain ID the chain on which the transaction occurred etc.

Setting Up a Moralis Stream

Setting up a stream can be either done programmatically or using the User Interface on the Moralis dashboard. I will be making use of the User Interface in this tutorial. Setting up programmatically is suitable for use when you want to be able to dynamically create and delete streams.

Follow the steps below to setup a Moralis stream

  1. Go to moralis.io and either login or signup then go to your dashboard.

  2. Click on Web3 APIs and copy your api key. You will be needing this later.

  3. Now go to the Streams section. (This is where it gets fun)

  4. Below is the user interface for managing your Moralis streams. You can choose to create your streams programmatically. Checkout the moralis streams documentation(https://docs.moralis.io/streams-api/evm) and Github(https://github.com/MoralisWeb3/streams-beta).

  5. Click on “create a new stream”

  6. Enter the address of the token contract you want to listen to. For USDT on the ethereum mainnet, it is 0xdAC17F958D2ee523a2206206994597C13D831ec7

  7. Fill in the webhook you want moralis to call when the token is received by the wallet as well as the stream description and an identifiable tag for the stream. Switch from Demo to Prod. If there is an issue with posting to the webhook url, check the webhook URL text thoroughly. The webhook should return status 200 or else it will not work.

  8. Select the network(In this case Ethereum mainnet).

  9. Select “contract interactions” under stream options because ERC20 token transfers are token contract interactions.

  10. Fill in the abi for ERC20 contract standard. This is publicly available. You can find it here (https://github.com/Boboye-Ak/project-constants/blob/main/erc20Abi.json). Copy the entire content of the file and paste it in the field provided.

  11. Select "Transfer" from among the topics to make sure the stream is only triggered by transfer operations.

  12. Under the filter section, select Transfer as topic, add an “equal” filter, set “to” as the variable and enter the recipient address of the transfer as value then click "update stream".

The stream is now setup to send a post request to your webhook whenever USDT is sent to the wallet in the filter. I will now show you how to setup a middleware to use in NodeJS/Express to extract data from and secure the webhook.

Setting Up Middleware For Securing And Extracting Data From Moralis Streams

  1. Install moralis SDK in your project with npm install moralis Import into your middleware file and initialize it using the API key you got from your moralis.io dashboard in step 2 of the previous section.

     const Moralis = require("moralis").default;
     const MORALIS_API_KEY=process.env.MORALIS_API_KEY
    
     //Initiating Moralis
     Moralis.start({
       apiKey:MORALIS_API_KEY ,
     });
    
  2. Create a middleware function block named erc20Middleware and follow the next steps inside the code block.

  3. The code block below protects the webhook from intruders that might try to simulate a moralis stream to commit fraud.

     const erc20MiddleWare = async (req, res, next) => {
       const { body, headers } = req;
       try {
         //Verify Moralis signature
         await Moralis.Streams.verifySignature({
           body,
           signature: headers["x-signature"],
         });
       } catch (e) {
         console.log(
           "Not Moralis!... An intruder attempted to send a request to the server"
         );
         console.log(e);
         return res.status(401).json({ error: "Not verified as moralis stream" });
       }
     }
    
  4. Extract the vital data from the request body and store them in an object you add to the request object before passing to the next middleware as shown below. You can now export the middleware.

const Moralis = require("moralis").default;

//Initiating Moralis
Moralis.start({
  apiKey: process.env.MORALIS_API_KEY,
});

const erc20MiddleWare = async (req, res, next) => {
  const { body, headers } = req;
  try {
    //Verify Moralis signature
    await Moralis.Streams.verifySignature({
      body,
      signature: headers["x-signature"],
    });
  } catch (e) {
    console.log(
      "Not Moralis!... An intruder attempted to send a request to the server"
    );
    console.log(e);
    return res.status(401).json({ error: "Not verified as moralis stream" });
  }
  const streamId = body.streamId;
  const isConfirmed = body.confirmed; //For each transaction, the webhook is called twice. Once with the value of confirmed false, then a few minutes later, another with value true(After a few block confirmations.)
  const blockNumber = body.block.number;
  const blockTime = body.block.timestamp;
  let eventTime = new Date(parseInt(blockTime) * 1000).toString();//Number of milliseconds since January 1st 1970
  const txHash = body?.logs[0]?.transactionHash?.toLowerCase();
  const chainId = body.chainId;
  const amountWithDecimals = parseFloat(body.erc20Transfers[0].valueWithDecimals);
  const amountWithoutDecimals = parseFloat(body.erc20Transfers[0].value);
  const tokenAddress =
    body.erc20Transfers[0].contract || body.erc20Transfers[0].tokenAddress; 
  const tokenDecimals = body.erc20Transfers[0].tokenDecimals;
  let response;
  try {
    response = await Moralis.EvmApi.transaction.getTransaction({
      transactionHash: txHash,
      chain: chainId,
    });
  } catch (e) {
    console.log(e);
    return res.status(500).json({ error: "error getting transaction" });
  }
  const senderAddress = response.data.from_address.toLowerCase();
  const data = {
    isConfirmed,
    blockNumber,
    blockTime,
    eventTime,
    txHash,
    chainId,
    amountWithDecimals,
    senderAddress,
    tokenDecimals,
    tokenAddress,
    amountWithoutDecimals,
    streamId,
  };
  req.data = data;
  next();
};

module.exports = { erc20MiddleWare };

You can console.log the entire request body and rifle through for any data you might find useful. A sample of the request body can be found in the stream settings page of your Moralis dashboard or in the Moralis Streams Github(https://github.com/MoralisWeb3/streams-beta).

The code above can be found here(https://github.com/Boboye-Ak/project-constants/blob/main/webhooksMiddleware.js).