ERC721
ERC721 是以太坊上的一個(gè)標(biāo)準(zhǔn)協(xié)議,用于創(chuàng)建不可分割、不可互換的獨(dú)特資產(chǎn),如數(shù)字藝術(shù)品、游戲物品、收藏品等。每個(gè) ERC721 資產(chǎn)都有一個(gè)唯一的標(biāo)識(shí)符,被稱為 token ID。這個(gè)標(biāo)準(zhǔn)協(xié)議定義了一組功能和事件,使得 ERC721 資產(chǎn)可以被安全地轉(zhuǎn)移、擁有、交易和檢索。與傳統(tǒng)的貨幣不同,每個(gè) ERC721 資產(chǎn)都是獨(dú)特的,并且不可替代,因此它們可以用于代表任何種類的獨(dú)特資產(chǎn),并且具有非常廣泛的應(yīng)用。
Mint
可以在以下測(cè)試網(wǎng)絡(luò)自己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 |
開(kāi)發(fā)
在 Openzeppelin 的幫助下可以很快的入手開(kāi)發(fā)ERC721標(biāo)準(zhǔn)的NFT合約。參考之前測(cè)試用的NFT代碼,自行編譯并且發(fā)布到了更多的測(cè)試網(wǎng)絡(luò)上,可以很方便的進(jìn)行ERC721 NFT測(cè)試。
引入ERC721庫(kù)之后,自己只需要實(shí)現(xiàn)setBaseURI mint等自定義方法即可,非常簡(jiǎn)單。
合約開(kāi)發(fā)使用的是VSCode和hardhat,合約驗(yàn)證使用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;
}
}