Last active
January 1, 2023 23:12
-
-
Save shobhitic/06320d91b59f447414244108d3f0f501 to your computer and use it in GitHub Desktop.
Mint random NFTs from a smart contract - https://www.youtube.com/watch?v=WV2cTebHQi8
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
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.0; | |
import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; | |
import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; | |
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol"; | |
import "@openzeppelin/contracts/utils/Address.sol"; | |
import "@openzeppelin/contracts/utils/Context.sol"; | |
import "@openzeppelin/contracts/utils/Strings.sol"; | |
import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; | |
/** | |
* @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including | |
* the Metadata extension, but not including the Enumerable extension. This does random batch minting. | |
*/ | |
contract ERC721r is Context, ERC165, IERC721, IERC721Metadata { | |
using Address for address; | |
using Strings for uint256; | |
// Token name | |
string private _name; | |
// Token symbol | |
string private _symbol; | |
mapping(uint => uint) private _availableTokens; | |
uint256 private _numAvailableTokens; | |
uint256 immutable _maxSupply; | |
// Mapping from token ID to owner address | |
mapping(uint256 => address) private _owners; | |
// Mapping owner address to token count | |
mapping(address => uint256) private _balances; | |
// Mapping from token ID to approved address | |
mapping(uint256 => address) private _tokenApprovals; | |
// Mapping from owner to operator approvals | |
mapping(address => mapping(address => bool)) private _operatorApprovals; | |
/** | |
* @dev Initializes the contract by setting a `name` and a `symbol` to the token collection. | |
*/ | |
constructor(string memory name_, string memory symbol_, uint maxSupply_) { | |
_name = name_; | |
_symbol = symbol_; | |
_maxSupply = maxSupply_; | |
_numAvailableTokens = maxSupply_; | |
} | |
/** | |
* @dev See {IERC165-supportsInterface}. | |
*/ | |
function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) { | |
return | |
interfaceId == type(IERC721).interfaceId || | |
interfaceId == type(IERC721Metadata).interfaceId || | |
super.supportsInterface(interfaceId); | |
} | |
function totalSupply() public view virtual returns (uint256) { | |
return _maxSupply - _numAvailableTokens; | |
} | |
function maxSupply() public view virtual returns (uint256) { | |
return _maxSupply; | |
} | |
/** | |
* @dev See {IERC721-balanceOf}. | |
*/ | |
function balanceOf(address owner) public view virtual override returns (uint256) { | |
require(owner != address(0), "ERC721: balance query for the zero address"); | |
return _balances[owner]; | |
} | |
/** | |
* @dev See {IERC721-ownerOf}. | |
*/ | |
function ownerOf(uint256 tokenId) public view virtual override returns (address) { | |
address owner = _owners[tokenId]; | |
require(owner != address(0), "ERC721: owner query for nonexistent token"); | |
return owner; | |
} | |
/** | |
* @dev See {IERC721Metadata-name}. | |
*/ | |
function name() public view virtual override returns (string memory) { | |
return _name; | |
} | |
/** | |
* @dev See {IERC721Metadata-symbol}. | |
*/ | |
function symbol() public view virtual override returns (string memory) { | |
return _symbol; | |
} | |
/** | |
* @dev See {IERC721Metadata-tokenURI}. | |
*/ | |
function tokenURI(uint256 tokenId) public view virtual override returns (string memory) { | |
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token"); | |
string memory baseURI = _baseURI(); | |
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ""; | |
} | |
/** | |
* @dev Base URI for computing {tokenURI}. If set, the resulting URI for each | |
* token will be the concatenation of the `baseURI` and the `tokenId`. Empty | |
* by default, can be overridden in child contracts. | |
*/ | |
function _baseURI() internal view virtual returns (string memory) { | |
return ""; | |
} | |
/** | |
* @dev See {IERC721-approve}. | |
*/ | |
function approve(address to, uint256 tokenId) public virtual override { | |
address owner = ERC721r.ownerOf(tokenId); | |
require(to != owner, "ERC721: approval to current owner"); | |
require( | |
_msgSender() == owner || isApprovedForAll(owner, _msgSender()), | |
"ERC721: approve caller is not owner nor approved for all" | |
); | |
_approve(to, tokenId); | |
} | |
/** | |
* @dev See {IERC721-getApproved}. | |
*/ | |
function getApproved(uint256 tokenId) public view virtual override returns (address) { | |
require(_exists(tokenId), "ERC721: approved query for nonexistent token"); | |
return _tokenApprovals[tokenId]; | |
} | |
/** | |
* @dev See {IERC721-setApprovalForAll}. | |
*/ | |
function setApprovalForAll(address operator, bool approved) public virtual override { | |
_setApprovalForAll(_msgSender(), operator, approved); | |
} | |
/** | |
* @dev See {IERC721-isApprovedForAll}. | |
*/ | |
function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) { | |
return _operatorApprovals[owner][operator]; | |
} | |
/** | |
* @dev See {IERC721-transferFrom}. | |
*/ | |
function transferFrom( | |
address from, | |
address to, | |
uint256 tokenId | |
) public virtual override { | |
//solhint-disable-next-line max-line-length | |
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); | |
_transfer(from, to, tokenId); | |
} | |
/** | |
* @dev See {IERC721-safeTransferFrom}. | |
*/ | |
function safeTransferFrom( | |
address from, | |
address to, | |
uint256 tokenId | |
) public virtual override { | |
safeTransferFrom(from, to, tokenId, ""); | |
} | |
/** | |
* @dev See {IERC721-safeTransferFrom}. | |
*/ | |
function safeTransferFrom( | |
address from, | |
address to, | |
uint256 tokenId, | |
bytes memory _data | |
) public virtual override { | |
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved"); | |
_safeTransfer(from, to, tokenId, _data); | |
} | |
/** | |
* @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients | |
* are aware of the ERC721 protocol to prevent tokens from being forever locked. | |
* | |
* `_data` is additional data, it has no specified format and it is sent in call to `to`. | |
* | |
* This internal function is equivalent to {safeTransferFrom}, and can be used to e.g. | |
* implement alternative mechanisms to perform token transfer, such as signature-based. | |
* | |
* Requirements: | |
* | |
* - `from` cannot be the zero address. | |
* - `to` cannot be the zero address. | |
* - `tokenId` token must exist and be owned by `from`. | |
* - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer. | |
* | |
* Emits a {Transfer} event. | |
*/ | |
function _safeTransfer( | |
address from, | |
address to, | |
uint256 tokenId, | |
bytes memory _data | |
) internal virtual { | |
_transfer(from, to, tokenId); | |
require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer"); | |
} | |
/** | |
* @dev Returns whether `tokenId` exists. | |
* | |
* Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}. | |
* | |
* Tokens start existing when they are minted (`_mint`), | |
* and stop existing when they are burned (`_burn`). | |
*/ | |
function _exists(uint256 tokenId) internal view virtual returns (bool) { | |
return _owners[tokenId] != address(0); | |
} | |
/** | |
* @dev Returns whether `spender` is allowed to manage `tokenId`. | |
* | |
* Requirements: | |
* | |
* - `tokenId` must exist. | |
*/ | |
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { | |
require(_exists(tokenId), "ERC721: operator query for nonexistent token"); | |
address owner = ERC721r.ownerOf(tokenId); | |
return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); | |
} | |
function _mintIdWithoutBalanceUpdate(address to, uint256 tokenId) private { | |
_beforeTokenTransfer(address(0), to, tokenId); | |
_owners[tokenId] = to; | |
emit Transfer(address(0), to, tokenId); | |
_afterTokenTransfer(address(0), to, tokenId); | |
} | |
function _mintRandom(address to, uint _numToMint) internal virtual { | |
require(_msgSender() == tx.origin, "Contracts cannot mint"); | |
require(to != address(0), "ERC721: mint to the zero address"); | |
require(_numToMint > 0, "ERC721r: need to mint at least one token"); | |
// TODO: Probably don't need this as it will underflow and revert automatically in this case | |
require(_numAvailableTokens >= _numToMint, "ERC721r: minting more tokens than available"); | |
uint updatedNumAvailableTokens = _numAvailableTokens; | |
for (uint256 i; i < _numToMint; ++i) { // Do this ++ unchecked? | |
uint256 tokenId = getRandomAvailableTokenId(to, updatedNumAvailableTokens); | |
_mintIdWithoutBalanceUpdate(to, tokenId); | |
--updatedNumAvailableTokens; | |
} | |
_numAvailableTokens = updatedNumAvailableTokens; | |
_balances[to] += _numToMint; | |
} | |
function getRandomAvailableTokenId(address to, uint updatedNumAvailableTokens) | |
internal | |
returns (uint256) | |
{ | |
uint256 randomNum = uint256( | |
keccak256( | |
abi.encode( | |
to, | |
tx.gasprice, | |
block.number, | |
block.timestamp, | |
block.difficulty, | |
blockhash(block.number - 1), | |
address(this), | |
updatedNumAvailableTokens | |
) | |
) | |
); | |
uint256 randomIndex = randomNum % updatedNumAvailableTokens; | |
return getAvailableTokenAtIndex(randomIndex, updatedNumAvailableTokens); | |
} | |
// Implements https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle. Code taken from CryptoPhunksV2 | |
function getAvailableTokenAtIndex(uint256 indexToUse, uint updatedNumAvailableTokens) | |
internal | |
returns (uint256) | |
{ | |
uint256 valAtIndex = _availableTokens[indexToUse]; | |
uint256 result; | |
if (valAtIndex == 0) { | |
// This means the index itself is still an available token | |
result = indexToUse; | |
} else { | |
// This means the index itself is not an available token, but the val at that index is. | |
result = valAtIndex; | |
} | |
uint256 lastIndex = updatedNumAvailableTokens - 1; | |
if (indexToUse != lastIndex) { | |
// Replace the value at indexToUse, now that it's been used. | |
// Replace it with the data from the last index in the array, since we are going to decrease the array size afterwards. | |
uint256 lastValInArray = _availableTokens[lastIndex]; | |
if (lastValInArray == 0) { | |
// This means the index itself is still an available token | |
_availableTokens[indexToUse] = lastIndex; | |
} else { | |
// This means the index itself is not an available token, but the val at that index is. | |
_availableTokens[indexToUse] = lastValInArray; | |
// Gas refund courtsey of @dievardump | |
delete _availableTokens[lastIndex]; | |
} | |
} | |
return result; | |
} | |
// Not as good as minting a specific tokenId, but will behave the same at the start | |
// allowing you to explicitly mint some tokens at launch. | |
function _mintAtIndex(address to, uint index) internal virtual { | |
require(_msgSender() == tx.origin, "Contracts cannot mint"); | |
require(to != address(0), "ERC721: mint to the zero address"); | |
require(_numAvailableTokens >= 1, "ERC721r: minting more tokens than available"); | |
uint tokenId = getAvailableTokenAtIndex(index, _numAvailableTokens); | |
--_numAvailableTokens; | |
_mintIdWithoutBalanceUpdate(to, tokenId); | |
_balances[to] += 1; | |
} | |
/** | |
* @dev Transfers `tokenId` from `from` to `to`. | |
* As opposed to {transferFrom}, this imposes no restrictions on msg.sender. | |
* | |
* Requirements: | |
* | |
* - `to` cannot be the zero address. | |
* - `tokenId` token must be owned by `from`. | |
* | |
* Emits a {Transfer} event. | |
*/ | |
function _transfer( | |
address from, | |
address to, | |
uint256 tokenId | |
) internal virtual { | |
require(ERC721r.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner"); | |
require(to != address(0), "ERC721: transfer to the zero address"); | |
_beforeTokenTransfer(from, to, tokenId); | |
// Clear approvals from the previous owner | |
_approve(address(0), tokenId); | |
_balances[from] -= 1; | |
_balances[to] += 1; | |
_owners[tokenId] = to; | |
emit Transfer(from, to, tokenId); | |
_afterTokenTransfer(from, to, tokenId); | |
} | |
/** | |
* @dev Approve `to` to operate on `tokenId` | |
* | |
* Emits a {Approval} event. | |
*/ | |
function _approve(address to, uint256 tokenId) internal virtual { | |
_tokenApprovals[tokenId] = to; | |
emit Approval(ERC721r.ownerOf(tokenId), to, tokenId); | |
} | |
/** | |
* @dev Approve `operator` to operate on all of `owner` tokens | |
* | |
* Emits a {ApprovalForAll} event. | |
*/ | |
function _setApprovalForAll( | |
address owner, | |
address operator, | |
bool approved | |
) internal virtual { | |
require(owner != operator, "ERC721: approve to caller"); | |
_operatorApprovals[owner][operator] = approved; | |
emit ApprovalForAll(owner, operator, approved); | |
} | |
/** | |
* @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address. | |
* The call is not executed if the target address is not a contract. | |
* | |
* @param from address representing the previous owner of the given token ID | |
* @param to target address that will receive the tokens | |
* @param tokenId uint256 ID of the token to be transferred | |
* @param _data bytes optional data to send along with the call | |
* @return bool whether the call correctly returned the expected magic value | |
*/ | |
function _checkOnERC721Received( | |
address from, | |
address to, | |
uint256 tokenId, | |
bytes memory _data | |
) private returns (bool) { | |
if (to.isContract()) { | |
try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data) returns (bytes4 retval) { | |
return retval == IERC721Receiver.onERC721Received.selector; | |
} catch (bytes memory reason) { | |
if (reason.length == 0) { | |
revert("ERC721: transfer to non ERC721Receiver implementer"); | |
} else { | |
assembly { | |
revert(add(32, reason), mload(reason)) | |
} | |
} | |
} | |
} else { | |
return true; | |
} | |
} | |
/** | |
* @dev Hook that is called before any token transfer. This includes minting | |
* and burning. | |
* | |
* Calling conditions: | |
* | |
* - When `from` and `to` are both non-zero, ``from``'s `tokenId` will be | |
* transferred to `to`. | |
* - When `from` is zero, `tokenId` will be minted for `to`. | |
* - When `to` is zero, ``from``'s `tokenId` will be burned. | |
* - `from` and `to` are never both zero. | |
* | |
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. | |
*/ | |
function _beforeTokenTransfer( | |
address from, | |
address to, | |
uint256 tokenId | |
) internal virtual {} | |
/** | |
* @dev Hook that is called after any transfer of tokens. This includes | |
* minting and burning. | |
* | |
* Calling conditions: | |
* | |
* - when `from` and `to` are both non-zero. | |
* - `from` and `to` are never both zero. | |
* | |
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks]. | |
*/ | |
function _afterTokenTransfer( | |
address from, | |
address to, | |
uint256 tokenId | |
) internal virtual {} | |
} |
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
// SPDX-License-Identifier: MIT | |
pragma solidity ^0.8.0; | |
import "./ERC721r.sol"; | |
contract MyToken is ERC721r { | |
constructor () ERC721r("MyToken", "MTK", 500) {} | |
function mint(uint256 quantity) external { | |
_mintRandom(msg.sender, quantity); | |
} | |
function reserve() external { | |
for (uint i = 5; i > 0; i--) { | |
_mintAtIndex(msg.sender, i - 1); | |
} | |
} | |
function _baseURI() override internal view virtual returns (string memory) { | |
return "ipfs://YOUR_CID/"; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment