Using event-syncs in Moralis SDK v2 & Parse Server

As of 9 Oct 2022 the current available tutorials/guides regarding using Smart contract event syncs similar to the older Moralis SDK 1 Workflow is inconsistent (so I don’t say inexistent). Please take note that this guide has been written in a hurry because of lack of time. The reason why I posted this here is to save some frustration I went through so you don’t have to.

WARNING
The code shown here has NOT been tested for production environment and is only a way of achieving functionality similar to the current Event syncs. It also lacks the mapping of tags->tableName and with the code shown below, the tableName is actually the event’s name (as defined in Smart contract). Also the :any is used widely here to suppress Typescript errors.

Introduction
Moralis offers a repo with a sample installation of parse-server with integrations to the EvmApi functions of the new SDK which is available at https://github.com/MoralisWeb3/Moralis-JS-SDK/tree/main/demos/parse-server-migration but which does not out-of-the-box support the Streams API.

How to integrate
A patch has been made and it is available in this repo: https://github.com/MoralisWeb3/parse-server-moralis-streams but this patch is NOT compatible with the previous repo, although they are linked in the same tutorial inside the Docs. (bummer!)

In order to get Streams API working with the first repo, the one where the EvmApi is already integrated you have to do the following:

You have to install either Moralis SDK v1 or the Parse SDK (which MoralisSDKv1 is build over) and connect to the parse-server from the parse-server (sounds like a paradox…right?).

You can read more about it here where you will also find instructions on how to connect to the parse-server.

Inside the express app which servers a foundation for the parse-server and for parse-dashboard (if you have it installed) you have to add some post routes for each of your webhooks.
Example route:

app.post('/webhooks/test', async (req: any, res: any) => {
  console.log(req.body);
  try {
    verifySignature(req, config.MORALIS_API_KEY);
    const { data, tagName, eventName }: any = parseEventData(req);
    console.log(data);
    await parseUpdate(`SFS_${eventName}`, data);
  } catch (e) {
    console.log(e);
  }
  res.send('ok');
});
export async function parseUpdate(tableName: string, object: any) {
  // Check if object exists in db

  const query = new Parse.Query(tableName);
  query.equalTo('transaction_hash', object.transaction_hash);
  const result = await query.first({ useMasterKey: true });
  if (result) {
    // Loop through object's keys
    for (const key in object) {
      result.set(key, object[key]);
    }
    return result?.save(null, { useMasterKey: true });
  } else {
    // Create new object
    const newObject = new Parse.Object(tableName);
    for (const key in object) {
      newObject.set(key, object[key]);
    }
    return newObject.save(null, { useMasterKey: true });
  }
}

The verifySignature function code is available here, you gotta get it from here and put it somewhere inside your code, I put it at ./src/helpers/utils.ts.

The parseEventData is a function with the following signature:

export function parseEventData(req: any) {
  try {
    const updates: any = {};
    for (const log of req.body.logs) {
      const abi = req.body.abis[log.streamId];
      if (abi) {
        const { filter, update, eventName } = realtimeUpsertParams(abi, log, req.body.confirmed, req.body.block);
        return { data: update, tagName: log.tag, eventName };
      }
    }
  } catch (e: any) {
    console.log(e);
  }
}

The realtimeUpsertParams function code is available here. To make the parse-dashboard work with values outputted by this function you need to remove the numberDecimal support until the properly patched parse-dashboard is released by the Moralis team.

Rewriting the decodeWithEthers function available in the same file to not include the numberDecimal stuff fixes the dashboard issue.

7 Likes

Awesome! works. I think maybe the response format changed since the recent one event per stream change? After adjusting the example parseEventData a bit got it running though.

So in the cloud code folder, you can define a route and use Parse.object without importing anything, but from the express route you need to install parse sdk, import, and initialize it, before running the same queries as you would in cloude code? And the web hook needs to be an express route rather than a cloud code function?

It seems to be working this way, just wasn’t sure if I fully understood everything going on there.

Some of the code to fix potential issues with stream when migrating from hosted moralis:

function decodeWithEthers(abi, data, topics) {
    try {
        const iface = new ethers.utils.Interface(abi);
        const { args } = iface.parseLog({ data, topics});
        const event = iface.getEvent(topics[0]);
        const decoded = {};
        event.inputs.forEach((input, index) => {   
            if (input.type === "uint256") {
            /*decoded[`${input.name}_decimal`] = {
                __type: "NumberDecimal",
                value: parseInt(ethers.BigNumber.from(args[index]._hex).toString())
            };*/
            decoded[input.name] = ethers.BigNumber.from(args[index]._hex).toString();
            return;
            }
            if(input.type === "bytes") {
            decoded[input.name] = args[index].hash                        
            return;
            }
            if(input.type === "address") {
            decoded[input.name] = args[index].toLowerCase()
            return;
            }
            decoded[input.name] = args[index];
        });
        return decoded;
    } catch (error) {
        return {};
    }
}
1 Like

Thanks for the help!
Could you please share the final typescript version of the express app and ./src/helpers/utils.ts.
It’d be greatly appreciated.

1 Like