Streams api for events with self hosted server

There is now a tutorial video to “Set Up Self-Hosted Server and Listen to Blockchain Events in Realtime”

It works well to listen to smart contract events and get them into the self hosted MongoDB, however I am still unable to add cloud functions and replicate what “watchContractEvent” was doing on Moralis-v1.
There are some elements of answers in this thread Self-hosted Moralis server: local devchain & cloud functions (e.g. someone made a “watchContractEvent” cloud function to be able to advance the chapter 15.2 of the FCC course https://youtu.be/gyMwXuJrbJQ which uses Moralis v1) but we would require a full Moralis tutorial on cloud functions to manipulate API streams data into the self hosted the DB, create new tables etc…

with what part you have issues?

what you mean with not being able to add cloud functions? you were not able to add a simple cloud function?

So at chapter 15.2 of the FCC course we build a NFT marketplace and the front end has a file called
AddEvent.js available here https://github.com/PatrickAlphaC/nextjs-nft-marketplace-moralis-fcc/blob/main/addEvents.js

AddEvent.js:

const Moralis = require("moralis/node")
require("dotenv").config()
const contractAddresses = require("./constants/networkMapping.json")
let chainId = process.env.chainId || 31337
let moralisChainId = chainId == "31337" ? "1337" : chainId
const contractAddressArray = contractAddresses[chainId]["NftMarketplace"]
const contractAddress = contractAddressArray[contractAddressArray.length - 1]

const serverUrl = process.env.NEXT_PUBLIC_SERVER_URL
const appId = process.env.NEXT_PUBLIC_APP_ID
const masterKey = process.env.masterKey

async function main() {
    await Moralis.start({ serverUrl, appId, masterKey })
    console.log(`Working with contrat address ${contractAddress}`)

    let itemListedOptions = {
        // Moralis understands a local chain is 1337
        chainId: moralisChainId,
        sync_historical: true,
        topic: "ItemListed(address,address,uint256,uint256)",
        address: contractAddress,
        abi: {
            anonymous: false,
            inputs: [
                {
                    indexed: true,
                    internalType: "address",
                    name: "seller",
                    type: "address",
                },
                {
                    indexed: true,
                    internalType: "address",
                    name: "nftAddress",
                    type: "address",
                },
                {
                    indexed: true,
                    internalType: "uint256",
                    name: "tokenId",
                    type: "uint256",
                },
                {
                    indexed: false,
                    internalType: "uint256",
                    name: "price",
                    type: "uint256",
                },
            ],
            name: "ItemListed",
            type: "event",
        },
        tableName: "ItemListed",
    }

    let itemBoughtOptions = {
        chainId: moralisChainId,
        address: contractAddress,
        sync_historical: true,
        topic: "ItemBought(address,address,uint256,uint256)",
        abi: {
            anonymous: false,
            inputs: [
                {
                    indexed: true,
                    internalType: "address",
                    name: "buyer",
                    type: "address",
                },
                {
                    indexed: true,
                    internalType: "address",
                    name: "nftAddress",
                    type: "address",
                },
                {
                    indexed: true,
                    internalType: "uint256",
                    name: "tokenId",
                    type: "uint256",
                },
                {
                    indexed: false,
                    internalType: "uint256",
                    name: "price",
                    type: "uint256",
                },
            ],
            name: "ItemBought",
            type: "event",
        },
        tableName: "ItemBought",
    }

    let itemCanceledOptions = {
        chainId: moralisChainId,
        address: contractAddress,
        topic: "ItemCanceled(address,address,uint256)",
        sync_historical: true,
        abi: {
            anonymous: false,
            inputs: [
                {
                    indexed: true,
                    internalType: "address",
                    name: "seller",
                    type: "address",
                },
                {
                    indexed: true,
                    internalType: "address",
                    name: "nftAddress",
                    type: "address",
                },
                {
                    indexed: true,
                    internalType: "uint256",
                    name: "tokenId",
                    type: "uint256",
                },
            ],
            name: "ItemCanceled",
            type: "event",
        },
        tableName: "ItemCanceled",
    }

    const listedResponse = await Moralis.Cloud.run("watchContractEvent", itemListedOptions, {
        useMasterKey: true,
    })
    const boughtResponse = await Moralis.Cloud.run("watchContractEvent", itemBoughtOptions, {
        useMasterKey: true,
    })
    const canceledResponse = await Moralis.Cloud.run("watchContractEvent", itemCanceledOptions, {
        useMasterKey: true,
    })
    if (listedResponse.success && canceledResponse.success && boughtResponse.success) {
        console.log("Success! Database Updated with watching events")
    } else {
        console.log("Something went wrong...")
    }
}

main()
    .then(() => process.exit(0))
    .catch((error) => {
        console.error(error)
        process.exit(1)
    })

I tried to update it as follows to consider the Goerli testate (chainId = 5) network instead of dev chain since Moralis v2 self hosted server has no native options to connect to dev chain at this stage. I also replaced morales/node and by parse/node:

// const Moralis = require("moralis-v1/node")
const Parse = require("parse/node")

require("dotenv").config()
const contractAddresses = require("./constants/networkMapping.json")
// let chainId = process.env.chainId || 31337
// let moralisChainId = chainId == "31337" ? "1337" : chainId

// temp fix for Moralis v2:
// let chainId = process.env.chainId || 5
// let moralisChainId = chainId == "5" ? "1337" : chainId
let moralisChainId = 5
const contractAddress = contractAddresses[chainId]["NftMarketplace"][0]

const serverUrl = process.env.NEXT_PUBLIC_SERVER_URL
const appId = process.env.NEXT_PUBLIC_APP_ID
const masterKey = process.env.masterKey

async function main() {
    // await Moralis.start({ serverUrl, appId, masterKey })
    await Parse.start({ serverUrl, appId, masterKey }) // masterKey needed?
    console.log(`Working with contract address ${contractAddress}`)

    let ItemListedOptions = {
        // Mortalis understands a local chain is 1337
        chainId: moralisChainId,
        address: contractAddress,
        sync_historical: true,
        topic: "ItemListed(address, address, uint256, uint256)",
        abi: {
            anonymous: false,
            inputs: [
                {
                    indexed: true,
                    internalType: "address",
                    name: "seller",
                    type: "address",
                },
                {
                    indexed: true,
                    internalType: "address",
                    name: "nftAddress",
                    type: "address",
                },
                {
                    indexed: true,
                    internalType: "uint256",
                    name: "tokenId",
                    type: "uint256",
                },
                {
                    indexed: false,
                    internalType: "uint256",
                    name: "price",
                    type: "uint256",
                },
            ],
            name: "ItemListed",
            type: "event",
        },
        tableName: "ItemListed",
    }

    let ItemBoughtOptions = {
        chainId: moralisChainId,
        address: contractAddress,
        topic: "ItemBought(address, address, uint256, uint256)",
        sync_historical: true,
        abi: {
            anonymous: false,
            inputs: [
                {
                    indexed: true,
                    internalType: "address",
                    name: "buyer",
                    type: "address",
                },
                {
                    indexed: true,
                    internalType: "address",
                    name: "nftAddress",
                    type: "address",
                },
                {
                    indexed: true,
                    internalType: "uint256",
                    name: "tokenId",
                    type: "uint256",
                },
                {
                    indexed: false,
                    internalType: "uint256",
                    name: "price",
                    type: "uint256",
                },
            ],
            name: "ItemBought",
            type: "event",
        },
        tableName: "ItemBought",
    }

    let ItemCanceledOptions = {
        chainId: moralisChainId,
        address: contractAddress,
        topic: "ItemCanceled(address, address, uint256)",
        sync_historical: true,
        abi: {
            anonymous: false,
            inputs: [
                {
                    indexed: true,
                    internalType: "address",
                    name: "seller",
                    type: "address",
                },
                {
                    indexed: true,
                    internalType: "address",
                    name: "nftAddress",
                    type: "address",
                },
                {
                    indexed: true,
                    internalType: "uint256",
                    name: "tokenId",
                    type: "uint256",
                },
            ],
            name: "ItemCanceled",
            type: "event",
        },
        tableName: "ItemCanceled",
    }

    // const listedResponse = await Moralis.Cloud.run("watchContractEvent", ItemListedOptions, {
    //     useMasterKey: true,
    // })
    // const boughtResponse = await Moralis.Cloud.run("watchContractEvent", ItemBoughtOptions, {
    //     useMasterKey: true,
    // })

    // const canceledResponse = await Moralis.Cloud.run("watchContractEvent", ItemCanceledOptions, {
    //     useMasterKey: true,
    // })

    // temp fix for Moralis v2:
    const listedResponse = await Parse.Cloud.run("watchContractEvent", ItemListedOptions, {
        useMasterKey: true,
    })
    const boughtResponse = await Parse.Cloud.run("watchContractEvent", ItemBoughtOptions, {
        useMasterKey: true,
    })

    const canceledResponse = await Parse.Cloud.run("watchContractEvent", ItemCanceledOptions, {
        useMasterKey: true,
    })
    if (listedResponse.success && canceledResponse.success && boughtResponse.success) {
        console.log("Success! Database Updated with watching event")
    } else {
        console.log("Something went wrong...")
    }
}

main()
    .then(() => process.exit(0))
    .catch((error) => {
        console.error(error)
        process.exit(1)
    })

Then regarding the cloud functions, the course offer the updateActiveItems.js file in the front end as well, as per the following link
https://github.com/PatrickAlphaC/nextjs-nft-marketplace-moralis-fcc/blob/main/cloudFunctions/updateActiveItems.js and code below:

updateActiveItems.js

Moralis.Cloud.afterSave("ItemListed", async (request) => {
    const confirmed = request.object.get("confirmed")
    const logger = Moralis.Cloud.getLogger()
    logger.info("Looking for confirmed TX...")
    if (confirmed) {
        logger.info("Found item!")
        const ActiveItem = Moralis.Object.extend("ActiveItem")

        // In case of listing update, search for already listed ActiveItem and delete
        const query = new Moralis.Query(ActiveItem)
        query.equalTo("nftAddress", request.object.get("nftAddress"))
        query.equalTo("tokenId", request.object.get("tokenId"))
        query.equalTo("marketplaceAddress", request.object.get("address"))
        query.equalTo("seller", request.object.get("seller"))
        logger.info(`Marketplace | Query: ${query}`)
        const alreadyListedItem = await query.first()
        console.log(`alreadyListedItem ${JSON.stringify(alreadyListedItem)}`)
        if (alreadyListedItem) {
            logger.info(`Deleting ${alreadyListedItem.id}`)
            await alreadyListedItem.destroy()
            logger.info(
                `Deleted item with tokenId ${request.object.get(
                    "tokenId"
                )} at address ${request.object.get(
                    "address"
                )} since the listing is being updated. `
            )
        }

        // Add new ActiveItem
        const activeItem = new ActiveItem()
        activeItem.set("marketplaceAddress", request.object.get("address"))
        activeItem.set("nftAddress", request.object.get("nftAddress"))
        activeItem.set("price", request.object.get("price"))
        activeItem.set("tokenId", request.object.get("tokenId"))
        activeItem.set("seller", request.object.get("seller"))
        logger.info(
            `Adding Address: ${request.object.get("address")} TokenId: ${request.object.get(
                "tokenId"
            )}`
        )
        logger.info("Saving...")
        await activeItem.save()
    }
})

Moralis.Cloud.afterSave("ItemCanceled", async (request) => {
    const confirmed = request.object.get("confirmed")
    const logger = Moralis.Cloud.getLogger()
    logger.info(`Marketplace | Object: ${request.object}`)
    if (confirmed) {
        const ActiveItem = Moralis.Object.extend("ActiveItem")
        const query = new Moralis.Query(ActiveItem)
        query.equalTo("marketplaceAddress", request.object.get("address"))
        query.equalTo("nftAddress", request.object.get("nftAddress"))
        query.equalTo("tokenId", request.object.get("tokenId"))
        logger.info(`Marketplace | Query: ${query}`)
        const canceledItem = await query.first()
        logger.info(`Marketplace | CanceledItem: ${JSON.stringify(canceledItem)}`)
        if (canceledItem) {
            logger.info(`Deleting ${canceledItem.id}`)
            await canceledItem.destroy()
            logger.info(
                `Deleted item with tokenId ${request.object.get(
                    "tokenId"
                )} at address ${request.object.get("address")} since it was canceled. `
            )
        } else {
            logger.info(
                `No item canceled with address: ${request.object.get(
                    "address"
                )} and tokenId: ${request.object.get("tokenId")} found.`
            )
        }
    }
})

Moralis.Cloud.afterSave("ItemBought", async (request) => {
    const confirmed = request.object.get("confirmed")
    const logger = Moralis.Cloud.getLogger()
    logger.info(`Marketplace | Object: ${request.object}`)
    if (confirmed) {
        const ActiveItem = Moralis.Object.extend("ActiveItem")
        const query = new Moralis.Query(ActiveItem)
        query.equalTo("marketplaceAddress", request.object.get("address"))
        query.equalTo("nftAddress", request.object.get("nftAddress"))
        query.equalTo("tokenId", request.object.get("tokenId"))
        logger.info(`Marketplace | Query: ${query}`)
        const boughtItem = await query.first()
        logger.info(`Marketplace | boughtItem: ${JSON.stringify(boughtItem)}`)
        if (boughtItem) {
            logger.info(`Deleting boughtItem ${boughtItem.id}`)
            await boughtItem.destroy()
            logger.info(
                `Deleted item with tokenId ${request.object.get(
                    "tokenId"
                )} at address ${request.object.get(
                    "address"
                )} from ActiveItem table since it was bought.`
            )
        } else {
            logger.info(
                `No item bought with address: ${request.object.get(
                    "address"
                )} and tokenId: ${request.object.get("tokenId")} found`
            )
        }
    }
})

The course offered an option to push the cloud functions to Moralis v1 with the following terminal command:

moralis-admin-cli watch-cloud-folder XXXXXXXXXXXXXXX --moralisSubdomain XXXXXXXXXXXX.grandmoralis.com --autoSave 1 --moralisCloudfolder ./cloudFunctions

But this can’t be done anymore with Moralis v2 so for the self hosted parse server of Moralis v2, I have moved these cloud functions to the Parse server repo available there: https://github.com/MoralisWeb3/Moralis-JS-SDK/tree/main/demos/parse-server-migration
I modified the cloud functions by basically replacing Moralis Parse in the following file: parse-server-migration/src/cloud/main.ts and as shown below:

main.ts

declare const Parse: any;

import './generated/evmApi';
import './generated/solApi';
import { requestMessage } from '../auth/authService';
const ethers = require('ethers');

Parse.Cloud.define('requestMessage', async ({ params }: any) => {
  const { address, chain, networkType } = params;

  const message = await requestMessage({
    address,
    chain,
    networkType,
  });

  return { message };
});

Parse.Cloud.define('getPluginSpecs', () => {
  // Not implemented, only excists to remove client-side errors when using the moralis-v1 package
  return [];
});

Parse.Cloud.define('getServerTime', () => {
  // Not implemented, only excists to remove client-side errors when using the moralis-v1 package
  return null;
});

Parse.Cloud.afterSave('ItemListed', async (request: any) => {
  // Every event gets triggered twice, once on unconfirmed, again on confirmed
  const confirmed = request.object.get('confirmed');
  const logger = Parse.Cloud.getLogger();
  logger.info('Looking for confirmed TX...');
  if (confirmed) {
    logger.info('Found item');
    const ActiveItem = Parse.Object.extend('ActiveItem');

    const query = new Parse.Query(ActiveItem);
    query.equalTo('nftAddress', request.object.get('nftAddress'));
    query.equalTo('tokenId', request.object.get('tokenId'));
    query.equalTo('marketplaceAddress', request.object.get('address'));
    query.equalTo('seller', request.object.get('seller'));
    const alreadyListedItem = await query.first();
    if (alreadyListedItem) {
      logger.info(`Deleting already listed ${request.object.get('objectId')}`);
      await alreadyListedItem.destroy();
      logger.info(
        `Deleted item with tokenId ${request.object.get('tokenId')} at address ${request.object.get(
          'address',
        )} since it's already been listed`,
      );
    }

    const activeItem = new ActiveItem();
    activeItem.set('marketplaceAddress', request.object.get('address'));
    activeItem.set('nftAddress', request.object.get('nftAddress'));
    activeItem.set('price', request.object.get('price'));
    activeItem.set('tokenId', request.object.get('tokenId'));
    activeItem.set('seller', request.object.get('seller'));
    logger.info(`Adding address: ${request.object.get('address')}. TokenId: ${request.object.get('tokenId')}`);
    logger.info('Saving');
    await activeItem.save();
  }
});

Parse.Cloud.afterSave('ItemCanceled', async (request: any) => {
  const confirmed = request.object.get('confirmed');
  const logger = Parse.Cloud.getLogger();
  logger.info(`Marketplace | Object: ${request.object}`);
  if (confirmed) {
    const ActiveItem = Parse.Object.extend('ActiveItem');
    const query = new Parse.Query(ActiveItem);
    query.equalTo('marketplaceAddress', request.object.get('address'));
    query.equalTo('nftAddress', request.object.get('nftAddress'));
    query.equalTo('tokenId', request.object.get('tokenId'));
    logger.info(`Marketplace | Query: ${query}`);
    const canceledItem = await query.first();
    logger.info(`Marketplace | CanceledItem: ${canceledItem}`);
    if (canceledItem) {
      logger.info(
        `Deleting ${request.object.get('tokenId')} at address: ${request.object.get('address')} since it was canceled`,
      );
      await canceledItem.destroy();
    } else {
      logger.info(
        `No item found with address ${request.object.get('address')} and tokenId: ${request.object.get('tokenId')}`,
      );
    }
  }
});

Parse.Cloud.afterSave('ItemBought', async (request: any) => {
  const confirmed = request.object.get('confirmed');
  const logger = Parse.Cloud.getLogger();
  logger.info(`Marketplace | Object: ${request.object}`);
  if (confirmed) {
    const ActiveItem = Parse.Object.extend('ActiveItem');
    const query = new Parse.Query(ActiveItem);
    query.equalTo('marketplaceAddress', request.object.get('address'));
    query.equalTo('nftAddress', request.object.get('nftAddress'));
    query.equalTo('tokenId', request.object.get('tokenId'));
    logger.info(`Marketplace | Query: ${query}`);
    const boughtItem = await query.first();
    logger.info(`Marketplace | BoughtItem: ${boughtItem}`);
    if (boughtItem) {
      logger.info(
        `Deleting ${request.object.get('objectId')} at address: ${request.object.get('address')} since it was bought`,
      );
      await boughtItem.destroy();
    } else {
      logger.info(
        `No item found with address ${request.object.get('address')} and tokenId: ${request.object.get('tokenId')}`,
      );
    }
  }
});

All I could get in my MongoDB collection was the smart contracts event logs in parse.NftmarketplaceEventsLogs but I am not getting the desired new tables rows about “ItemListed”, “ActiveItem”, “ItemCancelled”, and “ItemBought”.

Someone created a cloud function called “watchContractEvent” as per this forum thread Self-hosted Moralis server: local devchain & cloud functions and as shown below. I also tried to add it in my parse server repo in main.ts (parse-server-migration/src/cloud/main.ts) but no success to get the new DB table rows.

Parse.Cloud.define('watchContractEvent', async ({ params, user, ip }: any) => {
  let provider: any;
  if (params['chainId'] == 1337) {
    provider = new ethers.providers.WebSocketProvider('http://127.0.0.1:8545/');
  } else {
    provider = new ethers.providers.WebSocketProvider(process.env.GOERLI_RPC_WEBS!);
  }
  const contractAddress = params['address'];
  const contract = new ethers.Contract(contractAddress, [params['abi']], provider);
  if (params['tableName'] == 'ItemBought') {
    contract.on('ItemBought', (buyer: any, nftAddress: any, tokenId: any, price: any, event: any) => {
      const ItemBought = Parse.Object.extend('ItemBought');
      const itemBought = new ItemBought();
      itemBought.set('buyer', buyer);
      itemBought.set('nftAddress', nftAddress);
      itemBought.set('tokenId', tokenId);
      itemBought.set('price', ethers.utils.formatUnits(price, 6));
      itemBought.save();
    });
    return { success: true };
  }
  if (params['tableName'] == 'ItemListed') {
    contract.on('ItemListed', (seller: any, nftAddress: any, tokenId: any, price: any, event: any) => {
      const ItemListed = Parse.Object.extend('ItemListed');
      const itemListed = new ItemListed();
      itemListed.set('seller', seller);
      itemListed.set('nftAddress', nftAddress);
      itemListed.set('tokenId', tokenId);
      itemListed.set('price', ethers.utils.formatUnits(price, 6));
      itemListed.save();
    });
    return { success: true };
  }
  if (params['tableName'] == 'ItemCanceled') {
    contract.on('ItemCanceled', (seller: any, nftAddress: any, tokenId: any, event: any) => {
      const ItemCanceled = Parse.Object.extend('ItemCanceled');
      const itemCanceled = new ItemCanceled();
      itemCanceled.set('seller', seller);
      itemCanceled.set('nftAddress', nftAddress);
      itemCanceled.set('tokenId', tokenId);
      itemCanceled.save();
    });
    return { success: true };
  }
  return { success: false };
});
2 Likes

I forgot to mention that any help to resolve the above questions and make the code work for Moralis V2 would be greatly appreciated as many of us seem to be stuck with this.
Note that FCC course has 1.1M views on Youtube so it’d be very beneficial for the community and for Moralis to resolve this.

what is the main issue that you are having at this moment?

I am not able to get the new “ItemListed”, “ActiveItem”, “ItemCancelled”, and “ItemBought” tables in the MongoDB.

what is the difference between the table where it worked and the other tables?

There are new tables summarizing the NFTs activeItem, itemBought, itemCanceled, and itemListed.
I have attached snips from the course of what we were supposed to see in Moralis v1.


what information you have here in this table?

The only information I am currently getting in the self hosted MongoDB is coming from the Moralis web hook and it gives me the smart contract event log (parse.NftmarketplaceEventsLogs) for a NFT item I listed on the marketplace (see snip below).
But for example I am not getting the custom “itemListed” table which is supposed to be generated with the cloud functions.

my expectation is that similar to how the data for this table was added, you can add it for any other event in a separate table, after you receive the event, you check its topic, and based on the topic or address, you use the abi to extract the data, and then you insert the data in a table in mongo db

No the Moralis stream is already listening to all the contract events (see snip below).
I am talking about the new tables activeItem, itemBought, itemCanceled, and itemListed that are
generated from the cloud functions listed here.

that I have moved into the parse server file:

Note that these customized tables typically set key fields like buyer, nftAddress, tokenId, price etc… as shown below:

[quote=“Tristan3000, post:13, topic:20758”]

itemBought.set('buyer', buyer);
      itemBought.set('nftAddress', nftAddress);
      itemBought.set('tokenId', tokenId);
      itemBought.set('price', ethers.utils.formatUnits(price, 6));
      itemBought.save();

[/quote]

So you are using streams api here?

In this case you use the abi to decode the event I order to extract the fields and then you insert the data in mongondb.

At what step you are with those events from streams api?

There is also be a plugin for parse server and streams api.

streams API will work only with a chain that is supported, it will not work with a local devchain (just to clarify it)

here is more info about the plugin for parse server

Yep using stream API of course and it works fine thanks to the Moralis plugin and tutorial.
Yes indeed decoding the events is what is being done with the cloud functions listed above.
The whole question now is how to make these cloud functions work from Moralis v1 to Moralis v2?

1 Like

on what cloud functions you are referring?

These ones in updateActiveItems.js

maybe you can get more specific? like a simple example

So if you go back to the code pasted higher in the thread in updateActiveItems.js you can see the cloud functions, here is an example pasted below for “ItemListed” and “activeItem” tables.
Can you please advise what elements are missing in the stack to get these cloud functions to work with Moralis v2.

Moralis.Cloud.afterSave("ItemListed", async (request) => {
    const confirmed = request.object.get("confirmed")
    const logger = Moralis.Cloud.getLogger()
    logger.info("Looking for confirmed TX...")
    if (confirmed) {
        logger.info("Found item!")
        const ActiveItem = Moralis.Object.extend("ActiveItem")

        // In case of listing update, search for already listed ActiveItem and delete
        const query = new Moralis.Query(ActiveItem)
        query.equalTo("nftAddress", request.object.get("nftAddress"))
        query.equalTo("tokenId", request.object.get("tokenId"))
        query.equalTo("marketplaceAddress", request.object.get("address"))
        query.equalTo("seller", request.object.get("seller"))
        logger.info(`Marketplace | Query: ${query}`)
        const alreadyListedItem = await query.first()
        console.log(`alreadyListedItem ${JSON.stringify(alreadyListedItem)}`)
        if (alreadyListedItem) {
            logger.info(`Deleting ${alreadyListedItem.id}`)
            await alreadyListedItem.destroy()
            logger.info(
                `Deleted item with tokenId ${request.object.get(
                    "tokenId"
                )} at address ${request.object.get(
                    "address"
                )} since the listing is being updated. `
            )
        }

        // Add new ActiveItem
        const activeItem = new ActiveItem()
        activeItem.set("marketplaceAddress", request.object.get("address"))
        activeItem.set("nftAddress", request.object.get("nftAddress"))
        activeItem.set("price", request.object.get("price"))
        activeItem.set("tokenId", request.object.get("tokenId"))
        activeItem.set("seller", request.object.get("seller"))
        logger.info(
            `Adding Address: ${request.object.get("address")} TokenId: ${request.object.get(
                "tokenId"
            )}`
        )
        logger.info("Saving...")
        await activeItem.save()
    }
})