ETH Price: $2,097.34 (+1.24%)

Transaction Decoder

Block:
15135513 at Jul-13-2022 05:08:01 PM +UTC
Transaction Fee:
0.003752094776842176 ETH $7.87
Gas Used:
53,804 Gas / 69.736353744 Gwei

Account State Difference:

  Address   Before After State Difference Code
0x37c44B6d...907804D17
6.182775034741605572 Eth
Nonce: 352
6.179022939964763396 Eth
Nonce: 353
0.003752094776842176
(Ethermine)
1,385.91693559369372365 Eth1,385.916975148599030366 Eth0.000039554905306716

Execution Trace

ETH 2.809446254071661238 MinterDAExpV1.purchase( _projectId=334 ) => ( tokenId=3963877391197344453575983046348115674221700746820753546331534351508065746944 )
  • MinterFilterV0.mint( _to=0x37c44B6dB049eC101f530CE1F62CE1e907804D17, _projectId=334, sender=0x37c44B6dB049eC101f530CE1F62CE1e907804D17 ) => ( _tokenId=3963877391197344453575983046348115674221700746820753546331534351508065746944 )
    • GenArt721Core.mint( _to=0x37c44B6dB049eC101f530CE1F62CE1e907804D17, _projectId=334, _by=0x37c44B6dB049eC101f530CE1F62CE1e907804D17 ) => ( _tokenId=3963877391197344453575983046348115674221700746820753546331534351508065746944 )
      File 1 of 3: MinterDAExpV1
      // SPDX-License-Identifier: LGPL-3.0-only
      // Created By: Art Blocks Inc.
      import "../interfaces/0.8.x/IGenArt721CoreContractV1.sol";
      import "../interfaces/0.8.x/IMinterFilterV0.sol";
      import "../interfaces/0.8.x/IFilteredMinterV0.sol";
      import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
      pragma solidity 0.8.9;
      /**
       * @title Filtered Minter contract that allows tokens to be minted with ETH.
       * Pricing is achieved using an automated Dutch-auction mechanism.
       * @author Art Blocks Inc.
       */
      contract MinterDAExpV1 is ReentrancyGuard, IFilteredMinterV0 {
          /// Auction details updated for project `projectId`.
          event SetAuctionDetails(
              uint256 indexed projectId,
              uint256 _auctionTimestampStart,
              uint256 _priceDecayHalfLifeSeconds,
              uint256 _startPrice,
              uint256 _basePrice
          );
          /// Auction details cleared for project `projectId`.
          event ResetAuctionDetails(uint256 indexed projectId);
          /// Maximum and minimum allowed price decay half lifes updated.
          event AuctionHalfLifeRangeSecondsUpdated(
              uint256 _minimumPriceDecayHalfLifeSeconds,
              uint256 _maximumPriceDecayHalfLifeSeconds
          );
          /// Core contract address this minter interacts with
          address public immutable genArt721CoreAddress;
          /// This contract handles cores with interface IV1
          IGenArt721CoreContractV1 private immutable genArtCoreContract;
          /// Minter filter address this minter interacts with
          address public immutable minterFilterAddress;
          /// Minter filter this minter may interact with.
          IMinterFilterV0 private immutable minterFilter;
          /// minterType for this minter
          string public constant minterType = "MinterDAExpV1";
          uint256 constant ONE_MILLION = 1_000_000;
          /// projectId => has project reached its maximum number of invocations?
          mapping(uint256 => bool) public projectMaxHasBeenInvoked;
          /// projectId => project's maximum number of invocations
          mapping(uint256 => uint256) public projectMaxInvocations;
          /// Minimum price decay half life: price must decay with a half life of at
          /// least this amount (must cut in half at least every N seconds).
          uint256 public minimumPriceDecayHalfLifeSeconds = 300; // 5 minutes
          /// Maximum price decay half life: price may decay with a half life of no
          /// more than this amount (may cut in half at no more than every N seconds).
          uint256 public maximumPriceDecayHalfLifeSeconds = 3600; // 60 minutes
          /// projectId => auction parameters
          mapping(uint256 => AuctionParameters) public projectAuctionParameters;
          struct AuctionParameters {
              uint256 timestampStart;
              uint256 priceDecayHalfLifeSeconds;
              uint256 startPrice;
              uint256 basePrice;
          }
          modifier onlyCoreWhitelisted() {
              require(
                  genArtCoreContract.isWhitelisted(msg.sender),
                  "Only Core whitelisted"
              );
              _;
          }
          modifier onlyArtist(uint256 _projectId) {
              require(
                  (msg.sender ==
                      genArtCoreContract.projectIdToArtistAddress(_projectId)),
                  "Only Artist"
              );
              _;
          }
          /**
           * @notice Initializes contract to be a Filtered Minter for
           * `_minterFilter`, integrated with Art Blocks core contract
           * at address `_genArt721Address`.
           * @param _genArt721Address Art Blocks core contract address for
           * which this contract will be a minter.
           * @param _minterFilter Minter filter for which
           * this will a filtered minter.
           */
          constructor(address _genArt721Address, address _minterFilter)
              ReentrancyGuard()
          {
              genArt721CoreAddress = _genArt721Address;
              genArtCoreContract = IGenArt721CoreContractV1(_genArt721Address);
              minterFilterAddress = _minterFilter;
              minterFilter = IMinterFilterV0(_minterFilter);
              require(
                  minterFilter.genArt721CoreAddress() == _genArt721Address,
                  "Illegal contract pairing"
              );
          }
          /**
           * @notice Sets the maximum invocations of project `_projectId` based
           * on the value currently defined in the core contract.
           * @param _projectId Project ID to set the maximum invocations for.
           * @dev also checks and may refresh projectMaxHasBeenInvoked for project
           * @dev this enables gas reduction after maxInvocations have been reached -
           * core contracts shall still enforce a maxInvocation check during mint.
           */
          function setProjectMaxInvocations(uint256 _projectId)
              external
              onlyCoreWhitelisted
          {
              uint256 invocations;
              uint256 maxInvocations;
              (, , invocations, maxInvocations, , , , , ) = genArtCoreContract
                  .projectTokenInfo(_projectId);
              // update storage with results
              projectMaxInvocations[_projectId] = maxInvocations;
              if (invocations < maxInvocations) {
                  projectMaxHasBeenInvoked[_projectId] = false;
              }
          }
          /**
           * @notice Warning: Disabling purchaseTo is not supported on this minter.
           * This method exists purely for interface-conformance purposes.
           */
          function togglePurchaseToDisabled(uint256 _projectId)
              external
              view
              onlyArtist(_projectId)
          {
              revert("Action not supported");
          }
          /**
           * @notice Sets the minimum and maximum values that are settable for
           * `_priceDecayHalfLifeSeconds` across all projects.
           * @param _minimumPriceDecayHalfLifeSeconds Minimum price decay half life
           * (in seconds).
           * @param _maximumPriceDecayHalfLifeSeconds Maximum price decay half life
           * (in seconds).
           */
          function setAllowablePriceDecayHalfLifeRangeSeconds(
              uint256 _minimumPriceDecayHalfLifeSeconds,
              uint256 _maximumPriceDecayHalfLifeSeconds
          ) external onlyCoreWhitelisted {
              require(
                  _maximumPriceDecayHalfLifeSeconds >
                      _minimumPriceDecayHalfLifeSeconds,
                  "Maximum half life must be greater than minimum"
              );
              require(
                  _minimumPriceDecayHalfLifeSeconds > 0,
                  "Half life of zero not allowed"
              );
              minimumPriceDecayHalfLifeSeconds = _minimumPriceDecayHalfLifeSeconds;
              maximumPriceDecayHalfLifeSeconds = _maximumPriceDecayHalfLifeSeconds;
              emit AuctionHalfLifeRangeSecondsUpdated(
                  _minimumPriceDecayHalfLifeSeconds,
                  _maximumPriceDecayHalfLifeSeconds
              );
          }
          ////// Auction Functions
          /**
           * @notice Sets auction details for project `_projectId`.
           * @param _projectId Project ID to set auction details for.
           * @param _auctionTimestampStart Timestamp at which to start the auction.
           * @param _priceDecayHalfLifeSeconds The half life with which to decay the
           *  price (in seconds).
           * @param _startPrice Price at which to start the auction, in Wei.
           * @param _basePrice Resting price of the auction, in Wei.
           */
          function setAuctionDetails(
              uint256 _projectId,
              uint256 _auctionTimestampStart,
              uint256 _priceDecayHalfLifeSeconds,
              uint256 _startPrice,
              uint256 _basePrice
          ) external onlyArtist(_projectId) {
              AuctionParameters memory auctionParams = projectAuctionParameters[
                  _projectId
              ];
              require(
                  auctionParams.timestampStart == 0 ||
                      block.timestamp < auctionParams.timestampStart,
                  "No modifications mid-auction"
              );
              require(
                  block.timestamp < _auctionTimestampStart,
                  "Only future auctions"
              );
              require(
                  _startPrice > _basePrice,
                  "Auction start price must be greater than auction end price"
              );
              require(
                  (_priceDecayHalfLifeSeconds >= minimumPriceDecayHalfLifeSeconds) &&
                      (_priceDecayHalfLifeSeconds <=
                          maximumPriceDecayHalfLifeSeconds),
                  "Price decay half life must fall between min and max allowable values"
              );
              projectAuctionParameters[_projectId] = AuctionParameters(
                  _auctionTimestampStart,
                  _priceDecayHalfLifeSeconds,
                  _startPrice,
                  _basePrice
              );
              emit SetAuctionDetails(
                  _projectId,
                  _auctionTimestampStart,
                  _priceDecayHalfLifeSeconds,
                  _startPrice,
                  _basePrice
              );
          }
          /**
           * @notice Resets auction details for project `_projectId`, zero-ing out all
           * relevant auction fields. Not intended to be used in normal auction
           * operation, but rather only in case of the need to halt an auction.
           * @param _projectId Project ID to set auction details for.
           */
          function resetAuctionDetails(uint256 _projectId)
              external
              onlyCoreWhitelisted
          {
              delete projectAuctionParameters[_projectId];
              emit ResetAuctionDetails(_projectId);
          }
          /**
           * @notice Purchases a token from project `_projectId`.
           * @param _projectId Project ID to mint a token on.
           * @return tokenId Token ID of minted token
           */
          function purchase(uint256 _projectId)
              external
              payable
              returns (uint256 tokenId)
          {
              tokenId = purchaseTo(msg.sender, _projectId);
              return tokenId;
          }
          /**
           * @notice Purchases a token from project `_projectId` and sets
           * the token's owner to `_to`.
           * @param _to Address to be the new token's owner.
           * @param _projectId Project ID to mint a token on.
           * @return tokenId Token ID of minted token
           */
          function purchaseTo(address _to, uint256 _projectId)
              public
              payable
              nonReentrant
              returns (uint256 tokenId)
          {
              // CHECKS
              require(
                  !projectMaxHasBeenInvoked[_projectId],
                  "Maximum number of invocations reached"
              );
              // _getPrice reverts if auction is unconfigured or has not started
              uint256 currentPriceInWei = _getPrice(_projectId);
              require(
                  msg.value >= currentPriceInWei,
                  "Must send minimum value to mint!"
              );
              // EFFECTS
              tokenId = minterFilter.mint(_to, _projectId, msg.sender);
              // what if projectMaxInvocations[_projectId] is 0 (default value)?
              // that is intended, so that by default the minter allows infinite transactions,
              // allowing the artblocks contract to stop minting
              // uint256 tokenInvocation = tokenId % ONE_MILLION;
              if (
                  projectMaxInvocations[_projectId] > 0 &&
                  tokenId % ONE_MILLION == projectMaxInvocations[_projectId] - 1
              ) {
                  projectMaxHasBeenInvoked[_projectId] = true;
              }
              // INTERACTIONS
              _splitFundsETHAuction(_projectId, currentPriceInWei);
              return tokenId;
          }
          /**
           * @dev splits ETH funds between sender (if refund), foundation,
           * artist, and artist's additional payee for a token purchased on
           * project `_projectId`.
           * @dev utilizes transfer() to send ETH, which may fail if access
           * lists are not properly populated when purchasing tokens.
           * @param _projectId Project ID for which funds shall be split.
           * @param _currentPriceInWei Current price of token, in Wei.
           */
          function _splitFundsETHAuction(
              uint256 _projectId,
              uint256 _currentPriceInWei
          ) internal {
              if (msg.value > 0) {
                  uint256 refund = msg.value - _currentPriceInWei;
                  if (refund > 0) {
                      (bool success_, ) = msg.sender.call{value: refund}("");
                      require(success_, "Refund failed");
                  }
                  uint256 foundationAmount = (_currentPriceInWei *
                      genArtCoreContract.artblocksPercentage()) / 100;
                  if (foundationAmount > 0) {
                      (bool success_, ) = genArtCoreContract.artblocksAddress().call{
                          value: foundationAmount
                      }("");
                      require(success_, "Foundation payment failed");
                  }
                  uint256 projectFunds = _currentPriceInWei - foundationAmount;
                  uint256 additionalPayeeAmount;
                  if (
                      genArtCoreContract.projectIdToAdditionalPayeePercentage(
                          _projectId
                      ) > 0
                  ) {
                      additionalPayeeAmount =
                          (projectFunds *
                              genArtCoreContract.projectIdToAdditionalPayeePercentage(
                                  _projectId
                              )) /
                          100;
                      if (additionalPayeeAmount > 0) {
                          (bool success_, ) = genArtCoreContract
                              .projectIdToAdditionalPayee(_projectId)
                              .call{value: additionalPayeeAmount}("");
                          require(success_, "Additional payment failed");
                      }
                  }
                  uint256 creatorFunds = projectFunds - additionalPayeeAmount;
                  if (creatorFunds > 0) {
                      (bool success_, ) = genArtCoreContract
                          .projectIdToArtistAddress(_projectId)
                          .call{value: creatorFunds}("");
                      require(success_, "Artist payment failed");
                  }
              }
          }
          /**
           * @notice Gets price of minting a token on project `_projectId` given
           * the project's AuctionParameters and current block timestamp.
           * Reverts if auction has not yet started or auction is unconfigured.
           * @param _projectId Project ID to get price of token for.
           * @return current price of token in Wei
           * @dev This method calculates price decay using a linear interpolation
           * of exponential decay based on the artist-provided half-life for price
           * decay, `_priceDecayHalfLifeSeconds`.
           */
          function _getPrice(uint256 _projectId) private view returns (uint256) {
              AuctionParameters memory auctionParams = projectAuctionParameters[
                  _projectId
              ];
              require(
                  block.timestamp > auctionParams.timestampStart,
                  "Auction not yet started"
              );
              require(
                  auctionParams.priceDecayHalfLifeSeconds > 0,
                  "Only configured auctions"
              );
              uint256 decayedPrice = auctionParams.startPrice;
              uint256 elapsedTimeSeconds = block.timestamp -
                  auctionParams.timestampStart;
              // Divide by two (via bit-shifting) for the number of entirely completed
              // half-lives that have elapsed since auction start time.
              decayedPrice >>=
                  elapsedTimeSeconds /
                  auctionParams.priceDecayHalfLifeSeconds;
              // Perform a linear interpolation between partial half-life points, to
              // approximate the current place on a perfect exponential decay curve.
              decayedPrice -=
                  (decayedPrice *
                      (elapsedTimeSeconds %
                          auctionParams.priceDecayHalfLifeSeconds)) /
                  auctionParams.priceDecayHalfLifeSeconds /
                  2;
              if (decayedPrice < auctionParams.basePrice) {
                  // Price may not decay below stay `basePrice`.
                  return auctionParams.basePrice;
              }
              return decayedPrice;
          }
          /**
           * @notice Gets if price of token is configured, price of minting a
           * token on project `_projectId`, and currency symbol and address to be
           * used as payment. Supersedes any core contract price information.
           * @param _projectId Project ID to get price information for.
           * @return isConfigured true only if project's auction parameters have been
           * configured on this minter
           * @return tokenPriceInWei current price of token on this minter - invalid
           * if auction has not yet been configured
           * @return currencySymbol currency symbol for purchases of project on this
           * minter. This minter always returns "ETH"
           * @return currencyAddress currency address for purchases of project on
           * this minter. This minter always returns null address, reserved for ether
           */
          function getPriceInfo(uint256 _projectId)
              external
              view
              returns (
                  bool isConfigured,
                  uint256 tokenPriceInWei,
                  string memory currencySymbol,
                  address currencyAddress
              )
          {
              AuctionParameters memory auctionParams = projectAuctionParameters[
                  _projectId
              ];
              isConfigured = (auctionParams.startPrice > 0);
              if (block.timestamp <= auctionParams.timestampStart) {
                  // Provide a reasonable value for `tokenPriceInWei` when it would
                  // otherwise revert, using the starting price before auction starts.
                  tokenPriceInWei = auctionParams.startPrice;
              } else if (auctionParams.startPrice == 0) {
                  // In the case of unconfigured auction, return price of zero when
                  // it would otherwise revert
                  tokenPriceInWei = 0;
              } else {
                  tokenPriceInWei = _getPrice(_projectId);
              }
              currencySymbol = "ETH";
              currencyAddress = address(0);
          }
      }
      // SPDX-License-Identifier: LGPL-3.0-only
      // Created By: Art Blocks Inc.
      pragma solidity ^0.8.0;
      interface IGenArt721CoreContractV1 {
          event Mint(
              address indexed _to,
              uint256 indexed _tokenId,
              uint256 indexed _projectId
          );
          // getter function of public variable
          function admin() external view returns (address);
          // getter function of public variable
          function nextProjectId() external view returns (uint256);
          // getter function of public mapping
          function tokenIdToProjectId(uint256 tokenId)
              external
              view
              returns (uint256 projectId);
          function isWhitelisted(address sender) external view returns (bool);
          // @dev this is not available in V0
          function isMintWhitelisted(address minter) external view returns (bool);
          function projectIdToArtistAddress(uint256 _projectId)
              external
              view
              returns (address payable);
          function projectIdToAdditionalPayee(uint256 _projectId)
              external
              view
              returns (address payable);
          function projectIdToAdditionalPayeePercentage(uint256 _projectId)
              external
              view
              returns (uint256);
          function projectTokenInfo(uint256 _projectId)
              external
              view
              returns (
                  address,
                  uint256,
                  uint256,
                  uint256,
                  bool,
                  address,
                  uint256,
                  string memory,
                  address
              );
          function artblocksAddress() external view returns (address payable);
          function artblocksPercentage() external view returns (uint256);
          function mint(
              address _to,
              uint256 _projectId,
              address _by
          ) external returns (uint256 tokenId);
          function getRoyaltyData(uint256 _tokenId)
              external
              view
              returns (
                  address artistAddress,
                  address additionalPayee,
                  uint256 additionalPayeePercentage,
                  uint256 royaltyFeeByID
              );
      }
      // SPDX-License-Identifier: LGPL-3.0-only
      // Created By: Art Blocks Inc.
      pragma solidity ^0.8.0;
      interface IMinterFilterV0 {
          /**
           * @notice Approved minter `_minterAddress`.
           */
          event MinterApproved(address indexed _minterAddress, string _minterType);
          /**
           * @notice Revoked approval for minter `_minterAddress`
           */
          event MinterRevoked(address indexed _minterAddress);
          /**
           * @notice Minter `_minterAddress` of type `_minterType`
           * registered for project `_projectId`.
           */
          event ProjectMinterRegistered(
              uint256 indexed _projectId,
              address indexed _minterAddress,
              string _minterType
          );
          /**
           * @notice Any active minter removed for project `_projectId`.
           */
          event ProjectMinterRemoved(uint256 indexed _projectId);
          function genArt721CoreAddress() external returns (address);
          function setMinterForProject(uint256, address) external;
          function removeMinterForProject(uint256) external;
          function mint(
              address _to,
              uint256 _projectId,
              address sender
          ) external returns (uint256);
          function getMinterForProject(uint256) external view returns (address);
          function projectHasMinter(uint256) external view returns (bool);
      }
      // SPDX-License-Identifier: LGPL-3.0-only
      // Created By: Art Blocks Inc.
      pragma solidity ^0.8.0;
      interface IFilteredMinterV0 {
          /**
           * @notice Price per token in wei updated for project `_projectId` to
           * `_pricePerTokenInWei`.
           */
          event PricePerTokenInWeiUpdated(
              uint256 indexed _projectId,
              uint256 indexed _pricePerTokenInWei
          );
          /**
           * @notice Currency updated for project `_projectId` to symbol
           * `_currencySymbol` and address `_currencyAddress`.
           */
          event ProjectCurrencyInfoUpdated(
              uint256 indexed _projectId,
              address indexed _currencyAddress,
              string _currencySymbol
          );
          /// togglePurchaseToDisabled updated
          event PurchaseToDisabledUpdated(
              uint256 indexed _projectId,
              bool _purchaseToDisabled
          );
          // getter function of public variable
          function minterType() external view returns (string memory);
          function genArt721CoreAddress() external returns (address);
          function minterFilterAddress() external returns (address);
          // Triggers a purchase of a token from the desired project, to the
          // TX-sending address.
          function purchase(uint256 _projectId)
              external
              payable
              returns (uint256 tokenId);
          // Triggers a purchase of a token from the desired project, to the specified
          // receiving address.
          function purchaseTo(address _to, uint256 _projectId)
              external
              payable
              returns (uint256 tokenId);
          // Toggles the ability for `purchaseTo` to be called directly with a
          // specified receiving address that differs from the TX-sending address.
          function togglePurchaseToDisabled(uint256 _projectId) external;
          // Called to make the minter contract aware of the max invocations for a
          // given project.
          function setProjectMaxInvocations(uint256 _projectId) external;
          // Gets if token price is configured, token price in wei, currency symbol,
          // and currency address, assuming this is project's minter.
          // Supersedes any defined core price.
          function getPriceInfo(uint256 _projectId)
              external
              view
              returns (
                  bool isConfigured,
                  uint256 tokenPriceInWei,
                  string memory currencySymbol,
                  address currencyAddress
              );
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts v4.4.1 (security/ReentrancyGuard.sol)
      pragma solidity ^0.8.0;
      /**
       * @dev Contract module that helps prevent reentrant calls to a function.
       *
       * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
       * available, which can be applied to functions to make sure there are no nested
       * (reentrant) calls to them.
       *
       * Note that because there is a single `nonReentrant` guard, functions marked as
       * `nonReentrant` may not call one another. This can be worked around by making
       * those functions `private`, and then adding `external` `nonReentrant` entry
       * points to them.
       *
       * TIP: If you would like to learn more about reentrancy and alternative ways
       * to protect against it, check out our blog post
       * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
       */
      abstract contract ReentrancyGuard {
          // Booleans are more expensive than uint256 or any type that takes up a full
          // word because each write operation emits an extra SLOAD to first read the
          // slot's contents, replace the bits taken up by the boolean, and then write
          // back. This is the compiler's defense against contract upgrades and
          // pointer aliasing, and it cannot be disabled.
          // The values being non-zero value makes deployment a bit more expensive,
          // but in exchange the refund on every call to nonReentrant will be lower in
          // amount. Since refunds are capped to a percentage of the total
          // transaction's gas, it is best to keep them low in cases like this one, to
          // increase the likelihood of the full refund coming into effect.
          uint256 private constant _NOT_ENTERED = 1;
          uint256 private constant _ENTERED = 2;
          uint256 private _status;
          constructor() {
              _status = _NOT_ENTERED;
          }
          /**
           * @dev Prevents a contract from calling itself, directly or indirectly.
           * Calling a `nonReentrant` function from another `nonReentrant`
           * function is not supported. It is possible to prevent this from happening
           * by making the `nonReentrant` function external, and making it call a
           * `private` function that does the actual work.
           */
          modifier nonReentrant() {
              // On the first call to nonReentrant, _notEntered will be true
              require(_status != _ENTERED, "ReentrancyGuard: reentrant call");
              // Any calls to nonReentrant after this point will fail
              _status = _ENTERED;
              _;
              // By storing the original value once again, a refund is triggered (see
              // https://eips.ethereum.org/EIPS/eip-2200)
              _status = _NOT_ENTERED;
          }
      }
      

      File 2 of 3: MinterFilterV0
      // SPDX-License-Identifier: LGPL-3.0-only
      // Created By: Art Blocks Inc.
      import "../interfaces/0.8.x/IMinterFilterV0.sol";
      import "../interfaces/0.8.x/IFilteredMinterV0.sol";
      import "../interfaces/0.8.x/IGenArt721CoreContractV1.sol";
      import "@openzeppelin/contracts/utils/structs/EnumerableMap.sol";
      pragma solidity 0.8.9;
      /**
       * @title Minter filter contract that allows filtered minters to be set
       * on a per-project basis.
       * @author Art Blocks Inc.
       */
      contract MinterFilterV0 is IMinterFilterV0 {
          /**
           * @notice This minter is to be considered `_coreContractAddress`'s
           * canonical minter.
           * @dev may be vestigial after migrating to V3 core contracts
           */
          event IsCanonicalMinterFilter(address indexed _coreContractAddress);
          // add Enumerable Map methods
          using EnumerableMap for EnumerableMap.UintToAddressMap;
          /// Core contract address this minter interacts with
          address public immutable genArt721CoreAddress;
          /// This contract only uses the portion of V3 interface also on V1 core
          IGenArt721CoreContractV1 private immutable genArtCoreContract;
          /// projectId => minter address
          EnumerableMap.UintToAddressMap private minterForProject;
          /// minter address => qty projects currently using minter
          mapping(address => uint256) public numProjectsUsingMinter;
          /// minter address => is an approved minter?
          mapping(address => bool) public isApprovedMinter;
          modifier onlyCoreWhitelisted() {
              require(
                  genArtCoreContract.isWhitelisted(msg.sender),
                  "Only Core whitelisted"
              );
              _;
          }
          modifier onlyCoreWhitelistedOrArtist(uint256 _projectId) {
              require(
                  (genArtCoreContract.isWhitelisted(msg.sender) ||
                      msg.sender ==
                      genArtCoreContract.projectIdToArtistAddress(_projectId)),
                  "Only Core whitelisted or Artist"
              );
              _;
          }
          modifier projectExists(uint256 _projectId) {
              require(
                  _projectId < genArtCoreContract.nextProjectId(),
                  "Only existing projects"
              );
              _;
          }
          modifier usingApprovedMinter(address _minterAddress) {
              require(
                  isApprovedMinter[_minterAddress],
                  "Only approved minters are allowed"
              );
              _;
          }
          modifier onlyMintWhitelisted() {
              require(
                  genArtCoreContract.isMintWhitelisted(address(this)),
                  "Only mint allowlisted"
              );
              _;
          }
          /**
           * @notice Initializes contract to be a Minter for `_genArt721Address`.
           * @param _genArt721Address Art Blocks core contract address
           * this contract will be a minter for. Can never be updated.
           */
          constructor(address _genArt721Address) {
              genArt721CoreAddress = _genArt721Address;
              genArtCoreContract = IGenArt721CoreContractV1(_genArt721Address);
          }
          /**
           * @notice Emits event notifying indexers that this is core contract's
           * canonical minter filter.
           * @dev may be vestigial after migrating to V3 core contracts
           */
          function alertAsCanonicalMinterFilter()
              external
              onlyCoreWhitelisted
              onlyMintWhitelisted
          {
              emit IsCanonicalMinterFilter(genArt721CoreAddress);
          }
          /**
           * @notice Approves minter `_minterAddress`.
           * @param _minterAddress Minter to be added as an approved minter.
           */
          function addApprovedMinter(address _minterAddress)
              external
              onlyCoreWhitelisted
          {
              isApprovedMinter[_minterAddress] = true;
              emit MinterApproved(
                  _minterAddress,
                  IFilteredMinterV0(_minterAddress).minterType()
              );
          }
          /**
           * @notice Removes previously approved minter `_minterAddress`.
           * @param _minterAddress Minter to remove.
           */
          function removeApprovedMinter(address _minterAddress)
              external
              onlyCoreWhitelisted
          {
              require(
                  numProjectsUsingMinter[_minterAddress] == 0,
                  "Only unused minters"
              );
              isApprovedMinter[_minterAddress] = false;
              emit MinterRevoked(_minterAddress);
          }
          /**
           * @notice Sets minter for project `_projectId` to minter
           * `_minterAddress`.
           * @param _projectId Project ID to set minter for.
           * @param _minterAddress Minter to be the project's minter.
           */
          function setMinterForProject(uint256 _projectId, address _minterAddress)
              external
              onlyCoreWhitelistedOrArtist(_projectId)
              usingApprovedMinter(_minterAddress)
              projectExists(_projectId)
          {
              // decrement number of projects using a previous minter
              (bool hasPreviousMinter, address previousMinter) = minterForProject
                  .tryGet(_projectId);
              if (hasPreviousMinter) {
                  numProjectsUsingMinter[previousMinter]--;
              }
              // add new minter
              numProjectsUsingMinter[_minterAddress]++;
              minterForProject.set(_projectId, _minterAddress);
              emit ProjectMinterRegistered(
                  _projectId,
                  _minterAddress,
                  IFilteredMinterV0(_minterAddress).minterType()
              );
          }
          /**
           * @notice Updates project `_projectId` to have no configured minter.
           * @param _projectId Project ID to remove minter.
           * @dev requires project to have an assigned minter
           */
          function removeMinterForProject(uint256 _projectId)
              external
              onlyCoreWhitelistedOrArtist(_projectId)
          {
              _removeMinterForProject(_projectId);
          }
          /**
           * @notice Updates an array of project IDs to have no configured minter.
           * @param _projectIds Array of project IDs to remove minters for.
           * @dev requires all project IDs to have an assigned minter
           * @dev caution with respect to single tx gas limits
           */
          function removeMintersForProjects(uint256[] calldata _projectIds)
              external
              onlyCoreWhitelisted
          {
              uint256 numProjects = _projectIds.length;
              for (uint256 i; i < numProjects; i++) {
                  _removeMinterForProject(_projectIds[i]);
              }
          }
          /**
           * @notice Updates project `_projectId` to have no configured minter
           * (reverts tx if project does not have an assigned minter).
           * @param _projectId Project ID to remove minter.
           */
          function _removeMinterForProject(uint256 _projectId) private {
              // remove minter for project and emit
              // `minterForProject.get()` reverts tx if no minter set for project
              numProjectsUsingMinter[minterForProject.get(_projectId)]--;
              minterForProject.remove(_projectId);
              emit ProjectMinterRemoved(_projectId);
          }
          /**
           * @notice Mint a token from project `_projectId` to `_to`, originally
           * purchased by `sender`.
           * @param _to The new token's owner.
           * @param _projectId Project ID to mint a new token on.
           * @param sender Address purchasing a new token.
           * @return _tokenId Token ID of minted token
           * @dev reverts w/nonexistent key error when project has no assigned minter
           */
          function mint(
              address _to,
              uint256 _projectId,
              address sender
          ) external returns (uint256 _tokenId) {
              // CHECKS
              // minter is the project's minter
              require(
                  msg.sender == minterForProject.get(_projectId),
                  "Only assigned minter"
              );
              // EFFECTS
              uint256 tokenId = genArtCoreContract.mint(_to, _projectId, sender);
              return tokenId;
          }
          /**
           * @notice Gets the assigned minter for project `_projectId`.
           * @param _projectId Project ID to query.
           * @return address Minter address assigned to project `_projectId`
           * @dev requires project to have an assigned minter
           */
          function getMinterForProject(uint256 _projectId)
              external
              view
              returns (address)
          {
              (bool _hasMinter, address _currentMinter) = minterForProject.tryGet(
                  _projectId
              );
              require(_hasMinter, "No minter assigned");
              return _currentMinter;
          }
          /**
           * @notice Queries if project `_projectId` has an assigned minter.
           * @param _projectId Project ID to query.
           * @return bool true if project has an assigned minter, else false
           * @dev requires project to have an assigned minter
           */
          function projectHasMinter(uint256 _projectId) external view returns (bool) {
              (bool _hasMinter, ) = minterForProject.tryGet(_projectId);
              return _hasMinter;
          }
          /**
           * @notice Gets quantity of projects that have assigned minters.
           * @return uint256 quantity of projects that have assigned minters
           */
          function getNumProjectsWithMinters() external view returns (uint256) {
              return minterForProject.length();
          }
          /**
           * @notice Get project ID and minter address at index `_index` of
           * enumerable map.
           * @param _index enumerable map index to query.
           * @return projectId project ID at index `_index`
           * @return minterAddress minter address for project at index `_index`
           * @return minterType minter type of minter at minterAddress
           * @dev index must be < quantity of projects that have assigned minters
           */
          function getProjectAndMinterInfoAt(uint256 _index)
              external
              view
              returns (
                  uint256 projectId,
                  address minterAddress,
                  string memory minterType
              )
          {
              (projectId, minterAddress) = minterForProject.at(_index);
              minterType = IFilteredMinterV0(minterAddress).minterType();
              return (projectId, minterAddress, minterType);
          }
      }
      // SPDX-License-Identifier: LGPL-3.0-only
      // Created By: Art Blocks Inc.
      pragma solidity ^0.8.0;
      interface IMinterFilterV0 {
          /**
           * @notice Approved minter `_minterAddress`.
           */
          event MinterApproved(address indexed _minterAddress, string _minterType);
          /**
           * @notice Revoked approval for minter `_minterAddress`
           */
          event MinterRevoked(address indexed _minterAddress);
          /**
           * @notice Minter `_minterAddress` of type `_minterType`
           * registered for project `_projectId`.
           */
          event ProjectMinterRegistered(
              uint256 indexed _projectId,
              address indexed _minterAddress,
              string _minterType
          );
          /**
           * @notice Any active minter removed for project `_projectId`.
           */
          event ProjectMinterRemoved(uint256 indexed _projectId);
          function genArt721CoreAddress() external returns (address);
          function setMinterForProject(uint256, address) external;
          function removeMinterForProject(uint256) external;
          function mint(
              address _to,
              uint256 _projectId,
              address sender
          ) external returns (uint256);
          function getMinterForProject(uint256) external view returns (address);
          function projectHasMinter(uint256) external view returns (bool);
      }
      // SPDX-License-Identifier: LGPL-3.0-only
      // Created By: Art Blocks Inc.
      pragma solidity ^0.8.0;
      interface IFilteredMinterV0 {
          /**
           * @notice Price per token in wei updated for project `_projectId` to
           * `_pricePerTokenInWei`.
           */
          event PricePerTokenInWeiUpdated(
              uint256 indexed _projectId,
              uint256 indexed _pricePerTokenInWei
          );
          /**
           * @notice Currency updated for project `_projectId` to symbol
           * `_currencySymbol` and address `_currencyAddress`.
           */
          event ProjectCurrencyInfoUpdated(
              uint256 indexed _projectId,
              address indexed _currencyAddress,
              string _currencySymbol
          );
          /// togglePurchaseToDisabled updated
          event PurchaseToDisabledUpdated(
              uint256 indexed _projectId,
              bool _purchaseToDisabled
          );
          // getter function of public variable
          function minterType() external view returns (string memory);
          function genArt721CoreAddress() external returns (address);
          function minterFilterAddress() external returns (address);
          // Triggers a purchase of a token from the desired project, to the
          // TX-sending address.
          function purchase(uint256 _projectId)
              external
              payable
              returns (uint256 tokenId);
          // Triggers a purchase of a token from the desired project, to the specified
          // receiving address.
          function purchaseTo(address _to, uint256 _projectId)
              external
              payable
              returns (uint256 tokenId);
          // Toggles the ability for `purchaseTo` to be called directly with a
          // specified receiving address that differs from the TX-sending address.
          function togglePurchaseToDisabled(uint256 _projectId) external;
          // Called to make the minter contract aware of the max invocations for a
          // given project.
          function setProjectMaxInvocations(uint256 _projectId) external;
          // Gets if token price is configured, token price in wei, currency symbol,
          // and currency address, assuming this is project's minter.
          // Supersedes any defined core price.
          function getPriceInfo(uint256 _projectId)
              external
              view
              returns (
                  bool isConfigured,
                  uint256 tokenPriceInWei,
                  string memory currencySymbol,
                  address currencyAddress
              );
      }
      // SPDX-License-Identifier: LGPL-3.0-only
      // Created By: Art Blocks Inc.
      pragma solidity ^0.8.0;
      interface IGenArt721CoreContractV1 {
          event Mint(
              address indexed _to,
              uint256 indexed _tokenId,
              uint256 indexed _projectId
          );
          // getter function of public variable
          function admin() external view returns (address);
          // getter function of public variable
          function nextProjectId() external view returns (uint256);
          // getter function of public mapping
          function tokenIdToProjectId(uint256 tokenId)
              external
              view
              returns (uint256 projectId);
          function isWhitelisted(address sender) external view returns (bool);
          // @dev this is not available in V0
          function isMintWhitelisted(address minter) external view returns (bool);
          function projectIdToArtistAddress(uint256 _projectId)
              external
              view
              returns (address payable);
          function projectIdToAdditionalPayee(uint256 _projectId)
              external
              view
              returns (address payable);
          function projectIdToAdditionalPayeePercentage(uint256 _projectId)
              external
              view
              returns (uint256);
          function projectTokenInfo(uint256 _projectId)
              external
              view
              returns (
                  address,
                  uint256,
                  uint256,
                  uint256,
                  bool,
                  address,
                  uint256,
                  string memory,
                  address
              );
          function artblocksAddress() external view returns (address payable);
          function artblocksPercentage() external view returns (uint256);
          function mint(
              address _to,
              uint256 _projectId,
              address _by
          ) external returns (uint256 tokenId);
          function getRoyaltyData(uint256 _tokenId)
              external
              view
              returns (
                  address artistAddress,
                  address additionalPayee,
                  uint256 additionalPayeePercentage,
                  uint256 royaltyFeeByID
              );
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableMap.sol)
      pragma solidity ^0.8.0;
      import "./EnumerableSet.sol";
      /**
       * @dev Library for managing an enumerable variant of Solidity's
       * https://solidity.readthedocs.io/en/latest/types.html#mapping-types[`mapping`]
       * type.
       *
       * Maps have the following properties:
       *
       * - Entries are added, removed, and checked for existence in constant time
       * (O(1)).
       * - Entries are enumerated in O(n). No guarantees are made on the ordering.
       *
       * ```
       * contract Example {
       *     // Add the library methods
       *     using EnumerableMap for EnumerableMap.UintToAddressMap;
       *
       *     // Declare a set state variable
       *     EnumerableMap.UintToAddressMap private myMap;
       * }
       * ```
       *
       * As of v3.0.0, only maps of type `uint256 -> address` (`UintToAddressMap`) are
       * supported.
       */
      library EnumerableMap {
          using EnumerableSet for EnumerableSet.Bytes32Set;
          // To implement this library for multiple types with as little code
          // repetition as possible, we write it in terms of a generic Map type with
          // bytes32 keys and values.
          // The Map implementation uses private functions, and user-facing
          // implementations (such as Uint256ToAddressMap) are just wrappers around
          // the underlying Map.
          // This means that we can only create new EnumerableMaps for types that fit
          // in bytes32.
          struct Map {
              // Storage of keys
              EnumerableSet.Bytes32Set _keys;
              mapping(bytes32 => bytes32) _values;
          }
          /**
           * @dev Adds a key-value pair to a map, or updates the value for an existing
           * key. O(1).
           *
           * Returns true if the key was added to the map, that is if it was not
           * already present.
           */
          function _set(
              Map storage map,
              bytes32 key,
              bytes32 value
          ) private returns (bool) {
              map._values[key] = value;
              return map._keys.add(key);
          }
          /**
           * @dev Removes a key-value pair from a map. O(1).
           *
           * Returns true if the key was removed from the map, that is if it was present.
           */
          function _remove(Map storage map, bytes32 key) private returns (bool) {
              delete map._values[key];
              return map._keys.remove(key);
          }
          /**
           * @dev Returns true if the key is in the map. O(1).
           */
          function _contains(Map storage map, bytes32 key) private view returns (bool) {
              return map._keys.contains(key);
          }
          /**
           * @dev Returns the number of key-value pairs in the map. O(1).
           */
          function _length(Map storage map) private view returns (uint256) {
              return map._keys.length();
          }
          /**
           * @dev Returns the key-value pair stored at position `index` in the map. O(1).
           *
           * Note that there are no guarantees on the ordering of entries inside the
           * array, and it may change when more entries are added or removed.
           *
           * Requirements:
           *
           * - `index` must be strictly less than {length}.
           */
          function _at(Map storage map, uint256 index) private view returns (bytes32, bytes32) {
              bytes32 key = map._keys.at(index);
              return (key, map._values[key]);
          }
          /**
           * @dev Tries to returns the value associated with `key`.  O(1).
           * Does not revert if `key` is not in the map.
           */
          function _tryGet(Map storage map, bytes32 key) private view returns (bool, bytes32) {
              bytes32 value = map._values[key];
              if (value == bytes32(0)) {
                  return (_contains(map, key), bytes32(0));
              } else {
                  return (true, value);
              }
          }
          /**
           * @dev Returns the value associated with `key`.  O(1).
           *
           * Requirements:
           *
           * - `key` must be in the map.
           */
          function _get(Map storage map, bytes32 key) private view returns (bytes32) {
              bytes32 value = map._values[key];
              require(value != 0 || _contains(map, key), "EnumerableMap: nonexistent key");
              return value;
          }
          /**
           * @dev Same as {_get}, with a custom error message when `key` is not in the map.
           *
           * CAUTION: This function is deprecated because it requires allocating memory for the error
           * message unnecessarily. For custom revert reasons use {_tryGet}.
           */
          function _get(
              Map storage map,
              bytes32 key,
              string memory errorMessage
          ) private view returns (bytes32) {
              bytes32 value = map._values[key];
              require(value != 0 || _contains(map, key), errorMessage);
              return value;
          }
          // UintToAddressMap
          struct UintToAddressMap {
              Map _inner;
          }
          /**
           * @dev Adds a key-value pair to a map, or updates the value for an existing
           * key. O(1).
           *
           * Returns true if the key was added to the map, that is if it was not
           * already present.
           */
          function set(
              UintToAddressMap storage map,
              uint256 key,
              address value
          ) internal returns (bool) {
              return _set(map._inner, bytes32(key), bytes32(uint256(uint160(value))));
          }
          /**
           * @dev Removes a value from a set. O(1).
           *
           * Returns true if the key was removed from the map, that is if it was present.
           */
          function remove(UintToAddressMap storage map, uint256 key) internal returns (bool) {
              return _remove(map._inner, bytes32(key));
          }
          /**
           * @dev Returns true if the key is in the map. O(1).
           */
          function contains(UintToAddressMap storage map, uint256 key) internal view returns (bool) {
              return _contains(map._inner, bytes32(key));
          }
          /**
           * @dev Returns the number of elements in the map. O(1).
           */
          function length(UintToAddressMap storage map) internal view returns (uint256) {
              return _length(map._inner);
          }
          /**
           * @dev Returns the element stored at position `index` in the set. O(1).
           * Note that there are no guarantees on the ordering of values inside the
           * array, and it may change when more values are added or removed.
           *
           * Requirements:
           *
           * - `index` must be strictly less than {length}.
           */
          function at(UintToAddressMap storage map, uint256 index) internal view returns (uint256, address) {
              (bytes32 key, bytes32 value) = _at(map._inner, index);
              return (uint256(key), address(uint160(uint256(value))));
          }
          /**
           * @dev Tries to returns the value associated with `key`.  O(1).
           * Does not revert if `key` is not in the map.
           *
           * _Available since v3.4._
           */
          function tryGet(UintToAddressMap storage map, uint256 key) internal view returns (bool, address) {
              (bool success, bytes32 value) = _tryGet(map._inner, bytes32(key));
              return (success, address(uint160(uint256(value))));
          }
          /**
           * @dev Returns the value associated with `key`.  O(1).
           *
           * Requirements:
           *
           * - `key` must be in the map.
           */
          function get(UintToAddressMap storage map, uint256 key) internal view returns (address) {
              return address(uint160(uint256(_get(map._inner, bytes32(key)))));
          }
          /**
           * @dev Same as {get}, with a custom error message when `key` is not in the map.
           *
           * CAUTION: This function is deprecated because it requires allocating memory for the error
           * message unnecessarily. For custom revert reasons use {tryGet}.
           */
          function get(
              UintToAddressMap storage map,
              uint256 key,
              string memory errorMessage
          ) internal view returns (address) {
              return address(uint160(uint256(_get(map._inner, bytes32(key), errorMessage))));
          }
      }
      // SPDX-License-Identifier: MIT
      // OpenZeppelin Contracts v4.4.1 (utils/structs/EnumerableSet.sol)
      pragma solidity ^0.8.0;
      /**
       * @dev Library for managing
       * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
       * types.
       *
       * Sets have the following properties:
       *
       * - Elements are added, removed, and checked for existence in constant time
       * (O(1)).
       * - Elements are enumerated in O(n). No guarantees are made on the ordering.
       *
       * ```
       * contract Example {
       *     // Add the library methods
       *     using EnumerableSet for EnumerableSet.AddressSet;
       *
       *     // Declare a set state variable
       *     EnumerableSet.AddressSet private mySet;
       * }
       * ```
       *
       * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
       * and `uint256` (`UintSet`) are supported.
       */
      library EnumerableSet {
          // To implement this library for multiple types with as little code
          // repetition as possible, we write it in terms of a generic Set type with
          // bytes32 values.
          // The Set implementation uses private functions, and user-facing
          // implementations (such as AddressSet) are just wrappers around the
          // underlying Set.
          // This means that we can only create new EnumerableSets for types that fit
          // in bytes32.
          struct Set {
              // Storage of set values
              bytes32[] _values;
              // Position of the value in the `values` array, plus 1 because index 0
              // means a value is not in the set.
              mapping(bytes32 => uint256) _indexes;
          }
          /**
           * @dev Add a value to a set. O(1).
           *
           * Returns true if the value was added to the set, that is if it was not
           * already present.
           */
          function _add(Set storage set, bytes32 value) private returns (bool) {
              if (!_contains(set, value)) {
                  set._values.push(value);
                  // The value is stored at length-1, but we add 1 to all indexes
                  // and use 0 as a sentinel value
                  set._indexes[value] = set._values.length;
                  return true;
              } else {
                  return false;
              }
          }
          /**
           * @dev Removes a value from a set. O(1).
           *
           * Returns true if the value was removed from the set, that is if it was
           * present.
           */
          function _remove(Set storage set, bytes32 value) private returns (bool) {
              // We read and store the value's index to prevent multiple reads from the same storage slot
              uint256 valueIndex = set._indexes[value];
              if (valueIndex != 0) {
                  // Equivalent to contains(set, value)
                  // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
                  // the array, and then remove the last element (sometimes called as 'swap and pop').
                  // This modifies the order of the array, as noted in {at}.
                  uint256 toDeleteIndex = valueIndex - 1;
                  uint256 lastIndex = set._values.length - 1;
                  if (lastIndex != toDeleteIndex) {
                      bytes32 lastvalue = set._values[lastIndex];
                      // Move the last value to the index where the value to delete is
                      set._values[toDeleteIndex] = lastvalue;
                      // Update the index for the moved value
                      set._indexes[lastvalue] = valueIndex; // Replace lastvalue's index to valueIndex
                  }
                  // Delete the slot where the moved value was stored
                  set._values.pop();
                  // Delete the index for the deleted slot
                  delete set._indexes[value];
                  return true;
              } else {
                  return false;
              }
          }
          /**
           * @dev Returns true if the value is in the set. O(1).
           */
          function _contains(Set storage set, bytes32 value) private view returns (bool) {
              return set._indexes[value] != 0;
          }
          /**
           * @dev Returns the number of values on the set. O(1).
           */
          function _length(Set storage set) private view returns (uint256) {
              return set._values.length;
          }
          /**
           * @dev Returns the value stored at position `index` in the set. O(1).
           *
           * Note that there are no guarantees on the ordering of values inside the
           * array, and it may change when more values are added or removed.
           *
           * Requirements:
           *
           * - `index` must be strictly less than {length}.
           */
          function _at(Set storage set, uint256 index) private view returns (bytes32) {
              return set._values[index];
          }
          /**
           * @dev Return the entire set in an array
           *
           * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
           * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
           * this function has an unbounded cost, and using it as part of a state-changing function may render the function
           * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
           */
          function _values(Set storage set) private view returns (bytes32[] memory) {
              return set._values;
          }
          // Bytes32Set
          struct Bytes32Set {
              Set _inner;
          }
          /**
           * @dev Add a value to a set. O(1).
           *
           * Returns true if the value was added to the set, that is if it was not
           * already present.
           */
          function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
              return _add(set._inner, value);
          }
          /**
           * @dev Removes a value from a set. O(1).
           *
           * Returns true if the value was removed from the set, that is if it was
           * present.
           */
          function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
              return _remove(set._inner, value);
          }
          /**
           * @dev Returns true if the value is in the set. O(1).
           */
          function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
              return _contains(set._inner, value);
          }
          /**
           * @dev Returns the number of values in the set. O(1).
           */
          function length(Bytes32Set storage set) internal view returns (uint256) {
              return _length(set._inner);
          }
          /**
           * @dev Returns the value stored at position `index` in the set. O(1).
           *
           * Note that there are no guarantees on the ordering of values inside the
           * array, and it may change when more values are added or removed.
           *
           * Requirements:
           *
           * - `index` must be strictly less than {length}.
           */
          function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
              return _at(set._inner, index);
          }
          /**
           * @dev Return the entire set in an array
           *
           * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
           * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
           * this function has an unbounded cost, and using it as part of a state-changing function may render the function
           * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
           */
          function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
              return _values(set._inner);
          }
          // AddressSet
          struct AddressSet {
              Set _inner;
          }
          /**
           * @dev Add a value to a set. O(1).
           *
           * Returns true if the value was added to the set, that is if it was not
           * already present.
           */
          function add(AddressSet storage set, address value) internal returns (bool) {
              return _add(set._inner, bytes32(uint256(uint160(value))));
          }
          /**
           * @dev Removes a value from a set. O(1).
           *
           * Returns true if the value was removed from the set, that is if it was
           * present.
           */
          function remove(AddressSet storage set, address value) internal returns (bool) {
              return _remove(set._inner, bytes32(uint256(uint160(value))));
          }
          /**
           * @dev Returns true if the value is in the set. O(1).
           */
          function contains(AddressSet storage set, address value) internal view returns (bool) {
              return _contains(set._inner, bytes32(uint256(uint160(value))));
          }
          /**
           * @dev Returns the number of values in the set. O(1).
           */
          function length(AddressSet storage set) internal view returns (uint256) {
              return _length(set._inner);
          }
          /**
           * @dev Returns the value stored at position `index` in the set. O(1).
           *
           * Note that there are no guarantees on the ordering of values inside the
           * array, and it may change when more values are added or removed.
           *
           * Requirements:
           *
           * - `index` must be strictly less than {length}.
           */
          function at(AddressSet storage set, uint256 index) internal view returns (address) {
              return address(uint160(uint256(_at(set._inner, index))));
          }
          /**
           * @dev Return the entire set in an array
           *
           * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
           * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
           * this function has an unbounded cost, and using it as part of a state-changing function may render the function
           * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
           */
          function values(AddressSet storage set) internal view returns (address[] memory) {
              bytes32[] memory store = _values(set._inner);
              address[] memory result;
              assembly {
                  result := store
              }
              return result;
          }
          // UintSet
          struct UintSet {
              Set _inner;
          }
          /**
           * @dev Add a value to a set. O(1).
           *
           * Returns true if the value was added to the set, that is if it was not
           * already present.
           */
          function add(UintSet storage set, uint256 value) internal returns (bool) {
              return _add(set._inner, bytes32(value));
          }
          /**
           * @dev Removes a value from a set. O(1).
           *
           * Returns true if the value was removed from the set, that is if it was
           * present.
           */
          function remove(UintSet storage set, uint256 value) internal returns (bool) {
              return _remove(set._inner, bytes32(value));
          }
          /**
           * @dev Returns true if the value is in the set. O(1).
           */
          function contains(UintSet storage set, uint256 value) internal view returns (bool) {
              return _contains(set._inner, bytes32(value));
          }
          /**
           * @dev Returns the number of values on the set. O(1).
           */
          function length(UintSet storage set) internal view returns (uint256) {
              return _length(set._inner);
          }
          /**
           * @dev Returns the value stored at position `index` in the set. O(1).
           *
           * Note that there are no guarantees on the ordering of values inside the
           * array, and it may change when more values are added or removed.
           *
           * Requirements:
           *
           * - `index` must be strictly less than {length}.
           */
          function at(UintSet storage set, uint256 index) internal view returns (uint256) {
              return uint256(_at(set._inner, index));
          }
          /**
           * @dev Return the entire set in an array
           *
           * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
           * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
           * this function has an unbounded cost, and using it as part of a state-changing function may render the function
           * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
           */
          function values(UintSet storage set) internal view returns (uint256[] memory) {
              bytes32[] memory store = _values(set._inner);
              uint256[] memory result;
              assembly {
                  result := store
              }
              return result;
          }
      }
      

      File 3 of 3: GenArt721Core
      // File contracts/libs/IERC165.sol
      
      // File: openzeppelin-solidity/contracts/introspection/IERC165.sol
      pragma solidity ^0.5.0;
      
      /**
       * @dev Interface of the ERC165 standard, as defined in the
       * [EIP](https://eips.ethereum.org/EIPS/eip-165).
       *
       * Implementers can declare support of contract interfaces, which can then be
       * queried by others (`ERC165Checker`).
       *
       * For an implementation, see `ERC165`.
       */
      interface IERC165 {
          /**
           * @dev Returns true if this contract implements the interface defined by
           * `interfaceId`. See the corresponding
           * [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
           * to learn more about how these ids are created.
           *
           * This function call must use less than 30 000 gas.
           */
          function supportsInterface(bytes4 interfaceId) external view returns (bool);
      }
      
      
      // File contracts/libs/ERC165.sol
      
      // File: openzeppelin-solidity/contracts/introspection/ERC165.sol
      
      pragma solidity ^0.5.0;
      
      
      
      /**
       * @dev Implementation of the `IERC165` interface.
       *
       * Contracts may inherit from this and call `_registerInterface` to declare
       * their support of an interface.
       */
      contract ERC165 is IERC165 {
          /*
           * bytes4(keccak256('supportsInterface(bytes4)')) == 0x01ffc9a7
           */
          bytes4 private constant _INTERFACE_ID_ERC165 = 0x01ffc9a7;
      
          /**
           * @dev Mapping of interface ids to whether or not it's supported.
           */
          mapping(bytes4 => bool) private _supportedInterfaces;
      
          constructor () internal {
              // Derived contracts need only register support for their own interfaces,
              // we register support for ERC165 itself here
              _registerInterface(_INTERFACE_ID_ERC165);
          }
      
          /**
           * @dev See `IERC165.supportsInterface`.
           *
           * Time complexity O(1), guaranteed to always use less than 30 000 gas.
           */
          function supportsInterface(bytes4 interfaceId) external view returns (bool) {
              return _supportedInterfaces[interfaceId];
          }
      
          /**
           * @dev Registers the contract as an implementer of the interface defined by
           * `interfaceId`. Support of the actual ERC165 interface is automatic and
           * registering its interface id is not required.
           *
           * See `IERC165.supportsInterface`.
           *
           * Requirements:
           *
           * - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`).
           */
          function _registerInterface(bytes4 interfaceId) internal {
              require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
              _supportedInterfaces[interfaceId] = true;
          }
      }
      
      
      // File contracts/libs/IERC721.sol
      
      // File: openzeppelin-solidity/contracts/token/ERC721/IERC721.sol
      
      pragma solidity ^0.5.0;
      
      
      
      /**
       * @dev Required interface of an ERC721 compliant contract.
       */
      contract IERC721 is IERC165 {
          event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
          event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
          event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
      
          /**
           * @dev Returns the number of NFTs in `owner`'s account.
           */
          function balanceOf(address owner) public view returns (uint256 balance);
      
          /**
           * @dev Returns the owner of the NFT specified by `tokenId`.
           */
          function ownerOf(uint256 tokenId) public view returns (address owner);
      
          /**
           * @dev Transfers a specific NFT (`tokenId`) from one account (`from`) to
           * another (`to`).
           *
           *
           *
           * Requirements:
           * - `from`, `to` cannot be zero.
           * - `tokenId` must be owned by `from`.
           * - If the caller is not `from`, it must be have been allowed to move this
           * NFT by either `approve` or `setApproveForAll`.
           */
          function safeTransferFrom(address from, address to, uint256 tokenId) public;
          /**
           * @dev Transfers a specific NFT (`tokenId`) from one account (`from`) to
           * another (`to`).
           *
           * Requirements:
           * - If the caller is not `from`, it must be approved to move this NFT by
           * either `approve` or `setApproveForAll`.
           */
          function transferFrom(address from, address to, uint256 tokenId) public;
          function approve(address to, uint256 tokenId) public;
          function getApproved(uint256 tokenId) public view returns (address operator);
      
          function setApprovalForAll(address operator, bool _approved) public;
          function isApprovedForAll(address owner, address operator) public view returns (bool);
      
      
          function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public;
      }
      
      
      // File contracts/libs/SafeMath.sol
      
      // File: openzeppelin-solidity/contracts/math/SafeMath.sol
      
      pragma solidity ^0.5.0;
      
      /**
       * @dev Wrappers over Solidity's arithmetic operations with added overflow
       * checks.
       *
       * Arithmetic operations in Solidity wrap on overflow. This can easily result
       * in bugs, because programmers usually assume that an overflow raises an
       * error, which is the standard behavior in high level programming languages.
       * `SafeMath` restores this intuition by reverting the transaction when an
       * operation overflows.
       *
       * Using this library instead of the unchecked operations eliminates an entire
       * class of bugs, so it's recommended to use it always.
       */
      library SafeMath {
          /**
           * @dev Returns the addition of two unsigned integers, reverting on
           * overflow.
           *
           * Counterpart to Solidity's `+` operator.
           *
           * Requirements:
           * - Addition cannot overflow.
           */
          function add(uint256 a, uint256 b) internal pure returns (uint256) {
              uint256 c = a + b;
              require(c >= a, "SafeMath: addition overflow");
      
              return c;
          }
      
          /**
           * @dev Returns the subtraction of two unsigned integers, reverting on
           * overflow (when the result is negative).
           *
           * Counterpart to Solidity's `-` operator.
           *
           * Requirements:
           * - Subtraction cannot overflow.
           */
          function sub(uint256 a, uint256 b) internal pure returns (uint256) {
              require(b <= a, "SafeMath: subtraction overflow");
              uint256 c = a - b;
      
              return c;
          }
      
          /**
           * @dev Returns the multiplication of two unsigned integers, reverting on
           * overflow.
           *
           * Counterpart to Solidity's `*` operator.
           *
           * Requirements:
           * - Multiplication cannot overflow.
           */
          function mul(uint256 a, uint256 b) internal pure returns (uint256) {
              // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
              // benefit is lost if 'b' is also tested.
              // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
              if (a == 0) {
                  return 0;
              }
      
              uint256 c = a * b;
              require(c / a == b, "SafeMath: multiplication overflow");
      
              return c;
          }
      
          /**
           * @dev Returns the integer division of two unsigned integers. Reverts on
           * division by zero. The result is rounded towards zero.
           *
           * Counterpart to Solidity's `/` operator. Note: this function uses a
           * `revert` opcode (which leaves remaining gas untouched) while Solidity
           * uses an invalid opcode to revert (consuming all remaining gas).
           *
           * Requirements:
           * - The divisor cannot be zero.
           */
          function div(uint256 a, uint256 b) internal pure returns (uint256) {
              // Solidity only automatically asserts when dividing by 0
              require(b > 0, "SafeMath: division by zero");
              uint256 c = a / b;
              // assert(a == b * c + a % b); // There is no case in which this doesn't hold
      
              return c;
          }
      }
      
      
      // File contracts/libs/Address.sol
      
      // File: openzeppelin-solidity/contracts/utils/Address.sol
      
      pragma solidity ^0.5.0;
      
      /**
       * @dev Collection of functions related to the address type,
       */
      library Address {
          /**
           * @dev Returns true if `account` is a contract.
           *
           * This test is non-exhaustive, and there may be false-negatives: during the
           * execution of a contract's constructor, its address will be reported as
           * not containing a contract.
           *
           * > It is unsafe to assume that an address for which this function returns
           * false is an externally-owned account (EOA) and not a contract.
           */
          function isContract(address account) internal view returns (bool) {
              // This method relies in extcodesize, which returns 0 for contracts in
              // construction, since the code is only stored at the end of the
              // constructor execution.
      
              uint256 size;
              // solhint-disable-next-line no-inline-assembly
              assembly { size := extcodesize(account) }
              return size > 0;
          }
      }
      
      
      // File contracts/libs/Counters.sol
      
      // File: openzeppelin-solidity/contracts/drafts/Counters.sol
      
      pragma solidity ^0.5.0;
      
      
      
      /**
       * @title Counters
       * @author Matt Condon (@shrugs)
       * @dev Provides counters that can only be incremented or decremented by one. This can be used e.g. to track the number
       * of elements in a mapping, issuing ERC721 ids, or counting request ids.
       *
       * Include with `using Counters for Counters.Counter;`
       * Since it is not possible to overflow a 256 bit integer with increments of one, `increment` can skip the SafeMath
       * overflow check, thereby saving gas. This does assume however correct usage, in that the underlying `_value` is never
       * directly accessed.
       */
      library Counters {
          using SafeMath for uint256;
      
          struct Counter {
              // This variable should never be directly accessed by users of the library: interactions must be restricted to
              // the library's function. As of Solidity v0.5.2, this cannot be enforced, though there is a proposal to add
              // this feature: see https://github.com/ethereum/solidity/issues/4637
              uint256 _value; // default: 0
          }
      
          function current(Counter storage counter) internal view returns (uint256) {
              return counter._value;
          }
      
          function increment(Counter storage counter) internal {
              counter._value += 1;
          }
      
          function decrement(Counter storage counter) internal {
              counter._value = counter._value.sub(1);
          }
      }
      
      
      // File contracts/libs/IERC721Receiver.sol
      
      // File: openzeppelin-solidity/contracts/token/ERC721/IERC721Receiver.sol
      
      pragma solidity ^0.5.0;
      
      /**
       * @title ERC721 token receiver interface
       * @dev Interface for any contract that wants to support safeTransfers
       * from ERC721 asset contracts.
       */
      contract IERC721Receiver {
          function onERC721Received(address operator, address from, uint256 tokenId, bytes memory data)
          public returns (bytes4);
      }
      
      
      // File contracts/libs/ERC721.sol
      
      // File: openzeppelin-solidity/contracts/token/ERC721/ERC721.sol
      
      pragma solidity ^0.5.0;
      
      
      
      
      
      
      
      /**
       * @title ERC721 Non-Fungible Token Standard basic implementation
       * @dev see https://eips.ethereum.org/EIPS/eip-721
       */
      contract ERC721 is ERC165, IERC721 {
          using SafeMath for uint256;
          using Address for address;
          using Counters for Counters.Counter;
      
          // Equals to `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
          // which can be also obtained as `IERC721Receiver(0).onERC721Received.selector`
          bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;
      
          // Mapping from token ID to owner
          mapping (uint256 => address) private _tokenOwner;
      
          // Mapping from token ID to approved address
          mapping (uint256 => address) private _tokenApprovals;
      
          // Mapping from owner to number of owned token
          mapping (address => Counters.Counter) private _ownedTokensCount;
      
          // Mapping from owner to operator approvals
          mapping (address => mapping (address => bool)) private _operatorApprovals;
          
          bytes4 private constant _INTERFACE_ID_ERC721 = 0x80ac58cd;
      
          constructor () public {
              // register the supported interfaces to conform to ERC721 via ERC165
              _registerInterface(_INTERFACE_ID_ERC721);
          }
      
      
          function balanceOf(address owner) public view returns (uint256) {
              require(owner != address(0), "ERC721: balance query for the zero address");
      
              return _ownedTokensCount[owner].current();
          }
      
          function ownerOf(uint256 tokenId) public view returns (address) {
              address owner = _tokenOwner[tokenId];
              require(owner != address(0), "ERC721: owner query for nonexistent token");
      
              return owner;
          }
      
          function approve(address to, uint256 tokenId) public {
              address owner = ownerOf(tokenId);
              require(to != owner, "ERC721: approval to current owner");
      
              require(msg.sender == owner || isApprovedForAll(owner, msg.sender),
                  "ERC721: approve caller is not owner nor approved for all"
              );
      
              _tokenApprovals[tokenId] = to;
              emit Approval(owner, to, tokenId);
          }
      
          function getApproved(uint256 tokenId) public view returns (address) {
              require(_exists(tokenId), "ERC721: approved query for nonexistent token");
      
              return _tokenApprovals[tokenId];
          }
      
          function setApprovalForAll(address to, bool approved) public {
              require(to != msg.sender, "ERC721: approve to caller");
      
              _operatorApprovals[msg.sender][to] = approved;
              emit ApprovalForAll(msg.sender, to, approved);
          }
      
          function isApprovedForAll(address owner, address operator) public view returns (bool) {
              return _operatorApprovals[owner][operator];
          }
      
          function transferFrom(address from, address to, uint256 tokenId) public {
              //solhint-disable-next-line max-line-length
              require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved");
      
              _transferFrom(from, to, tokenId);
          }
      
          function safeTransferFrom(address from, address to, uint256 tokenId) public {
              safeTransferFrom(from, to, tokenId, "");
          }
      
          function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public {
              transferFrom(from, to, tokenId);
              require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
          }
      
          function _exists(uint256 tokenId) internal view returns (bool) {
              address owner = _tokenOwner[tokenId];
              return owner != address(0);
          }
      
          function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
              require(_exists(tokenId), "ERC721: operator query for nonexistent token");
              address owner = ownerOf(tokenId);
              return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
          }
      
          function _mint(address to, uint256 tokenId) internal {
              require(to != address(0), "ERC721: mint to the zero address");
              require(!_exists(tokenId), "ERC721: token already minted");
      
              _tokenOwner[tokenId] = to;
              _ownedTokensCount[to].increment();
      
              emit Transfer(address(0), to, tokenId);
          }
      
          function _burn(address owner, uint256 tokenId) internal {
              require(ownerOf(tokenId) == owner, "ERC721: burn of token that is not own");
      
              _clearApproval(tokenId);
      
              _ownedTokensCount[owner].decrement();
              _tokenOwner[tokenId] = address(0);
      
              emit Transfer(owner, address(0), tokenId);
          }
      
          function _burn(uint256 tokenId) internal {
              _burn(ownerOf(tokenId), tokenId);
          }
      
          function _transferFrom(address from, address to, uint256 tokenId) internal {
              require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own");
              require(to != address(0), "ERC721: transfer to the zero address");
      
              _clearApproval(tokenId);
      
              _ownedTokensCount[from].decrement();
              _ownedTokensCount[to].increment();
      
              _tokenOwner[tokenId] = to;
      
              emit Transfer(from, to, tokenId);
          }
      
          function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data)
          internal returns (bool)
          {
              if (!to.isContract()) {
                  return true;
              }
      
              bytes4 retval = IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, _data);
              return (retval == _ERC721_RECEIVED);
          }
      
          function _clearApproval(uint256 tokenId) private {
              if (_tokenApprovals[tokenId] != address(0)) {
                  _tokenApprovals[tokenId] = address(0);
              }
          }
      }
      
      
      // File contracts/libs/IERC721Enumerable.sol
      
      // File: openzeppelin-solidity/contracts/token/ERC721/IERC721Enumerable.sol
      
      pragma solidity ^0.5.0;
      
      
      
      /**
       * @title ERC-721 Non-Fungible Token Standard, optional enumeration extension
       * @dev See https://eips.ethereum.org/EIPS/eip-721
       */
      contract IERC721Enumerable is IERC721 {
          function totalSupply() public view returns (uint256);
          function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256 tokenId);
      
          function tokenByIndex(uint256 index) public view returns (uint256);
      }
      
      
      // File contracts/libs/ERC721Enumerable.sol
      
      // File: openzeppelin-solidity/contracts/token/ERC721/ERC721Enumerable.sol
      
      pragma solidity ^0.5.0;
      
      
      
      
      
      
      
      /**
       * @title ERC-721 Non-Fungible Token with optional enumeration extension logic
       * @dev See https://eips.ethereum.org/EIPS/eip-721
       */
      contract ERC721Enumerable is ERC165, ERC721, IERC721Enumerable {
          // Mapping from owner to list of owned token IDs
          mapping(address => uint256[]) private _ownedTokens;
      
          // Mapping from token ID to index of the owner tokens list
          mapping(uint256 => uint256) private _ownedTokensIndex;
      
          // Array with all token ids, used for enumeration
          uint256[] private _allTokens;
      
          // Mapping from token id to position in the allTokens array
          mapping(uint256 => uint256) private _allTokensIndex;
      
          /*
           *     bytes4(keccak256('totalSupply()')) == 0x18160ddd
           *     bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) == 0x2f745c59
           *     bytes4(keccak256('tokenByIndex(uint256)')) == 0x4f6ccce7
           *
           *     => 0x18160ddd ^ 0x2f745c59 ^ 0x4f6ccce7 == 0x780e9d63
           */
          bytes4 private constant _INTERFACE_ID_ERC721_ENUMERABLE = 0x780e9d63;
      
          /**
           * @dev Constructor function.
           */
          constructor () public {
              // register the supported interface to conform to ERC721Enumerable via ERC165
              _registerInterface(_INTERFACE_ID_ERC721_ENUMERABLE);
          }
      
          /**
           * @dev Gets the token ID at a given index of the tokens list of the requested owner.
           * @param owner address owning the tokens list to be accessed
           * @param index uint256 representing the index to be accessed of the requested tokens list
           * @return uint256 token ID at the given index of the tokens list owned by the requested address
           */
          function tokenOfOwnerByIndex(address owner, uint256 index) public view returns (uint256) {
              require(index < balanceOf(owner), "ERC721Enumerable: owner index out of bounds");
              return _ownedTokens[owner][index];
          }
      
          /**
           * @dev Gets the total amount of tokens stored by the contract.
           * @return uint256 representing the total amount of tokens
           */
          function totalSupply() public view returns (uint256) {
              return _allTokens.length;
          }
      
          /**
           * @dev Gets the token ID at a given index of all the tokens in this contract
           * Reverts if the index is greater or equal to the total number of tokens.
           * @param index uint256 representing the index to be accessed of the tokens list
           * @return uint256 token ID at the given index of the tokens list
           */
          function tokenByIndex(uint256 index) public view returns (uint256) {
              require(index < totalSupply(), "ERC721Enumerable: global index out of bounds");
              return _allTokens[index];
          }
      
          /**
           * @dev Internal function to transfer ownership of a given token ID to another address.
           * As opposed to transferFrom, this imposes no restrictions on msg.sender.
           * @param from current owner of the token
           * @param to address to receive the ownership of the given token ID
           * @param tokenId uint256 ID of the token to be transferred
           */
          function _transferFrom(address from, address to, uint256 tokenId) internal {
              super._transferFrom(from, to, tokenId);
      
              _removeTokenFromOwnerEnumeration(from, tokenId);
      
              _addTokenToOwnerEnumeration(to, tokenId);
          }
      
          /**
           * @dev Internal function to mint a new token.
           * Reverts if the given token ID already exists.
           * @param to address the beneficiary that will own the minted token
           * @param tokenId uint256 ID of the token to be minted
           */
          function _mint(address to, uint256 tokenId) internal {
              super._mint(to, tokenId);
      
              _addTokenToOwnerEnumeration(to, tokenId);
      
              _addTokenToAllTokensEnumeration(tokenId);
          }
      
          /**
           * @dev Internal function to burn a specific token.
           * Reverts if the token does not exist.
           * Deprecated, use _burn(uint256) instead.
           * @param owner owner of the token to burn
           * @param tokenId uint256 ID of the token being burned
           */
          function _burn(address owner, uint256 tokenId) internal {
              super._burn(owner, tokenId);
      
              _removeTokenFromOwnerEnumeration(owner, tokenId);
              // Since tokenId will be deleted, we can clear its slot in _ownedTokensIndex to trigger a gas refund
              _ownedTokensIndex[tokenId] = 0;
      
              _removeTokenFromAllTokensEnumeration(tokenId);
          }
      
          /**
           * @dev Gets the list of token IDs of the requested owner.
           * @param owner address owning the tokens
           * @return uint256[] List of token IDs owned by the requested address
           */
          function _tokensOfOwner(address owner) internal view returns (uint256[] storage) {
              return _ownedTokens[owner];
          }
      
          /**
           * @dev Private function to add a token to this extension's ownership-tracking data structures.
           * @param to address representing the new owner of the given token ID
           * @param tokenId uint256 ID of the token to be added to the tokens list of the given address
           */
          function _addTokenToOwnerEnumeration(address to, uint256 tokenId) private {
              _ownedTokensIndex[tokenId] = _ownedTokens[to].length;
              _ownedTokens[to].push(tokenId);
          }
      
          /**
           * @dev Private function to add a token to this extension's token tracking data structures.
           * @param tokenId uint256 ID of the token to be added to the tokens list
           */
          function _addTokenToAllTokensEnumeration(uint256 tokenId) private {
              _allTokensIndex[tokenId] = _allTokens.length;
              _allTokens.push(tokenId);
          }
      
          /**
           * @dev Private function to remove a token from this extension's ownership-tracking data structures. Note that
           * while the token is not assigned a new owner, the _ownedTokensIndex mapping is _not_ updated: this allows for
           * gas optimizations e.g. when performing a transfer operation (avoiding double writes).
           * This has O(1) time complexity, but alters the order of the _ownedTokens array.
           * @param from address representing the previous owner of the given token ID
           * @param tokenId uint256 ID of the token to be removed from the tokens list of the given address
           */
          function _removeTokenFromOwnerEnumeration(address from, uint256 tokenId) private {
              // To prevent a gap in from's tokens array, we store the last token in the index of the token to delete, and
              // then delete the last slot (swap and pop).
      
              uint256 lastTokenIndex = _ownedTokens[from].length.sub(1);
              uint256 tokenIndex = _ownedTokensIndex[tokenId];
      
              // When the token to delete is the last token, the swap operation is unnecessary
              if (tokenIndex != lastTokenIndex) {
                  uint256 lastTokenId = _ownedTokens[from][lastTokenIndex];
      
                  _ownedTokens[from][tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
                  _ownedTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
              }
      
              // This also deletes the contents at the last position of the array
              _ownedTokens[from].length--;
      
              // Note that _ownedTokensIndex[tokenId] hasn't been cleared: it still points to the old slot (now occupied by
              // lastTokenId, or just over the end of the array if the token was the last one).
          }
      
          /**
           * @dev Private function to remove a token from this extension's token tracking data structures.
           * This has O(1) time complexity, but alters the order of the _allTokens array.
           * @param tokenId uint256 ID of the token to be removed from the tokens list
           */
          function _removeTokenFromAllTokensEnumeration(uint256 tokenId) private {
              // To prevent a gap in the tokens array, we store the last token in the index of the token to delete, and
              // then delete the last slot (swap and pop).
      
              uint256 lastTokenIndex = _allTokens.length.sub(1);
              uint256 tokenIndex = _allTokensIndex[tokenId];
      
              // When the token to delete is the last token, the swap operation is unnecessary. However, since this occurs so
              // rarely (when the last minted token is burnt) that we still do the swap here to avoid the gas cost of adding
              // an 'if' statement (like in _removeTokenFromOwnerEnumeration)
              uint256 lastTokenId = _allTokens[lastTokenIndex];
      
              _allTokens[tokenIndex] = lastTokenId; // Move the last token to the slot of the to-delete token
              _allTokensIndex[lastTokenId] = tokenIndex; // Update the moved token's index
      
              // This also deletes the contents at the last position of the array
              _allTokens.length--;
              _allTokensIndex[tokenId] = 0;
          }
      }
      
      
      // File contracts/libs/CustomERC721Metadata.sol
      
      // File: contracts/CustomERC721Metadata.sol
      
      pragma solidity ^0.5.0;
      
      
      
      
      
      
      /**
       * ERC721 base contract without the concept of tokenUri as this is managed by the parent
       */
      contract CustomERC721Metadata is ERC165, ERC721, ERC721Enumerable {
      
          // Token name
          string private _name;
      
          // Token symbol
          string private _symbol;
      
          bytes4 private constant _INTERFACE_ID_ERC721_METADATA = 0x5b5e139f;
      
          /**
           * @dev Constructor function
           */
          constructor (string memory name, string memory symbol) public {
              _name = name;
              _symbol = symbol;
      
              // register the supported interfaces to conform to ERC721 via ERC165
              _registerInterface(_INTERFACE_ID_ERC721_METADATA);
          }
      
          /**
           * @dev Gets the token name
           * @return string representing the token name
           */
          function name() external view returns (string memory) {
              return _name;
          }
      
          /**
           * @dev Gets the token symbol
           * @return string representing the token symbol
           */
          function symbol() external view returns (string memory) {
              return _symbol;
          }
      
      }
      
      
      // File contracts/libs/Strings.sol
      
      // File: contracts/Strings.sol
      
      pragma solidity ^0.5.0;
      
      //https://github.com/oraclize/ethereum-api/blob/master/oraclizeAPI_0.5.sol
      library Strings {
      
          function strConcat(string memory _a, string memory _b) internal pure returns (string memory _concatenatedString) {
              return strConcat(_a, _b, "", "", "");
          }
      
          function strConcat(string memory _a, string memory _b, string memory _c) internal pure returns (string memory _concatenatedString) {
              return strConcat(_a, _b, _c, "", "");
          }
      
          function strConcat(string memory _a, string memory _b, string memory _c, string memory _d) internal pure returns (string memory _concatenatedString) {
              return strConcat(_a, _b, _c, _d, "");
          }
      
          function strConcat(string memory _a, string memory _b, string memory _c, string memory _d, string memory _e) internal pure returns (string memory _concatenatedString) {
              bytes memory _ba = bytes(_a);
              bytes memory _bb = bytes(_b);
              bytes memory _bc = bytes(_c);
              bytes memory _bd = bytes(_d);
              bytes memory _be = bytes(_e);
              string memory abcde = new string(_ba.length + _bb.length + _bc.length + _bd.length + _be.length);
              bytes memory babcde = bytes(abcde);
              uint k = 0;
              uint i = 0;
              for (i = 0; i < _ba.length; i++) {
                  babcde[k++] = _ba[i];
              }
              for (i = 0; i < _bb.length; i++) {
                  babcde[k++] = _bb[i];
              }
              for (i = 0; i < _bc.length; i++) {
                  babcde[k++] = _bc[i];
              }
              for (i = 0; i < _bd.length; i++) {
                  babcde[k++] = _bd[i];
              }
              for (i = 0; i < _be.length; i++) {
                  babcde[k++] = _be[i];
              }
              return string(babcde);
          }
      
          function uint2str(uint _i) internal pure returns (string memory _uintAsString) {
              if (_i == 0) {
                  return "0";
              }
              uint j = _i;
              uint len;
              while (j != 0) {
                  len++;
                  j /= 10;
              }
              bytes memory bstr = new bytes(len);
              uint k = len - 1;
              while (_i != 0) {
                  bstr[k--] = byte(uint8(48 + _i % 10));
                  _i /= 10;
              }
              return string(bstr);
          }
      }
      
      
      // File contracts/GenArt721Core.sol
      
      // File: contracts/GenArt721Core.sol
      
      //0x1454EFCa69FA654e5A7d83CB61c1aD81790c44B7
      
      //https://oneclickdapp.com/radar-valery/
      
      pragma solidity ^0.5.0;
      
      
      
      
      interface Randomizer {
         function returnValue() external view returns(bytes32);
      }
      
      contract GenArt721Core is CustomERC721Metadata {
          using SafeMath for uint256;
      
          event Mint(
              address indexed _to,
              uint256 indexed _tokenId,
              uint256 indexed _projectId
      
          );
      
          Randomizer public randomizerContract;
      
          struct Project {
              string name;
              string artist;
              string description;
              string website;
              string license;
              bool dynamic;
              string projectBaseURI;
              string projectBaseIpfsURI;
              uint256 invocations;
              uint256 maxInvocations;
              string scriptJSON;
              mapping(uint256 => string) scripts;
              uint scriptCount;
              string ipfsHash;
              bool useHashString;
              bool useIpfs;
              bool active;
              bool locked;
              bool paused;
      
          }
      
          uint256 constant ONE_MILLION = 1_000_000;
          mapping(uint256 => Project) projects;
      
          //All financial functions are stripped from struct for visibility
          mapping(uint256 => address) public projectIdToArtistAddress;
          mapping(uint256 => string) public projectIdToCurrencySymbol;
          mapping(uint256 => address) public projectIdToCurrencyAddress;
          mapping(uint256 => uint256) public projectIdToPricePerTokenInWei;
          mapping(uint256 => address) public projectIdToAdditionalPayee;
          mapping(uint256 => uint256) public projectIdToAdditionalPayeePercentage;
          mapping(uint256 => uint256) public projectIdToSecondaryMarketRoyaltyPercentage;
      
          address public artblocksAddress;
          uint256 public artblocksPercentage = 10;
      
          mapping(uint256 => string) public staticIpfsImageLink;
          mapping(uint256 => uint256) public tokenIdToProjectId;
          mapping(uint256 => uint256[]) internal projectIdToTokenIds;
          mapping(uint256 => bytes32) public tokenIdToHash;
          mapping(bytes32 => uint256) public hashToTokenId;
      
          address public admin;
          mapping(address => bool) public isWhitelisted;
          mapping(address => bool) public isMintWhitelisted;
      
          uint256 public nextProjectId = 3;
      
          modifier onlyValidTokenId(uint256 _tokenId) {
              require(_exists(_tokenId), "Token ID does not exist");
              _;
          }
      
          modifier onlyUnlocked(uint256 _projectId) {
              require(!projects[_projectId].locked, "Only if unlocked");
              _;
          }
      
          modifier onlyArtist(uint256 _projectId) {
              require(msg.sender == projectIdToArtistAddress[_projectId], "Only artist");
              _;
          }
      
          modifier onlyAdmin() {
              require(msg.sender == admin, "Only admin");
              _;
          }
      
          modifier onlyWhitelisted() {
              require(isWhitelisted[msg.sender], "Only whitelisted");
              _;
          }
      
          modifier onlyArtistOrWhitelisted(uint256 _projectId) {
              require(isWhitelisted[msg.sender] || msg.sender == projectIdToArtistAddress[_projectId], "Only artist or whitelisted");
              _;
          }
      
          constructor(string memory _tokenName, string memory _tokenSymbol, address _randomizerContract) CustomERC721Metadata(_tokenName, _tokenSymbol) public {
              admin = msg.sender;
              isWhitelisted[msg.sender] = true;
              artblocksAddress = msg.sender;
              randomizerContract = Randomizer(_randomizerContract);
      
          }
      
          function mint(address _to, uint256 _projectId, address _by) external returns (uint256 _tokenId) {
              require(isMintWhitelisted[msg.sender], "Must mint from whitelisted minter contract.");
              require(projects[_projectId].invocations.add(1) <= projects[_projectId].maxInvocations, "Must not exceed max invocations");
              require(projects[_projectId].active || _by == projectIdToArtistAddress[_projectId], "Project must exist and be active");
              require(!projects[_projectId].paused || _by == projectIdToArtistAddress[_projectId], "Purchases are paused.");
      
      
              uint256 tokenId = _mintToken(_to, _projectId);
      
              return tokenId;
          }
      
          function _mintToken(address _to, uint256 _projectId) internal returns (uint256 _tokenId) {
      
              uint256 tokenIdToBe = (_projectId * ONE_MILLION) + projects[_projectId].invocations;
      
              projects[_projectId].invocations = projects[_projectId].invocations.add(1);
      
      
                  bytes32 hash = keccak256(abi.encodePacked(projects[_projectId].invocations, block.number, blockhash(block.number - 1), msg.sender, randomizerContract.returnValue()));
                  tokenIdToHash[tokenIdToBe]=hash;
                  hashToTokenId[hash] = tokenIdToBe;
      
      
              _mint(_to, tokenIdToBe);
      
              tokenIdToProjectId[tokenIdToBe] = _projectId;
              projectIdToTokenIds[_projectId].push(tokenIdToBe);
      
              emit Mint(_to, tokenIdToBe, _projectId);
      
              return tokenIdToBe;
          }
          function updateArtblocksAddress(address _artblocksAddress) public onlyAdmin {
              artblocksAddress = _artblocksAddress;
          }
      
          function updateArtblocksPercentage(uint256 _artblocksPercentage) public onlyAdmin {
              require(_artblocksPercentage <= 25, "Max of 25%");
              artblocksPercentage = _artblocksPercentage;
          }
      
          function addWhitelisted(address _address) public onlyAdmin {
              isWhitelisted[_address] = true;
          }
      
          function removeWhitelisted(address _address) public onlyAdmin {
              isWhitelisted[_address] = false;
          }
      
          function addMintWhitelisted(address _address) public onlyAdmin {
              isMintWhitelisted[_address] = true;
          }
      
          function removeMintWhitelisted(address _address) public onlyAdmin {
              isMintWhitelisted[_address] = false;
          }
      
          function updateRandomizerAddress(address _randomizerAddress) public onlyWhitelisted {
            randomizerContract = Randomizer(_randomizerAddress);
          }
          function toggleProjectIsLocked(uint256 _projectId) public onlyWhitelisted onlyUnlocked(_projectId) {
              projects[_projectId].locked = true;
          }
      
          function toggleProjectIsActive(uint256 _projectId) public onlyWhitelisted {
              projects[_projectId].active = !projects[_projectId].active;
          }
      
          function updateProjectArtistAddress(uint256 _projectId, address _artistAddress) public onlyArtistOrWhitelisted(_projectId) {
              projectIdToArtistAddress[_projectId] = _artistAddress;
          }
      
          function toggleProjectIsPaused(uint256 _projectId) public onlyArtist(_projectId) {
              projects[_projectId].paused = !projects[_projectId].paused;
          }
      
          function addProject(string memory _projectName, address _artistAddress, uint256 _pricePerTokenInWei, bool _dynamic) public onlyWhitelisted {
      
              uint256 projectId = nextProjectId;
              projectIdToArtistAddress[projectId] = _artistAddress;
              projects[projectId].name = _projectName;
              projectIdToCurrencySymbol[projectId] = "ETH";
              projectIdToPricePerTokenInWei[projectId] = _pricePerTokenInWei;
              projects[projectId].paused=true;
              projects[projectId].dynamic=_dynamic;
              projects[projectId].maxInvocations = ONE_MILLION;
              if (!_dynamic) {
                  projects[projectId].useHashString = false;
              } else {
                  projects[projectId].useHashString = true;
              }
              nextProjectId = nextProjectId.add(1);
          }
      
          function updateProjectCurrencyInfo(uint256 _projectId, string memory _currencySymbol, address _currencyAddress) onlyArtist(_projectId) public {
              projectIdToCurrencySymbol[_projectId] = _currencySymbol;
              projectIdToCurrencyAddress[_projectId] = _currencyAddress;
          }
      
          function updateProjectPricePerTokenInWei(uint256 _projectId, uint256 _pricePerTokenInWei) onlyArtist(_projectId) public {
              projectIdToPricePerTokenInWei[_projectId] = _pricePerTokenInWei;
          }
      
          function updateProjectName(uint256 _projectId, string memory _projectName) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
              projects[_projectId].name = _projectName;
          }
      
          function updateProjectArtistName(uint256 _projectId, string memory _projectArtistName) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
              projects[_projectId].artist = _projectArtistName;
          }
      
          function updateProjectAdditionalPayeeInfo(uint256 _projectId, address _additionalPayee, uint256 _additionalPayeePercentage) onlyArtist(_projectId) public {
              require(_additionalPayeePercentage <= 100, "Max of 100%");
              projectIdToAdditionalPayee[_projectId] = _additionalPayee;
              projectIdToAdditionalPayeePercentage[_projectId] = _additionalPayeePercentage;
          }
      
          function updateProjectSecondaryMarketRoyaltyPercentage(uint256 _projectId, uint256 _secondMarketRoyalty) onlyArtist(_projectId) public {
              require(_secondMarketRoyalty <= 100, "Max of 100%");
              projectIdToSecondaryMarketRoyaltyPercentage[_projectId] = _secondMarketRoyalty;
          }
      
          function updateProjectDescription(uint256 _projectId, string memory _projectDescription) onlyArtist(_projectId) public {
              projects[_projectId].description = _projectDescription;
          }
      
          function updateProjectWebsite(uint256 _projectId, string memory _projectWebsite) onlyArtist(_projectId) public {
              projects[_projectId].website = _projectWebsite;
          }
      
          function updateProjectLicense(uint256 _projectId, string memory _projectLicense) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
              projects[_projectId].license = _projectLicense;
          }
      
          function updateProjectMaxInvocations(uint256 _projectId, uint256 _maxInvocations) onlyArtist(_projectId) public {
              require((!projects[_projectId].locked || _maxInvocations<projects[_projectId].maxInvocations), "Only if unlocked");
              require(_maxInvocations > projects[_projectId].invocations, "You must set max invocations greater than current invocations");
              require(_maxInvocations <= ONE_MILLION, "Cannot exceed 1,000,000");
              projects[_projectId].maxInvocations = _maxInvocations;
          }
      
          function toggleProjectUseHashString(uint256 _projectId) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
            require(projects[_projectId].invocations == 0, "Cannot modify after a token is minted.");
            projects[_projectId].useHashString = !projects[_projectId].useHashString;
          }
      
          function addProjectScript(uint256 _projectId, string memory _script) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
              projects[_projectId].scripts[projects[_projectId].scriptCount] = _script;
              projects[_projectId].scriptCount = projects[_projectId].scriptCount.add(1);
          }
      
          function updateProjectScript(uint256 _projectId, uint256 _scriptId, string memory _script) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
              require(_scriptId < projects[_projectId].scriptCount, "scriptId out of range");
              projects[_projectId].scripts[_scriptId] = _script;
          }
      
          function removeProjectLastScript(uint256 _projectId) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
              require(projects[_projectId].scriptCount > 0, "there are no scripts to remove");
              delete projects[_projectId].scripts[projects[_projectId].scriptCount - 1];
              projects[_projectId].scriptCount = projects[_projectId].scriptCount.sub(1);
          }
      
          function updateProjectScriptJSON(uint256 _projectId, string memory _projectScriptJSON) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
              projects[_projectId].scriptJSON = _projectScriptJSON;
          }
      
          function updateProjectIpfsHash(uint256 _projectId, string memory _ipfsHash) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
              projects[_projectId].ipfsHash = _ipfsHash;
          }
      
          function updateProjectBaseURI(uint256 _projectId, string memory _newBaseURI) onlyArtist(_projectId) public {
              projects[_projectId].projectBaseURI = _newBaseURI;
          }
      
          function updateProjectBaseIpfsURI(uint256 _projectId, string memory _projectBaseIpfsURI) onlyArtist(_projectId) public {
              projects[_projectId].projectBaseIpfsURI = _projectBaseIpfsURI;
          }
      
          function toggleProjectUseIpfsForStatic(uint256 _projectId) onlyArtist(_projectId) public {
              require(!projects[_projectId].dynamic, "can only set static IPFS hash for static projects");
              projects[_projectId].useIpfs = !projects[_projectId].useIpfs;
          }
      
          function toggleProjectIsDynamic(uint256 _projectId) onlyUnlocked(_projectId) onlyArtistOrWhitelisted(_projectId) public {
            require(projects[_projectId].invocations == 0, "Can not switch after a token is minted.");
              if (projects[_projectId].dynamic) {
                  projects[_projectId].useHashString = false;
              } else {
                  projects[_projectId].useHashString = true;
              }
              projects[_projectId].dynamic = !projects[_projectId].dynamic;
          }
      
          function overrideTokenDynamicImageWithIpfsLink(uint256 _tokenId, string memory _ipfsHash) onlyArtist(tokenIdToProjectId[_tokenId]) public {
              staticIpfsImageLink[_tokenId] = _ipfsHash;
          }
      
          function clearTokenIpfsImageUri(uint256 _tokenId) onlyArtist(tokenIdToProjectId[_tokenId]) public {
              delete staticIpfsImageLink[tokenIdToProjectId[_tokenId]];
          }
      
          function projectDetails(uint256 _projectId) view public returns (string memory projectName, string memory artist, string memory description, string memory website, string memory license, bool dynamic) {
              projectName = projects[_projectId].name;
              artist = projects[_projectId].artist;
              description = projects[_projectId].description;
              website = projects[_projectId].website;
              license = projects[_projectId].license;
              dynamic = projects[_projectId].dynamic;
          }
      
          function projectTokenInfo(uint256 _projectId) view public returns (address artistAddress, uint256 pricePerTokenInWei, uint256 invocations, uint256 maxInvocations, bool active, address additionalPayee, uint256 additionalPayeePercentage ,string memory currency, address currencyAddress) {
              artistAddress = projectIdToArtistAddress[_projectId];
              pricePerTokenInWei = projectIdToPricePerTokenInWei[_projectId];
              invocations = projects[_projectId].invocations;
              maxInvocations = projects[_projectId].maxInvocations;
              active = projects[_projectId].active;
              additionalPayee = projectIdToAdditionalPayee[_projectId];
              additionalPayeePercentage = projectIdToAdditionalPayeePercentage[_projectId];
              currency = projectIdToCurrencySymbol[_projectId];
              currencyAddress = projectIdToCurrencyAddress[_projectId];
          }
      
          function projectScriptInfo(uint256 _projectId) view public returns (string memory scriptJSON, uint256 scriptCount, bool useHashString, string memory ipfsHash, bool locked, bool paused) {
              scriptJSON = projects[_projectId].scriptJSON;
              scriptCount = projects[_projectId].scriptCount;
              useHashString = projects[_projectId].useHashString;
              ipfsHash = projects[_projectId].ipfsHash;
              locked = projects[_projectId].locked;
              paused = projects[_projectId].paused;
          }
      
          function projectScriptByIndex(uint256 _projectId, uint256 _index) view public returns (string memory){
              return projects[_projectId].scripts[_index];
          }
      
          function projectURIInfo(uint256 _projectId) view public returns (string memory projectBaseURI, string memory projectBaseIpfsURI, bool useIpfs) {
              projectBaseURI = projects[_projectId].projectBaseURI;
              projectBaseIpfsURI = projects[_projectId].projectBaseIpfsURI;
              useIpfs = projects[_projectId].useIpfs;
          }
      
          function projectShowAllTokens(uint _projectId) public view returns (uint256[] memory){
              return projectIdToTokenIds[_projectId];
          }
      
          function tokensOfOwner(address owner) external view returns (uint256[] memory) {
              return _tokensOfOwner(owner);
          }
      
          function getRoyaltyData(uint256 _tokenId) public view returns (address artistAddress, address additionalPayee, uint256 additionalPayeePercentage, uint256 royaltyFeeByID) {
              artistAddress = projectIdToArtistAddress[tokenIdToProjectId[_tokenId]];
              additionalPayee = projectIdToAdditionalPayee[tokenIdToProjectId[_tokenId]];
              additionalPayeePercentage = projectIdToAdditionalPayeePercentage[tokenIdToProjectId[_tokenId]];
              royaltyFeeByID = projectIdToSecondaryMarketRoyaltyPercentage[tokenIdToProjectId[_tokenId]];
          }
      
          function tokenURI(uint256 _tokenId) external view onlyValidTokenId(_tokenId) returns (string memory) {
              if (bytes(staticIpfsImageLink[_tokenId]).length > 0) {
                  return Strings.strConcat(projects[tokenIdToProjectId[_tokenId]].projectBaseIpfsURI, staticIpfsImageLink[_tokenId]);
              }
      
              if (!projects[tokenIdToProjectId[_tokenId]].dynamic && projects[tokenIdToProjectId[_tokenId]].useIpfs) {
                  return Strings.strConcat(projects[tokenIdToProjectId[_tokenId]].projectBaseIpfsURI, projects[tokenIdToProjectId[_tokenId]].ipfsHash);
              }
      
              return Strings.strConcat(projects[tokenIdToProjectId[_tokenId]].projectBaseURI, Strings.uint2str(_tokenId));
          }
      }