ERC721
ERC721
是以太坊上的一个标准协议,用于创建不可分割、不可互换的独特资产,如数字艺术品、游戏物品、收藏品等。每个 ERC721
资产都有一个唯一的标识符,被称为 token ID
。这个标准协议定义了一组功能和事件,使得 ERC721
资产可以被安全地转移、拥有、交易和检索。与传统的货币不同,每个 ERC721
资产都是独特的,并且不可替代,因此它们可以用于代表任何种类的独特资产,并且具有非常广泛的应用。
Mint
可以在以下测试网络自己mint,具体步骤可以参考 Github
Network | Contract Address |
---|---|
Ethereum Speolia | 0x8b6c7d03bd8911236feaa43afacbb8b0e563d93a |
BNB Chain Testnet | 0xfD9585145A5BC2D7A74c82E78aB9314723683BA8 |
Polygon Mumbai | 0x9B1AAb1492c375F011811cBdBd88FFEf3ce2De76 |
Avalanche Testnet | 0x825706F885445867958A3f3a4dec8189e0E02524 |
Arbitrum Goerli | 0x1E2c6DD54b473B63112416850E73B2F567DDE3Df |
Optimism Goerili | 0x825706f885445867958a3f3a4dec8189e0e02524 |
Base Testnet | 0x155afc7fbaedda178c6185ec9151964b9719828b |
开发
在 Openzeppelin 的帮助下可以很快的入手开发ERC721
标准的NFT合约。参考之前测试用的NFT代码,自行编译并且发布到了更多的测试网络上,可以很方便的进行ERC721
NFT测试。
引入ERC721
库之后,自己只需要实现setBaseURI
mint
等自定义方法即可,非常简单。
合约开发使用的是VSCode和hardhat,合约验证使用hardhat flattening。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract TestToadz is ERC721, Ownable {
uint256 public constant maxTokens = 6969;
uint256 public numAvailableTokens = 6969;
uint256 public constant maxMintsPerTx = 5;
mapping(address => uint256) public addressToNumOwned;
string private _contractURI;
bool public devMintLocked = false;
uint256[10000] private _availableTokens;
constructor() ERC721("TestToadz", "TESTTOADZ") {}
// metadata URI
string private _baseTokenURI =
"ipfs://QmWEFSMku6yGLQ9TQr66HjSd9kay8ZDYKbBEfjNi4pLtrr/";
function _baseURI() internal view virtual override returns (string memory) {
return _baseTokenURI;
}
function tokenURI(uint256 _serialId)
public
view
override
returns (string memory)
{
string memory base = _baseURI();
string memory _tokenURI = Strings.toString(_serialId);
// If there is no base URI, return the token URI.
if (bytes(base).length == 0) {
return _tokenURI;
}
return string(abi.encodePacked(base, _tokenURI));
}
function getNumAvailableTokens() public view returns (uint256) {
return numAvailableTokens;
}
function setBaseURI(string calldata baseURI) external onlyOwner {
_baseTokenURI = baseURI;
}
//Minting
function mint(uint256 quantity) public {
uint256 updatedNumAvailableTokens = numAvailableTokens;
require(
block.timestamp >= 1337133769,
"Sale starts at whatever this time is"
);
require(
quantity <= maxMintsPerTx,
"There is a limit on minting too many at a time!"
);
require(
updatedNumAvailableTokens - quantity >= 0,
"Minting this many would exceed supply!"
);
require(
addressToNumOwned[msg.sender] + quantity <= 40,
"Can't own more than 20 toadz"
);
require(msg.sender == tx.origin, "No contracts!");
for (uint256 i = 0; i < quantity; i++) {
uint256 tokenId = getRandomSerialToken(quantity, i);
_safeMint(msg.sender, tokenId);
updatedNumAvailableTokens--;
}
numAvailableTokens = updatedNumAvailableTokens;
addressToNumOwned[msg.sender] =
addressToNumOwned[msg.sender] +
quantity;
}
//Dev mint special tokens
function mintSpecial(uint256[] memory specialIds) external onlyOwner {
require(!devMintLocked, "Dev Mint Permanently Locked");
uint256 num = specialIds.length;
for (uint256 i = 0; i < num; i++) {
uint256 specialId = specialIds[i];
_safeMint(msg.sender, specialId);
}
}
function getRandomSerialToken(uint256 _numToFetch, uint256 _i)
internal
returns (uint256)
{
uint256 randomNum = uint256(
keccak256(
abi.encode(
msg.sender,
tx.gasprice,
block.number,
block.timestamp,
blockhash(block.number - 1),
_numToFetch,
_i
)
)
);
uint256 randomIndex = randomNum % numAvailableTokens;
uint256 valAtIndex = _availableTokens[randomIndex];
uint256 result;
if (valAtIndex == 0) {
result = randomIndex;
} else {
result = valAtIndex;
}
uint256 lastIndex = numAvailableTokens - 1;
if (randomIndex != lastIndex) {
uint256 lastValInArray = _availableTokens[lastIndex];
if (lastValInArray == 0) {
_availableTokens[randomIndex] = lastIndex;
} else {
_availableTokens[randomIndex] = lastValInArray;
}
}
numAvailableTokens--;
return result;
}
function lockDevMint() public onlyOwner {
devMintLocked = true;
}
}