forked from taikoxyz/taiko-mono
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(nfts): add snaefell-nft smart contracts (taikoxyz#17221)
Co-authored-by: d1onys1us <[email protected]>
- Loading branch information
Showing
21 changed files
with
59,433 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]); | ||
} | ||
} | ||
} |
Oops, something went wrong.