I have an NFT staking contract that issues rewards based on the property rarity of the NFT staked. The four property tiers are 1,2,3 and 4.

When a user clicks stakeNFT(tokenId, nftTier), I use the tokenId to pull the metadata json property from IPFS and send the NFT tier to the smart contract.

The issue here is while this works on the user interface of our website, anyone can use Etherscan directly and input an tier that isn’t in their NFT so as to earn more rewards.

How do I verify the NFT tier based on the tokenId onchain?


1 Answer 1


You can extract in advance the tier of each tokenId and sign with the stacking contract's Owner account ( or a defined account) the messages containing tokenId and nftTier (I use ethersjs for this kind of tasks), in an external script. Maintain the signatures in a map, where the tokenId is the key and the signature is the associated value. Pass the signature as third parameter to the contract's stakeNFT method:

const signature = sigsMap.get(tokenId);
//etherjs call
let tx = await contract.stakeNFT(tokenId, nftTier, signature);
await tx.wait();

Inside the contract you can use the tokenId and nftTier parameters to recreate the signed message and extract the signer from the signature checking that is equal tothe contract's Owner account or the account designed for th signing. In this way the tokenids are related to the tier you have decided and nobody can modify this. The method will work only with the right signature and fail otherwise.

These are the updates to do on the contract code:

Global variable containing the signer address used to create the (tokenid, nftTier) signatures:

address _signer;    

Assign it the right value in the constructor or use a dedicated function to update it.

Require check to be inserted inside the updated nftStake method:

function stakeNFT(uint256 tokenId, uint256 nftTier, bytes calldata signature) public....{
require(verifyTier(tokenId, nftTier, signature), 'The tier data is not valid');

Code to be added to the stacking contract source:

//-------------------------- Signature verification --------------------------------------//
    function getMessageHashTier(
        uint256 _tokenid,
        uint256 _nftTier
    ) public pure returns (bytes32) {
        return keccak256(abi.encodePacked(_tokenid, _nftTier));

    function getEthSignedMessageHash(bytes32 _messageHash)
        returns (bytes32)

                abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)

    function verifyTier(
        uint256 _tokenid,
        uint256 _nftTier,
        bytes memory signature
    ) public view returns (bool) {
        bytes32 messageHash = getMessageHashTier(_tokenid, _nftTier);
        bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash);

        return recoverSigner(ethSignedMessageHash, signature) == _signer;

    function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature)
        returns (address)
        (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);

        return ecrecover(_ethSignedMessageHash, v, r, s);

    function splitSignature(bytes memory sig)
        returns (
            bytes32 r,
            bytes32 s,
            uint8 v
        require(sig.length == 65, "invalid signature length");

        assembly {

            r := mload(add(sig, 32))
            s := mload(add(sig, 64))
            v := byte(0, mload(add(sig, 96)))


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.