For others that run into the same ‘breaking’ issue and want the NFTOwners table back, here’s what I did:
Use EthNFTTransfers table to sync mints and transfers in NFTOwners table
Run a beforeSave hook in cloud functions to catch NFT transactions from connected wallets.
Note: I have a marketplace with several Collections (ERC721 contracts) so I first check if the transfer is from a ‘white listed’ nft collection in my system.
// set <chain>NFTOwners table to track minted and transferred NFTs
Moralis.Cloud.beforeSave('EthNFTTransfers', async (request) => {
logger.info('process EthNFTTransfers')
logger.info(JSON.stringify(request.object))
// process transfer if tx confirmed and not a Burn
if (request.object.get('confirmed') && request.object.get('to_address') != '0x0000000000000000000000000000000000000000') {
const network = 'Eth'
const chain = 'rinkeby'
const chainId = '0x4'
const apiKey = '<your_moralis_web3_api_key>'
// check if token is part of app Collections
const getCollections = new Moralis.Query('Collection')
getCollections.equalTo('network', chainId)
getCollections.equalTo('isDeployed', true)
getCollections.ascending('menu_order')
const collections = await getCollections.find({useMasterKey:true})
if (collections) {
const collection = collections.filter(collection => collection.token_contract === request.object.get('token_address'))
if (collection) {
// add minted NFT to NFTOwners table
if (request.object.get('from_address') === '0x0000000000000000000000000000000000000000') {
const NFTOwners = Moralis.Object.extend(network+'NFTOwners')
const newNFT = new NFTOwners()
// fetch token details from moralis web3 api
const options = {
token_address: request.object.get('token_address'),
token_id: request.object.get('token_id'),
chain: chain,
}
Moralis.Cloud.httpRequest({
url: `https://deep-index.moralis.io/api/v2/nft/${options.token_address}/${options.token_id}`,
params: {chain: options.chain},
headers: {
accept: 'application/json',
"X-API-Key": apiKey,
},
})
.then((response) => {
logger.info(JSON.stringify(response.data))
if (response.data) {
// save NFT data to NFTOwners
newNFT.set('token_id', response.data.token_id)
newNFT.set('token_address', response.data.token_address)
newNFT.set('owner_of', response.data.owner_of)
newNFT.set('token_uri', response.data.token_uri)
newNFT.set('name', response.data.name)
newNFT.set('symbol', response.data.symbol)
newNFT.set('block_number', response.data.block_number_minted)
newNFT.set('contract_type', response.data.contract_type)
newNFT.save(null, {useMasterKey:true})
// }
}, function(error) {
logger.info('error getting NFT data from web3 api')
logger.info(error)
})
} else {
// change owner_of if NFT was transferred to other wallet
const query = new Moralis.Query(network+'NFTOwners')
query.equalTo('token_id', request.object.get('token_id'))
query.equalTo('token_address', request.object.get('token_address'))
const NFTOwnerItem = await query.first({useMasterKey:true})
if (NFTOwnerItem) {
NFTOwnerItem.set('owner_of', request.object.get('to_address'))
NFTOwnerItem.save(null, {useMasterKey:true})
logger.info('transferred to new owner '+request.object.get('to_address'))
}
}
}
}
}
})
Add a Cloud Job that you can run to do import existing NFTs as NFTOwners
I use this when I migrate to a new server instance and want to manually import existing NFTs from white listed Collections. Also handy for repairing bugs. I don’t run it on a schedule, but it’s an option if you want an additional backup check to process historical.
// add NFTOwners table from deployed contracts
Moralis.Cloud.job('addNFTOwners', async (request) => {
logger.info('Running addNFTOwners job')
const getCollections = new Moralis.Query('Collection')
getCollections.equalTo('network', request.params.input.chainId)
getCollections.equalTo('isDeployed', true)
getCollections.ascending('menu_order')
const collections = await getCollections.find({useMasterKey:true})
if (collections) {
collections.forEach(collection => {
const options = {
token_address: collection.get('token_contract'),
chain: request.params.input.chain,
}
Moralis.Cloud.httpRequest({
url: `https://deep-index.moralis.io/api/v2/nft/${options.token_address}/owners`,
params: {chain: options.chain},
headers: {
accept: "application/json",
"X-API-Key": "<your_moralis_web3_api_key>",
},
})
.then(async (response) => {
if (response.data.result.length) {
for (let i = 0; i < response.data.result.length; i++) {
const nft = response.data.result[i];
const NFTOwners = Moralis.Object.extend(request.params.input.network+'NFTOwners')
// check if exists
const query = new Moralis.Query(request.params.input.network+'NFTOwners')
query.equalTo('token_id', nft.token_id)
query.equalTo('token_address', nft.token_address)
const itemExists = await query.first({ useMasterKey: true })
// save item to NFTOwners
if (!itemExists) {
const item = new NFTOwners();
item.set('token_id', nft.token_id)
item.set('token_address', nft.token_address)
item.set('owner_of', nft.owner_of)
item.set('token_uri', nft.token_uri)
item.set('name', nft.name)
item.set('symbol', nft.symbol)
item.set('block_number', nft.block_number)
item.set('contract_type', nft.contract_type)
item.save(null, {useMasterKey:true})
}
}
}
}, function(error) {
logger.info('error getting NFTs in contract from web3 api')
logger.info(error)
});
});
}
})
Because I use a curated collection, I’d like to have the NFT and meta in my db for speed, caching, reliability and storing custom data for my system. Moreover, it takes less resources from servers.
Alternatively you could run the cloud Job on a schedule, instead of using EthNFTTransfers
.