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)
public
pure
returns (bytes32)
{
return
keccak256(
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)
public
pure
returns (address)
{
(bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature);
return ecrecover(_ethSignedMessageHash, v, r, s);
}
function splitSignature(bytes memory sig)
public
pure
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)))
}
}