No transfer 'value' in EthNFTTransfers or Web3 API endpoint

Iā€™m using a query to get all transfer history for an NFT, which includes sales.

Iā€™m wondering why EthNFTTransfers does not contain a field ā€˜valueā€™ like found in EthTransactions.
I query EthNFTTransfers but I need to the sell price, which means I have to do a $lookup from EthTransactions based on tx hash. Thatā€™s fine, but add a bit of query load.

So why did you choose not to include the tx ā€˜valueā€™ in EthNFTTransfers?

This is the same when using Web3 API endpoint /nft/{address}/{token_id}/transfers, so using this API instead of a db query is not an option for me.

1 Like

Hi @matiyin

You have a specific task. Few people need this information. I will move your question to the ā€œsuggestionsā€ category :raised_hands:

@Yomoo I respectfully disagree and see value as part of a transaction and especially for NFTs. Most ā€˜NFT Transfersā€™ will have a value attached to it actually. Just wondering why itā€™s left out, makes no sense to me.
:woman_mage:

2 Likes

This is just my opinion. I do not decide what to include in the database and what not :sweat_smile:

1 Like

Hey @matiyin

The dev team studied the issue in more detail and came to this conclusion:

  • 1 transactions could contain 20 different logs operations. This one transaction generated 11 logs, 2 approvals 1 batched NFT 2 single nft and 1 erc20 transfer:

  • Looking at value of EthTransaction is not always correct because there is no standard way to specify the price of NFTs, one EthTransaction can do many things

  • In your case using lookup like he does now is at the moment the best solution. As you can already see, sometimes one NFT transcation ā‰  1 eth transcation

  • We will check in the future how to reliably bring price data to NFTTransfers(ŠøŠ·Š¼ŠµŠ½ŠµŠ½Š¾)

2 Likes

wow thanks for checking team!
I understand the issue. The transaction value on chain can be 1eth but that doesnā€™t in all cases mean this was the value of the NFT, since the contract could have other things at the same time.

I ended up using this if anyone is looking to list the transaction history of an NFT:

// get transaction history
Moralis.Cloud.define('getTransactionHistory', async (request) => {
  const query = new Moralis.Query(request.params.network+'NFTTransfers');
  query.equalTo('token_id', request.params.token_id);
  query.equalTo('token_address', request.params.token_address);
  query.equalTo('confirmed', true)
  query.descending('createdAt')
  const pipeline = [
    {
      lookup: {
        from: request.params.network+'Transactions',
        let: { transaction_hash: '$transaction_hash' },
        pipeline: [
          { $match: { $expr: { $eq: [ '$$transaction_hash',  '$hash' ] } }},
          { $project: { value: 1 }}
        ],
        as: 'value'
      }
    },
    {
      project: {
        value: { $first: '$value.value' },
        to_address: 1,
        from_address: 1,
        createdAt: 1,
        transaction_hash: 1,
      }
    },
  ]
  const results = await query.aggregate(pipeline);
  let items = []
  if (results) {
    results.reverse() // for some reason query order is reversed by pipeline
    // add user data
    for (let i = 0; i < results.length; i++) {
      const receiverQuery = new Moralis.Query(Moralis.User)
      receiverQuery.equalTo('accounts', results[i].to_address)
      const receiver = await receiverQuery.first({useMasterKey:true})
      const senderQuery = new Moralis.Query(Moralis.User)
      senderQuery.equalTo('accounts', results[i].from_address)
      const sender = await senderQuery.first({useMasterKey:true})
      if (sender) results[i].sender = { username: sender.attributes.username, avatar: sender.attributes.avatar ? sender.attributes.avatar.url() : null }
      if (receiver) results[i].receiver = { username: receiver.attributes.username, avatar: receiver.attributes.avatar ? receiver.attributes.avatar.url() : null }
      items.push(results[i])
    }
  }
  return items
},{
  fields : ['network','token_id','token_address'],
  requireUser: true 
})

Note that I still add the User data after the pipeline, sadly getting the right User data doesnā€™t work fully in such a pipeline. But it works, just wondering if it matters at all in performance. For my use case totally fine.

See here:

By looking at the value, to_address and from_address you can see if a transaction was a sale, normal transfer, mint (from 0x000ā€¦) or burn (to 0x000ā€¦).

1 Like

Great job @matiyin

Iā€™ll figure it out today with the pipelines. :man_mechanic:

1 Like