Accessing NFT metadata in Elrond smart contract without payable transfer

I’m working on an Elrond smart contract and need some advice on the best approach. My contract needs to process two NFTs and check their metadata properties to decide whether to mint a new token or not. Either way, the original NFTs should be returned to the user.

Currently I’m thinking about the gas costs and complexity. Instead of making users send their NFTs directly to the contract (using payable), would it be better to just pass the token IDs as parameters? Can a smart contract fetch NFT attributes directly using just the identifier?

It feels like there should be a more efficient way than transferring tokens back and forth every time. What’s the recommended pattern for this kind of metadata-based logic?

Nice challenge! I’ve been working on similar stuff and metadata access is definitely tricky.

Have you checked out the builtin functions for token metadata? There might be a way to query token attributes without receiving the tokens first, but I’m not sure if Elrond supports this directly in smart contracts.

One concern though - what happens if NFT metadata gets updated after your contract checks it but before minting? You worried about that race condition?

Also thinking about UX - keeping NFTs in user wallets sounds way better. But how do you verify they actually own those tokens? Got a verification plan?

Maybe try a hybrid approach? Users approve your contract to access their tokens without transferring, then you read metadata that way. Not sure if Elrond allows this but worth exploring.

What metadata properties are you checking? Rarity scores or something more complex? Might affect which approach works best.

The Problem: You are building an NFT smart contract that requires a whitelist feature allowing different addresses to mint varying maximum numbers of tokens. Manually setting hundreds of wallet addresses on-chain is prohibitively expensive in gas fees. You need a more gas-efficient solution to manage variable mint allowances.

TL;DR: The Quick Fix: Instead of storing all whitelist addresses and their mint limits on-chain, use an off-chain whitelist and verify eligibility using cryptographic signatures. This significantly reduces deployment costs and gas consumption during minting.

:thinking: Understanding the “Why” (The Root Cause):

Storing a large mapping of addresses and mint limits directly in your smart contract incurs high gas costs during deployment and increases contract size, leading to higher gas fees for all subsequent transactions. The solution lies in moving the whitelist data off-chain and using a mechanism to verify the validity of a user’s request without needing on-chain storage for every address. Cryptographic signatures provide a secure and gas-efficient way to achieve this.

:gear: Step-by-Step Guide:

Step 1: Generate Off-Chain Whitelist: Create a CSV or JSON file containing the whitelist addresses and their respective maximum mint amounts. This file will not be stored on-chain. Example JSON:

[
  {"address": "0x...", "maxMint": 2},
  {"address": "0x...", "maxMint": 5},
  // ...more entries
]

Step 2: Implement Signature Verification: Modify your smart contract to accept a cryptographic signature as proof of whitelist eligibility. This typically involves:

  • Whitelist Signer: Designate a private key (kept secure off-chain) to sign whitelist data.
  • Signed Message: Create a message including the user’s address and the desired mint amount. This message should be signed using the whitelist signer’s private key.
  • Contract Verification: In your smart contract, include a function that verifies the signature against the public key of the whitelist signer. This function should also check that the signed data matches the user’s address and requested mint amount, and that the requested mint amount is less than or equal to the user’s allowed maxMint from the off-chain whitelist. Libraries like ECDSA can help with this.
// Example using ECDSA library (replace with your chosen library)
function verifyWhitelist(address user, uint256 mintAmount, bytes memory signature, bytes32 messageHash) public view returns (bool) {
    // ...Fetch maxMint from off-chain data using an oracle or other mechanism
    uint256 maxMint = fetchMaxMint(user); //Implementation needed to fetch from off-chain
    if (mintAmount > maxMint) {
        return false;
    }

    address signer = ECDSA.recover(messageHash, signature);
    // ...Check if 'signer' is the designated whitelist signer address...
    // ...Check if the message hash corresponds to the user, mintAmount etc...
    return true;  // Returns true only if signature is valid AND mint amount <= maxMint
}


function mintNFT(address user, uint256 mintAmount, bytes memory signature, bytes32 messageHash) public {
    require(verifyWhitelist(user, mintAmount, signature, messageHash), "Not whitelisted or insufficient allowance");
    // ... Proceed with minting ...
}

Step 3: Client-Side Signing: On the client-side (frontend), before calling the mintNFT function, sign the message using the whitelist signer’s private key and pass the signature to the contract. Ensure secure handling of the private key.

:mag: Common Pitfalls & What to Check Next:

  • Security: Securely manage your whitelist signer’s private key. A compromised key could allow unauthorized minting. Consider using a multi-signature setup for enhanced security.
  • Off-chain Data Access: Decide how your contract will access the off-chain whitelist data. Options include oracles, IPFS, or a dedicated data feed. Carefully consider the reliability and gas costs associated with each approach. Ensure your method is robust and prevents manipulation.
  • Error Handling: Implement comprehensive error handling to catch invalid signatures, incorrect data, and other potential issues. Return informative error messages to the user.
  • Gas Optimization: Further optimize gas usage by using efficient data structures and algorithms within your contract.

:speech_balloon: Still running into issues? Share your (sanitized) config files, the exact command you ran, and any other relevant details. The community is here to help!

I encountered a similar situation last year, and the approach largely depends on how stringent your security needs to be. If you choose to pass only token IDs, it’s critical to verify ownership through blockchain queries, as this prevents users from passing IDs that they don’t own.

Regarding the race condition that Zoe mentioned, my strategy has been to check the metadata at the time of execution instead of relying on pre-validated data. It may incur a slight performance penalty but ensures consistency.

If your smart contract frequently processes the same NFTs, consider caching the metadata for those popular items. This can significantly reduce gas costs but remember to implement cache invalidation to address changes in metadata.

Have you explored using view functions for metadata checks? These functions are gas-free and could be useful for validation before minting.