ZkRoninsNft.sol

NOTE: this part of the contract is not upgradable as its intended purpose was clear to us.

ZkRoninsNft.sol is the main contract, meaning: every needed functionality to have a working ERC721 contract is included in this file. At first sight this does not look to be the case as it's very minimalistic, but is done through the practice of abstraction and inheritance.

To prevent ourselves from reinventing the wheel and since the ERC721A.sol contract already has most of the base functionality for an NFT project, we've made sure to use as much of their provided functions as possible and only override those necessary to tailor to our needs.

The functions we've added ourselves were mostly for streamlining the mint process, the handling of funds, the inclusion of royalty standards (ERC2981) and our advanced storage and retrieval of metadata. Next - without diving into the latter - follows a summary of the contract's code and what we've changed.

Breakdown

  • ERC Standard inheritance and overrides

    • Override: the default value of the start token ID is set to 1 (instead of the default: 0, irreversible).

    • Override: the name and symbol of the NFT project is set to zkRonins and ZRO (irreversible).

    • Setting and getting of royalty info (implementation of ERC2981).

    • In the mint function we leverage the _safeMint function of OpenZeppelin (ERC721).

    • Owning, approving and transferring of NFTs (ERC721).

  • Streamlining the mint process

    • Setting and getting the mint date.

    • Setting and getting the mint price.

    • Setting and getting whether the mint is active or not.

    • Setting and getting the number of NFTs a wallet is allowed to mint (0 = no limit).

    • Setting and getting the number of NFTs a wallet can mint per transaction (0 = no limit).

    • Bypassing the mint date (see why in the Transparency and Security section below).

  • Handling of funds

    • Reverts the mint transaction if the request is invalid (see Transparency and Security below).

    • Funds are stored inside of the contract for transparency. Only the contract owner is allowed to make withdrawals.

  • Third-party Compliancy (Additional functions to comply with the interfaces of third-parties)

    • Thirdweb's SDK needs nextTokenIdToMint to be present in the contract. This is the equivalent of _currentIndex found in the ERC721A.sol contract. We added it as a simple get-function. In the near future, we'll likely step away from using Thirdweb and use our own infrastructure.

    • zkMarkets' requires mintPrice and mintDate to be called publicPrice and publicSaleStartTime to comply with their Launchpad's ABI. Here we could have chosen to rename our variables instead of creating separate functions, but chose the latter for consistency and conciseness (See our Code of Conduct).

Transparency and Security

  • zkSync's block.timestamp issue At the time of writing, block.timestampon zkSync Era returns L1 timestamps instead of L2 timestamps. Due to this technical constraint and to ensure a timely and accurate launch, we have the option to bypass the mint date check. For more information, see discussion: https://github.com/zkSync-Community-Hub/zkync-developers/discussions/32.

// truncated version of actual code from the contract
function mint(uint8 quantity) external payable {
  ...
  if(!bypassDate) {
    ...
    require(block.timestamp >= mintDate, "The minting phase has not started yet.");
  }
  ...
}
  • Validating the mint

    To be sure that someone is only allowed to mint when it's possible, require-statements are introduced into the mint-function. Here follows, in pseudo-code, what the requirements for a valid mint are. The sequence of instructions is one-to-one with the order of the actual code.

// pseudo-code for minting
mint(quantity) {
    require(mint is active)
    if mint date is not bypassed then require(block time is >= mint date)
    if max per wallet is not 0 then require(owned + quantity is <= max per wallet)
    if max per mint is not 0 then require(quantity is <= max per mint)
    require(total minted + quantity is <= max total supply)
    require(funds sent is >= quantity * mint price)
    
    // if any require() fails then the transaction reverts else it's safe to mint!
    safe_mint(quantity)
}
  • Withdrawing funds Minting fees are stored within the contract. This requires a withdrawal by the contract owner to acquire funds.

// actual code from the contract
function withdraw() external onlyOwner {
    Address.sendValue(payable(msg.sender), _self.balance);
}

Our approach to metadata handling will be covered in ZkRoninsMeta.sol

Last updated