Hi,
There is a pattern in many PFP projects where the tokenURI does something like:
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
}
Where the baseURI is some IPFS directory.
This is the standard implementation from OpenZeppelin v4 ERC721.sol and it's fine.
However, relying on this implementation along with random startingIndex generation and (inevitably) a setBaseURI method - negates the entire point of a fair and random distribution:
Since the startingIndex is not known beforehand, it is not possible to generate the mapping between the tokenId and the initialSequenceId.
This leads the devs to upload the metadata only after the startingIndex has been set.
The mapping formula for reference :
F: {0...MAX_TOKENS-1} -> {0...MAX_TOKENS-1}
F(tokenId) = initialSequenceId = (tokenId + startingIndex) % MAX_TOKENS
(depends on the constant startingIndex)
This presents us with some problems:
Primo, the devs and team are free to determine (aka Man-in-the-Middle) the order and allocation of the metadata (items in the collection) - the so-called "Original"/"Initial" sequence ID is meaningless.
Secundo, the metadata of the entire collection is not known before the sale ends which usually means the art itself will not be visible, unless otherwise exposed by the team - which requires additional and unnecessary efforts (see solution below). Take note that it is OK to expose the collection before and during sale - it only becomes a "metadata-leak" if the order in relation to the tokenId (aka mapping) is known as well.
Tertio, the devs are forced to implement a setBaseURI method - which allows them to defacto change the allocation of items in the collection - unless a locking mechanism is implemented.
Proposed Solution:
Follow these steps:
-
Generate the metadata (and art if this is a generative art project).
-
Establish the collection order using a computer RNG and enumerate the metadata (and art) files from
0toMAX_TOKENS - 1. If you want to do a late reveal and display a mystery image, create an additional metadata file named-1which will point to an IPFS link to a mystery image/video/file. -
Create the
provenanceHash(sha256 string of concatenation of sha256 strings of ordered art files/images). -
Upload the art directory to IPFS and update the metadata files to point to the correct IPFS directory link (or Arweave path manifest, whatever you use).
-
Upload the metadata directory to IPFS.
-
Deploy your ERC721 contract with the
baseURIvariable set to the IPFS dir of the metadata
and theprovenanceHashvariable set to the calculated sha256 string (you can use aconstvariable in Solidity or pass a constructor argument).
In your contract, implement tokenURI as follows:
function tokenURI(uint256 tokenId) public view virtual override(ERC721) returns (string memory) {
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
string memory baseURI = _baseURI();
string memory sequenceId;
if (startingIndex > 0) {
sequenceId = ( (tokenId + startingIndex) % MAX_TOKENS ).toString();
} else {
sequenceId = "-1";
}
return string(abi.encodePacked(baseURI, sequenceId));
}
Make sure your startingIndex cannot be set to 0!
In this manner, the original sequence order as well as the provenance hash are well-known at contract deployment time (enforced by IPFS/Arweave immutability), they have actual meaning and there is no need to implement setBaseURI (although you could if you want).
Or am I wrong? 
)
) should be enough for 99% of NFT projects to make them SCAM resistant (also by the dev team). Simple (semi) random assignment extension here: