contract MonsterBlocks is MonsterBlockCoreERC721, VRFConsumerBase {
  using Counters for Counters.Counter;
  Counters.Counter private _tokenIds;

  uint256 public totalMinted = 0;
  uint256 public totalMintedByOwner = 0;

  IUniswapV2Router02 public uniswapRouter;

  address private VRFCoordinator = 0xf0d54349aDdcf704F77AE15b96510dEA15cb7952;
  address private LinkToken = 0x514910771AF9Ca656af840dff83E8264EcF986CA;
  bytes32 internal keyHash = 0xAA77729D3466CA35AE8D28B3BBAC7CC36A5031EFDC430821C02BC31A238AF445;
  uint256 internal LinkFee = 2 * 10**18; // 2 LINK

  uint256 public constant NFTMintPrice = 55000000000000000; // 0.055 ETH
  uint256 public constant maxSupply = 10000;
  uint256 public constant maxOwnerMintedSupply = 50;
  uint256 public constant maxMintLimit = 20;

  uint256 public constant numberOfTraits = 6;
  uint256 internal constant traitWeighting = 1000;

  uint8[][numberOfTraits] internal traitProbabilities;
  string[numberOfTraits] internal traitCategories;
  string[][numberOfTraits] internal traitNames;

  // DNA is the root random number generated by Chainlink
  mapping(uint256 => uint256) public monsterBlocksDna;
  mapping(bytes32 => uint256[]) internal vrfRequestIds;
  mapping(uint256 => uint256[]) internal stacks;
  mapping(uint256 => bool) internal baseBrickFlourishOverrides;
  mapping(uint256 => bool) internal middleBrickFlourishOverrides;
  mapping(uint256 => bool) internal topBrickFlourishOverrides;
  mapping(address => uint256) internal amountPreMintableByAddress;

  /* Pre-Minting
     Reward your earliest supporters! */

  event PreMintingPaused(address account);
  event PreMintingUnpaused(address account);
  bool private _preMintingPaused;

  function preMintingPaused() public view virtual returns (bool) {
      return _preMintingPaused;
  }

  function pausePreMinting() public onlyOwner {
      _preMintingPaused = true;
      emit PreMintingPaused(_msgSender());
  }

  function unpausePreMinting() public onlyOwner {
      _preMintingPaused = false;
      emit PreMintingUnpaused(_msgSender());
  }

  function preMintMonsterBlock(uint256 _quantity, uint256 _deadline) external payable nonReentrant {
    require(!preMintingPaused(), "Pre-minting paused");
    require(totalMinted + _quantity < maxSupply + 1, "Not enough left");
    require(_quantity < amountPreMintableByAddress[msg.sender] + 1, "Cannot pre-mint this many");
    require(NFTMintPrice * _quantity <= msg.value, "Not enough ETH");

    amountPreMintableByAddress[msg.sender] -= _quantity;

    customerMintMonsterBlock(_quantity, _deadline);
  }

  function pushPreMinters(address[] memory _preMinters, uint256[] memory _amounts) external onlyOwner whenPaused {
    require(_preMinters.length == _amounts.length);

    for(uint8 i = 0; i < _amounts.length; i++) {
      amountPreMintableByAddress[_preMinters[i]] = amountPreMintableByAddress[_preMinters[i]] + _amounts[i];
    }
  }

  function getAmountPreMintable(address _userAddress) public view returns (uint256 amountPreMintable) {
    amountPreMintable = amountPreMintableByAddress[_userAddress];
  }

  /* Owner Minting
     Allow a small number of pre-mints without ETH payment required */

  function ownerMintMonsterBlocks(uint256 _quantity) external onlyOwner {
    require(totalMinted + _quantity < maxSupply + 1, "Not enough left");
    require(totalMintedByOwner + _quantity <= maxOwnerMintedSupply, "Not enough left");
    require(_quantity < maxMintLimit + 1, "Max mint is 20");

    totalMintedByOwner += _quantity;

    mintMonsterBlock(_quantity);
  }

  /* Regular Minting */

  event GenerateMonsterBlock(
    uint256 tokenId
  );

  event UpdateMonsterBlock(
    uint256 indexed id,
    uint256 dna
  );

  function generateMonsterBlock(uint256 _quantity, uint256 _deadline) external payable whenNotPaused nonReentrant {
    require(totalMinted + _quantity < maxSupply + 1, "Not enough left");
    require(_quantity < maxMintLimit + 1, "Max mint is 20");
    require(NFTMintPrice * _quantity <= msg.value, "Not enough ETH");

    customerMintMonsterBlock(_quantity, _deadline);
  }

  function customerMintMonsterBlock(uint256 _quantity, uint256 _deadline) internal {
    /* Every Chainlink call can be used to generate many random numbers
       However, the gas limit for callbacks is 200k
       To balance cost with reliability, use one Chainlink call for every 5 mints */
    uint256 vrfRequestsNeeded = (_quantity + 4) / 5;

    address[] memory path = new address[](2);
    path[0] = uniswapRouter.WETH();
    path[1] = LinkToken;
    uniswapRouter.swapETHForExactTokens { value: msg.value } (LinkFee * vrfRequestsNeeded, path, address(this), _deadline);

    require(LINK.balanceOf(address(this)) >= LinkFee * vrfRequestsNeeded, "Not enough LINK");

    mintMonsterBlock(_quantity);
  }

  function mintMonsterBlock(uint256 _quantity) internal {
    totalMinted += _quantity;

    bytes32 requestId;
    for (uint8 i = 0; i < _quantity; i++) {
      _tokenIds.increment();

      if (i % 5 == 0) { // New Chainlink call for every five mints
        requestId = requestRandomness(keyHash, LinkFee);
      }
      vrfRequestIds[requestId].push(_tokenIds.current());

      _safeMint(msg.sender, _tokenIds.current());

      stacks[_tokenIds.current()] = [_tokenIds.current()];

      emit GenerateMonsterBlock(_tokenIds.current());
    }
  }

  /* Stacking
     Choose a top and bottom block (or tower),
     burn them and create a new tower */

  event StackMonsterBlock(
    uint256 tokenId1,
    uint256 tokenId2,
    uint256 resultTokenId
  );

  function engageStackinator(uint256 _bottomToken, uint256 _topToken) external nonReentrant {
    require(_exists(_bottomToken), "Bottom token does not exist");
    require(_exists(_topToken), "Top token does not exist");
    require(_isApprovedOrOwner(msg.sender, _bottomToken), "Not approved for bottom token");
    require(_isApprovedOrOwner(msg.sender, _topToken), "Not approved for top token");
    require(_bottomToken != _topToken, "Tokens cannot be the same");
    require(stacks[_bottomToken].length > 1 || monsterBlocksDna[_bottomToken] != 0, "No traits for bottom token yet");
    require(stacks[_topToken].length > 1 || monsterBlocksDna[_topToken] != 0, "No traits for top token yet");

    _tokenIds.increment();

    _burn(_bottomToken);
    _burn(_topToken);

    _safeMint(msg.sender, _tokenIds.current());

    for (uint8 i = 0; i < stacks[_bottomToken].length; i++) {
      stacks[_tokenIds.current()].push(stacks[_bottomToken][i]);
    }
    for (uint8 i = 0; i < stacks[_topToken].length; i++) {
      stacks[_tokenIds.current()].push(stacks[_topToken][i]);
    }

    emit StackMonsterBlock(_bottomToken, _topToken, _tokenIds.current());
  }

  /* Constructor */

  constructor(address _withdrawContract, address _giveWellAddress, address _charityWalletAddress)
    ERC721("MonsterBlocks", "MONSTERBLOCK")
    VRFConsumerBase(VRFCoordinator, LinkToken)
  {
    uniswapRouter = IUniswapV2Router02(0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D);

    withdrawContract = _withdrawContract;
    giveWellAddress = _giveWellAddress;
    charityWalletAddress = _charityWalletAddress;

    // Start in paused sale state
    pauseSale();
    pausePreMinting();

    setBaseURI('https://api.monsterblocks.io/metadata/');

    traitProbabilities[0] = [1, 4, 10, 17, 20, 4, 17, 17, 17, 17, 4, 17, 17, 20, 17, 17, 17, 10, 17, 17, 10, 17, 17, 3, 17, 17, 10, 17, 20, 17, 17, 17, 17, 17, 8, 17, 19, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 2, 17, 8, 17, 17, 17, 10, 17, 17, 17, 17, 17, 17, 17, 17, 17, 17, 3, 17, 17, 1];
    traitCategories[0] = "Base Brick";

    traitProbabilities[1] = [12, 10, 3, 10, 12, 12, 7, 13, 14, 12, 12, 12, 7, 12, 10, 12, 12, 15, 4, 12, 12, 6, 12, 2, 2, 12, 12, 12, 12, 12, 12, 12, 12, 10, 15, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 15, 12, 12, 12, 12, 12, 15, 12, 12, 12, 12, 12, 15, 12, 12, 12, 12, 15, 6, 3, 12, 12, 12, 15, 12, 6, 12, 15, 12, 12, 12, 12, 14, 6, 12, 12, 12, 8, 12, 12, 15, 14, 14];
    traitCategories[1] = "Middle Brick";

    traitProbabilities[2] = [2, 12, 10, 4, 10, 12, 12, 7, 12, 14, 12, 12, 12, 7, 12, 10, 12, 12, 14, 4, 12, 12, 6, 12, 4, 4, 12, 12, 12, 12, 12, 12, 12, 12, 10, 14, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 14, 12, 12, 12, 12, 12, 14, 12, 12, 12, 12, 12, 14, 12, 12, 12, 12, 13, 6, 3, 12, 12, 12, 14, 12, 6, 12, 15, 12, 12, 12, 12, 14, 6, 12, 12, 12, 8, 12, 12, 15, 15, 15];
    traitCategories[2] = "Top Brick";

    traitProbabilities[3] = [19, 19, 19, 19, 19, 22, 19, 19, 3, 19, 15, 19, 20, 22, 19, 19, 19, 19, 22, 15, 19, 19, 19, 3, 19, 19, 22, 10, 3, 3, 19, 19, 19, 9, 19, 22, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 2, 22, 19, 19, 19, 9, 19, 19, 22, 10, 19, 22, 0];
    traitCategories[3] = "Base Flourish";

    traitProbabilities[4] = [19, 19, 19, 19, 19, 22, 19, 19, 3, 19, 15, 19, 20, 22, 19, 19, 19, 19, 22, 15, 19, 19, 19, 3, 19, 19, 22, 10, 3, 3, 19, 19, 19, 9, 19, 22, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 2, 22, 19, 19, 19, 9, 19, 19, 22, 10, 19, 22, 0];
    traitCategories[4] = "Middle Flourish";

    traitProbabilities[5] = [19, 19, 19, 19, 19, 22, 19, 19, 3, 19, 15, 19, 20, 22, 19, 19, 19, 19, 22, 15, 19, 19, 19, 3, 19, 19, 22, 10, 3, 3, 19, 19, 19, 9, 19, 22, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 2, 22, 19, 19, 19, 9, 19, 19, 22, 10, 19, 22, 0];
    traitCategories[5] = "Top Flourish";

    traitNames[0] = [
      'Multivac',
      'Purple Hallucinogenic Frog',
      'Green Hallucinogenic Frog',
      'A Shrewd Mind',
      'Sorrowful Wyvern',
      'Golden Sorrowful Wyvern',
      'Blue Bull',
      'Pink Bull',
      'Crawling Skull',
      'Fly Statue',
      'Frothy Waterkin',
      'Currish Lifter',
      'Craven Maltworm',
      'Mammering Moldwarp',
      'Doghearted Hermit',
      'Saucy Measle',
      'Salacious Lewdster',
      'Golden Vigilant Gator',
      'Vigilant Gator',
      'Primordial Pigdog',
      'The Butcher',
      'Gnarly Vegetation',
      'Unwanted Octopus',
      'Albino Neighborly Chameleon',
      'Neighborly Chameleon',
      'Red Squeamish Behemoth',
      'Stone Squeamish Behemoth',
      'Turquoise Squeamish Behemoth',
      'Orange Lethargic Giraffe',
      'Green Lethargic Giraffe',
      'Green Pensive Serpent',
      'Blue Pensive Serpent',
      'Green Beleaguered Troll',
      'Orange Beleaguered Troll',
      'Gold Contemplative Grizzly',
      'Stone Contemplative Grizzly',
      'Burdened Hobgoblin',
      'Turquoise Militarized Rodent',
      'Amber Militarized Rodent',
      'Concrete Militarized Rodent',
      'Shielded Goblin',
      'Welcoming Demon',
      'Blue Paralyzed Bull',
      'Brown Paralyzed Bull',
      'Yellow Weaponized Hare',
      'Green Weaponized Hare',
      'Obedient Reptile',
      'Red Phillip',
      'Somber Ouroboros',
      'Degenerate Collector',
      'Despondent Skeleton',
      'Merry Beast',
      'Guffawing Skull',
      'Golden Guffawing Skull',
      'Gleeking Footlicker',
      'Gorbellied Varlet',
      'Frothy Wagtail',
      'Red Lumpish Vassal',
      'Grey Lumpish Vassal',
      'Batfowl Statue',
      'Milk Livered Scoundrel',
      'Paunchy Chum',
      'Boxed Cat',
      'Masked Troll',
      'Henrique',
      'Frogwig',
      'Flexible Menace',
      'Shameful Troll'
    ];

    traitNames[1] = [
      'Welcoming Palms',
      'Volcanic Eruption',
      'Vile Spider',
      'Sluggish Tiger',
      'Peculiar Seahorse',
      'Intimidating Amphibian',
      'Hostile Beetle',
      'Golden Hound',
      'Extinct Losers',
      'Chunky Shark',
      'Brown Hound',
      'Cantankerous Snapper',
      'Bodacious Twins',
      'Paranoid Rex',
      'Meek Triceratops',
      'Contemplative Kiwi',
      'Haughty Eagle',
      'Squished Elephant',
      'Jaded Cellmates',
      'Angler Fish',
      'Counterfeit Sphinx',
      'Happy Meal Crown',
      'Primitive Salamander',
      'Trifling Hornrat',
      'Ectoplasm',
      'Red Grisly Harpy',
      'Green Grisly Harpy',
      'Stone Grisly Harpy',
      'Yellow Frantic Coyote',
      'Blue Frantic Coyote',
      'Stone Petrified Owl',
      'Amber Petrified Owl',
      'Burrowing Reptile',
      'Purple Space Walrus',
      'Stone Space Walrus',
      'Jolly Martian',
      'Cement Irate Swine',
      'Red Irate Swine',
      'Plague Rat',
      'Wooden Crate',
      'Red Euphoric Pond Scum',
      'Green Euphoric Pond Scum',
      'Red Pipsqueak',
      'Blue Pipsqueak',
      'Worried Tiki',
      'Sedated Ogre',
      'Medicated Druid',
      'Yellow Ecstatic Shrew',
      'Green Ecstatic Shrew',
      'Green Prancing Lurker',
      'Stone Prancing Lurker',
      'Forlorn Shaman',
      'Marbled Moth',
      'Piggish Vicar',
      'Red Paltry Soothsayer',
      'Green Paltry Soothsayer',
      'Red Moon Hound',
      'Yellow Moon Hound',
      'Unsightly Vermin',
      'Anonymous Eyeball',
      'Weather Satellite',
      'Treasure Chest',
      'Thingamajig',
      'Tesla Coil',
      'Supreme Cyborg',
      'Precious Crystals',
      'Ice Cube',
      'Decommissioned Cyborg',
      'Comet Kohoutek',
      'Boring Columns',
      'Log',
      'Quizzical Pig',
      'Omnipotent Mutant',
      'Psychedelic Bison',
      'Scrunched Brain',
      'Concerned Loaf',
      'Uncultured Swine',
      'Cyclone',
      'Barrel of Fungible Tokens',
      'Blue Odious Bird',
      'Clumsy Eavesdropper',
      'Disembodied Mouth',
      'Just Some Bricks',
      'Metallic Odious Bird',
      'Putrid Underling',
      'Useless Machine',
      'Irritated Aardvark',
      'Hungry Komodo'
    ];
    traitNames[2] = [
      'Gold Bullion',
      'Welcoming Palms',
      'Volcanic Eruption',
      'Vile Spider',
      'Sluggish Tiger',
      'Peculiar Seahorse',
      'Intimidating Amphibian',
      'Hostile Beetle',
      'Golden Hound',
      'Extinct Losers',
      'Chunky Shark',
      'Brown Hound',
      'Cantankerous Snapper',
      'Bodacious Twins',
      'Paranoid Rex',
      'Meek Triceratops',
      'Contemplative Kiwi',
      'Haughty Eagle',
      'Squished Elephant',
      'Jaded Cellmates',
      'Angler Fish',
      'Counterfeit Sphinx',
      'Happy Meal Crown',
      'Primitive Salamander',
      'Trifling Hornrat',
      'Ectoplasm',
      'Red Grisly Harpy',
      'Green Grisly Harpy',
      'Stone Grisly Harpy',
      'Yellow Frantic Coyote',
      'Blue Frantic Coyote',
      'Stone Petrified Owl',
      'Amber Petrified Owl',
      'Burrowing Reptile',
      'Purple Space Walrus',
      'Stone Space Walrus',
      'Jolly Martian',
      'Cement Irate Swine',
      'Red Irate Swine',
      'Plague Rat',
      'Wooden Crate',
      'Red Euphoric Pond Scum',
      'Green Euphoric Pond Scum',
      'Red Pipsqueak',
      'Blue Pipsqueak',
      'Worried Tiki',
      'Sedated Ogre',
      'Medicated Druid',
      'Yellow Ecstatic Shrew',
      'Green Ecstatic Shrew',
      'Green Prancing Lurker',
      'Stone Prancing Lurker',
      'Forlorn Shaman',
      'Marbled Moth',
      'Piggish Vicar',
      'Red Paltry Soothsayer',
      'Green Paltry Soothsayer',
      'Red Moon Hound',
      'Yellow Moon Hound',
      'Unsightly Vermin',
      'Anonymous Eyeball',
      'Weather Satellite',
      'Treasure Chest',
      'Thingamajig',
      'Tesla Coil',
      'Supreme Cyborg',
      'Precious Crystals',
      'Ice Cube',
      'Decommissioned Cyborg',
      'Comet Kohoutek',
      'Boring Columns',
      'Log',
      'Quizzical Pig',
      'Omnipotent Mutant',
      'Psychedelic Bison',
      'Scrunched Brain',
      'Concerned Loaf',
      'Uncultured Swine',
      'Cyclone',
      'Barrel of Fungible Tokens',
      'Blue Odious Bird',
      'Clumsy Eavesdropper',
      'Disembodied Mouth',
      'Just Some Bricks',
      'Metallic Odious Bird',
      'Putrid Underling',
      'Useless Machine',
      'Irritated Aardvark',
      'Hungry Komodo'
    ];

    string[59] memory flourishTraits = [
      'Stone Stern Fists',
      'Blue Stern Fists',
      'Orange Stern Fists',
      'Green Depressive Lizard',
      'Stone Depressive Lizard',
      'Vengeful Dagger',
      'Precious Little Antlers',
      'Prominent Rooster',
      'Gilded Wings',
      'Cretinous Gargoyle',
      'A Real Spark',
      'Royal Flourish',
      'Morning Star',
      'Lush Foliage',
      'Glorious Flappers',
      'Fairy Ears',
      'Expensive Gizmo',
      'Dragonly Wings',
      'Broad Sword',
      'Bear Claw',
      'Cool Hands',
      'Nice Flippers',
      'Feet',
      'Sumptuous Amethyst',
      'Primitive Clubs',
      'Enticing Antennae',
      'Raptor Claws',
      'Skeleton Shield',
      'Sick Flames',
      'Rad Axe',
      'Nice Leaves',
      'Mother Nature',
      'Decent Horns',
      'Detached Eyeball',
      'Impressive Pointers',
      'Butterfly Wings',
      'Fortified Spikes',
      'Skeletal Wings',
      'Tongue',
      'Tentacles',
      'Sumptuous Cactus',
      'Skinless Wings',
      'Satellite Dishes',
      'Roman Columns',
      'Ram Horns',
      'Ominous Cloud',
      'Night and Day',
      'Mounted Cannons',
      'Mortal Chameleon',
      'Helpful Hands',
      'Green Feelers',
      'Fishmonger Hands',
      'Disapproving Eyes',
      'Delightful Wings',
      'Crocodile Mouths',
      'Cheap Ghost',
      'Buzzsaws',
      'Blue Whiskers',
      'None'
    ];
    traitNames[3] = flourishTraits;
    traitNames[4] = flourishTraits;
    traitNames[5] = flourishTraits;

    for (uint256 i = 0; i < 25; i++) {
      baseBrickFlourishOverrides[i] = true;
    }

    for (uint256 i = 0; i < 25; i++) {
      middleBrickFlourishOverrides[i] = true;
    }

    for (uint256 i = 0; i < 26; i++) {
      topBrickFlourishOverrides[i] = true;
    }
  }

  /* Utility & Metadata */

  function latestSerialNumber() external view returns (uint256) {
    return _tokenIds.current();
  }

  function traitMetadata(uint256 _tokenId) external view returns (string memory) {
    require(stacks[_tokenId].length > 1 || monsterBlocksDna[_tokenId] != 0, "No DNA yet");

    string memory resultString = '{';
    for (uint8 i = 0; i < stacks[_tokenId].length; i++) {

      uint256[numberOfTraits] memory mintedTraits;
      for (uint8 j = 0; j < numberOfTraits; j++) {
        mintedTraits[j] = getMintedTrait(stacks[_tokenId][i], j);
      }

      // Override traits with no flourish
      if (baseBrickFlourishOverrides[mintedTraits[0]]) {
        mintedTraits[3] = traitProbabilities[3].length - 1;
      }
      if (middleBrickFlourishOverrides[mintedTraits[1]]) {
        mintedTraits[4] = traitProbabilities[4].length - 1;
      }
      if (topBrickFlourishOverrides[mintedTraits[2]]) {
        mintedTraits[5] = traitProbabilities[5].length - 1;
      }

      for (uint8 j = 0; j < numberOfTraits; j++) {
        if (i > 0 || j > 0) {
          resultString = strConcat(resultString, ', ');
        }
        resultString = strConcat(resultString, '"');
        resultString = strConcat(resultString, traitCategories[j]);
        resultString = strConcat(resultString, '": "');
        resultString = strConcat(resultString, traitNames[j][mintedTraits[j]]);
        resultString = strConcat(resultString, '"');
      }
    }

    resultString = strConcat(resultString, stackMetadata(_tokenId));

    resultString = strConcat(resultString, ', "Tower Height": "');
    resultString = strConcat(resultString, Strings.toString(stacks[_tokenId].length));
    resultString = strConcat(resultString, '"');

    return strConcat(resultString, '}');
  }

  function getMintedTrait(uint256 _tokenId, uint8 _traitIndex) internal view returns (uint256) {
    uint256 currentWeight = traitWeighting;
    uint256 dnaModuloWeight = (uint256(keccak256(abi.encode(monsterBlocksDna[_tokenId], _traitIndex + 1))) % currentWeight) + 1;

    for (uint8 j = 0; j < traitProbabilities[_traitIndex].length; j++) {
      currentWeight -= traitProbabilities[_traitIndex][j];
      if (dnaModuloWeight > currentWeight) {
        return j;
      }
    }

    return traitProbabilities[_traitIndex].length - 1;
  }

  function stackMetadata(uint256 _tokenId) internal view returns (string memory) {
    string memory resultString = '';

    if (stacks[_tokenId].length < 2) {
      return resultString;
    }

    for (uint8 i = 0; i < stacks[_tokenId].length; i++) {
      resultString = strConcat(resultString, ', "Token ');
      resultString = strConcat(resultString, Strings.toString(i + 1));
      resultString = strConcat(resultString, '": "');
      resultString = strConcat(resultString, Strings.toString(stacks[_tokenId][i]));
      resultString = strConcat(resultString, '"');
    }

    return resultString;
  }

  /* Chainlink */

  function setLinkFee(uint256 _LinkFee) external onlyOwner {
    LinkFee = _LinkFee;
  }

  function fulfillRandomness(bytes32 _requestId, uint256 _randomNumber) internal override {
    for (uint8 i = 0; i < vrfRequestIds[_requestId].length; i++) {
      uint256 blockDna;
      if (i == 0) {
        blockDna = _randomNumber;
      } else {
        blockDna = uint256(keccak256(abi.encode(_randomNumber, numberOfTraits * i)));
      }
      monsterBlocksDna[vrfRequestIds[_requestId][i]] = blockDna;

      emit UpdateMonsterBlock(vrfRequestIds[_requestId][i], blockDna);
    }
  }

  // Fail-safe in case Chainlink callback fails - hopefully never required
  function retryChainlinkRequest(uint256 _tokenId) external onlyOwner {
    require(_exists(_tokenId), "Token does not exist");
    require(monsterBlocksDna[_tokenId] == 0, "Already has DNA set - cannot be changed");
    require(LINK.balanceOf(address(this)) >= LinkFee, "Not enough LINK");

    bytes32 requestId = requestRandomness(keyHash, LinkFee);
    vrfRequestIds[requestId].push(_tokenId);
  }
}
function ownerMintMonsterBlocks(uint256 _quantity) external onlyOwner {
  require(totalMinted + _quantity < maxSupply + 1, "Not enough left");
  require(totalMintedByOwner + _quantity <= maxOwnerMintedSupply, "Not enough left");
  require(_quantity < maxMintLimit + 1, "Max mint is 20");

  totalMintedByOwner += _quantity;

  mintMonsterBlock(_quantity);
}

/* Owner Minting
 Allow a small number of pre-mints without ETH payment required */