Skip to content

Commit

Permalink
feat(nfts): add snaefell-nft smart contracts (taikoxyz#17221)
Browse files Browse the repository at this point in the history
Co-authored-by: d1onys1us <[email protected]>
  • Loading branch information
bearni95 and dionysuzx authored May 21, 2024
1 parent c170497 commit ef3d7d5
Show file tree
Hide file tree
Showing 21 changed files with 59,433 additions and 13 deletions.
7 changes: 7 additions & 0 deletions packages/nfts/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,10 @@ data/taikoon/whitelist/hardhat.csv
data/taikoon/whitelist/holesky.csv
data/taikoon/whitelist/mainnet.csv
data/taikoon/whitelist/*.json
data/snaefell/original
data/snaefell/images
data/snaefell/metadata
data/snaefell/whitelist/hardhat.csv
data/snaefell/whitelist/holesky.csv
data/snaefell/whitelist/mainnet.csv
data/snaefell/whitelist/*.json
86 changes: 86 additions & 0 deletions packages/nfts/contracts/snaefell/MerkleWhitelist.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { UUPSUpgradeable } from
"@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import { Ownable2StepUpgradeable } from
"@openzeppelin/contracts-upgradeable/access/Ownable2StepUpgradeable.sol";
import { MerkleProof } from "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import { ContextUpgradeable } from
"@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol";

/// @title MerkleWhitelist
/// @dev Merkle Tree Whitelist
/// @custom:security-contact [email protected]
contract MerkleWhitelist is ContextUpgradeable, UUPSUpgradeable, Ownable2StepUpgradeable {
event RootUpdated(bytes32 _root);
event MintConsumed(address _minter, uint256 _mintAmount);

error MINTS_EXCEEDED();
error INVALID_PROOF();
error INVALID_TOKEN_AMOUNT();

/// @notice Merkle Tree Root
bytes32 public root;
/// @notice Tracker for minted leaves
mapping(bytes32 leaf => bool hasMinted) public minted;

uint256[48] private __gap;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}

/// @notice Contract initializer
/// @param _root Merkle Tree root
function initialize(address _owner, bytes32 _root) external initializer {
__MerkleWhitelist_init(_owner, _root);
}

/// @notice Check if a wallet can free mint
/// @param _minter Address of the minter
/// @param _maxMints Max amount of free mints
/// @return Whether the wallet can mint
function canMint(address _minter, uint256 _maxMints) public view returns (bool) {
bytes32 _leaf = leaf(_minter, _maxMints);
return !minted[_leaf];
}

/// @notice Generate a leaf from the minter and mint counts
/// @param _minter Address of the minter
/// @param _maxMints Max amount of free mints
/// @return The leaf hash
function leaf(address _minter, uint256 _maxMints) public pure returns (bytes32) {
return keccak256(bytes.concat(keccak256(abi.encode(_minter, _maxMints))));
}

/// @notice Internal initializer
/// @param _root Merkle Tree root
function __MerkleWhitelist_init(address _owner, bytes32 _root) internal initializer {
_transferOwnership(_owner == address(0) ? msg.sender : _owner);
__Context_init();
root = _root;
}

/// @notice Update the merkle tree's root
/// @param _root The new root
function _updateRoot(bytes32 _root) internal {
root = _root;
emit RootUpdated(_root);
}

/// @notice Permanently consume mints from the minter
/// @param _proof Merkle proof
/// @param _maxMints Max amount of free mints
function _consumeMint(bytes32[] calldata _proof, uint256 _maxMints) internal {
if (!canMint(_msgSender(), _maxMints)) revert MINTS_EXCEEDED();
bytes32 _leaf = leaf(_msgSender(), _maxMints);
if (!MerkleProof.verify(_proof, root, _leaf)) revert INVALID_PROOF();
minted[_leaf] = true;
emit MintConsumed(_msgSender(), _maxMints);
}

/// @notice Internal method to authorize an upgrade
function _authorizeUpgrade(address) internal virtual override onlyOwner { }
}
103 changes: 103 additions & 0 deletions packages/nfts/contracts/snaefell/SnaefellToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import { ERC721EnumerableUpgradeable } from
"@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721EnumerableUpgradeable.sol";

import { MerkleWhitelist } from "./MerkleWhitelist.sol";

/// @title TaikoonToken
/// @dev The SnaefellToken ERC-721 token
/// @custom:security-contact [email protected]
contract SnaefellToken is ERC721EnumerableUpgradeable, MerkleWhitelist {
/// @notice The current supply
uint256 private _totalSupply;
// Base URI required to interact with IPFS
string private _baseURIExtended;

uint256[48] private __gap;

error MAX_MINTS_EXCEEDED();
error MAX_SUPPLY_REACHED();
error MINTER_NOT_WHITELISTED();
error TOKEN_NOT_MINTED();

/// @notice Contract initializer
/// @param _rootURI Base URI for the token metadata
/// @param _merkleRoot Merkle tree root for the whitelist
function initialize(
address _owner,
string memory _rootURI,
bytes32 _merkleRoot
)
external
initializer
{
__ERC721_init("SnaefellToken", "SNF");
__MerkleWhitelist_init(_owner, _merkleRoot);
_baseURIExtended = _rootURI;
}

/// @notice Update the whitelist's merkle root
/// @param _root New merkle root
function updateRoot(bytes32 _root) external onlyOwner {
_updateRoot(_root);
}

/// @notice Mint a token, handling the free vs paid internally
/// @param _proof Merkle proof validating the minter
/// @param _maxMints The amount of tokens to mint
/// @return tokenIds The minted token ids
function mint(
bytes32[] calldata _proof,
uint256 _maxMints
)
external
returns (uint256[] memory)
{
if (!canMint(_msgSender(), _maxMints)) revert MINTER_NOT_WHITELISTED();

_consumeMint(_proof, _maxMints);
return _batchMint(_msgSender(), _maxMints);
}

/// @notice Mint method for the owner
/// @param _to The address to mint to
/// @param _amount The amount of tokens to mint
/// @return tokenIds The minted token ids
function mint(address _to, uint256 _amount) external onlyOwner returns (uint256[] memory) {
return _batchMint(_to, _amount);
}

/// @notice Get the tokenURI of a particular tokenId
/// @return The token URI
function tokenURI(uint256) public view override returns (string memory) {
return _baseURI();
}

/// @notice Get the current total supply
/// @return The total supply
function totalSupply() public view override returns (uint256) {
return _totalSupply;
}

/// @notice Calculate the amount of free and paid mints
/// @return The base URI for the token metadata

function _baseURI() internal view override returns (string memory) {
return _baseURIExtended;
}

/// @notice Internal method to batch mint tokens
/// @param _to The address to mint to
/// @param _amount The amount of tokens to mint
/// @return tokenIds The minted token ids
function _batchMint(address _to, uint256 _amount) private returns (uint256[] memory tokenIds) {
tokenIds = new uint256[](_amount);

for (uint256 i; i < _amount; ++i) {
tokenIds[i] = ++_totalSupply;
_mint(_to, tokenIds[i]);
}
}
}
Loading

0 comments on commit ef3d7d5

Please sign in to comment.