打開CHAINPIP社區(qū),進(jìn)入應(yīng)用,創(chuàng)建一個(gè)新應(yīng)用;

將模板代碼刪除,上傳本次測(cè)試的合約代碼。

這是一個(gè)符合ERC721標(biāo)準(zhǔn)的NFT合約代碼,其中包括了幾個(gè)常用的標(biāo)準(zhǔn)接口和合約,下面是個(gè)合約的具體信息:
IERC65.sol
pragma?solidity?^0.8.0;
/**
?* @dev ERC165標(biāo)準(zhǔn)接口, 詳見
?* https://eips.ethereum.org/EIPS/eip-165[EIP].
?*
?* 合約可以聲明支持的接口,供其他合約檢查
?*
?*/
interface IERC165 {
? ? /**
? ? ?* @dev 如果合約實(shí)現(xiàn)了查詢的`interfaceId`,則返回true
? ? ?* 規(guī)則詳見:https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
? ? ?*
? ? ?*/
? ? function?supportsInterface(bytes4?interfaceId) external?view returns?(bool);
}
IERC721.sol
// SPDX-License-Identifier: MIT
pragma?solidity?^0.8.0;
import?"./IERC165.sol";
/**
?* @dev ERC721標(biāo)準(zhǔn)接口.
?*/
interface IERC721 is?IERC165 {
? ? event?Transfer(address?indexed from, address?indexed to, uint256?indexed tokenId);
? ? event?Approval(address?indexed owner, address?indexed approved, uint256?indexed tokenId);
? ? event?ApprovalForAll(address?indexed owner, address?indexed operator, bool?approved);
? ? function?balanceOf(address?owner) external?view returns?(uint256?balance);
? ? function?ownerOf(uint256?tokenId) external?view returns?(address?owner);
? ? function?safeTransferFrom(
? ? ? ? address?from,
? ? ? ? address?to,
? ? ? ? uint256?tokenId,
? ? ? ? bytes?calldata data
? ? ) external;
? ? function?safeTransferFrom(
? ? ? ? address?from,
? ? ? ? address?to,
? ? ? ? uint256?tokenId
? ? ) external;
? ? function?transferFrom(
? ? ? ? address?from,
? ? ? ? address?to,
? ? ? ? uint256?tokenId
? ? ) external;
? ? function?approve(address?to, uint256?tokenId) external;
? ? function?setApprovalForAll(address?operator, bool?_approved) external;
? ? function?getApproved(uint256?tokenId) external?view returns?(address?operator);
? ? function?isApprovedForAll(address?owner, address?operator) external?view returns?(bool);
}
IERC721Receiver.sol
// SPDX-License-Identifier: MIT
pragma?solidity?^0.8.0;
// ERC721接收者接口:合約必須實(shí)現(xiàn)這個(gè)接口來通過安全轉(zhuǎn)賬接收ERC721
interface IERC721Receiver {
? ? function?onERC721Received(
? ? ? ? address?operator,
? ? ? ? address?from,
? ? ? ? uint?tokenId,
? ? ? ? bytes?calldata data
? ? ) external?returns?(bytes4);
}
IERC721Metadata.sol
// SPDX-License-Identifier: MIT
pragma?solidity?^0.8.0;
interface IERC721Metadata {
? ? function?name() external?view returns?(string?memory);
? ? function?symbol() external?view returns?(string?memory);
? ? function?tokenURI(uint256?tokenId) external?view returns?(string?memory);
}
Strings.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)
pragma?solidity?^0.8.4;
/**
?* @dev String operations.
?*/
library?Strings {
? ? bytes16?private?constant?_HEX_SYMBOLS = "0123456789abcdef";
? ? uint8?private?constant?_ADDRESS_LENGTH = 20;
? ? /**
? ? ?* @dev Converts a `uint256` to its ASCII `string` decimal representation.
? ? ?*/
? ? function?toString(uint256?value) internal pure returns?(string?memory) {
? ? ? ? // Inspired by OraclizeAPI's implementation - MIT licence
? ? ? ? // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol
? ? ? ? if?(value == 0) {
? ? ? ? ? ? return?"0";
? ? ? ? }
? ? ? ? uint256?temp = value;
? ? ? ? uint256?digits;
? ? ? ? while?(temp != 0) {
? ? ? ? ? ? digits++;
? ? ? ? ? ? temp /= 10;
? ? ? ? }
? ? ? ? bytes?memory buffer = new?bytes(digits);
? ? ? ? while?(value != 0) {
? ? ? ? ? ? digits -= 1;
? ? ? ? ? ? buffer[digits] = bytes1(uint8(48?+ uint256(value % 10)));
? ? ? ? ? ? value /= 10;
? ? ? ? }
? ? ? ? return?string(buffer);
? ? }
? ? /**
? ? ?* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
? ? ?*/
? ? function?toHexString(uint256?value) internal pure returns?(string?memory) {
? ? ? ? if?(value == 0) {
? ? ? ? ? ? return?"0x00";
? ? ? ? }
? ? ? ? uint256?temp = value;
? ? ? ? uint256?length = 0;
? ? ? ? while?(temp != 0) {
? ? ? ? ? ? length++;
? ? ? ? ? ? temp >>= 8;
? ? ? ? }
? ? ? ? return?toHexString(value, length);
? ? }
? ? /**
? ? ?* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
? ? ?*/
? ? function?toHexString(uint256?value, uint256?length) internal pure returns?(string?memory) {
? ? ? ? bytes?memory buffer = new?bytes(2?* length + 2);
? ? ? ? buffer[0] = "0";
? ? ? ? buffer[1] = "x";
? ? ? ? for?(uint256?i = 2?* length + 1; i > 1; --i) {
? ? ? ? ? ? buffer[i] = _HEX_SYMBOLS[value & 0xf];
? ? ? ? ? ? value >>= 4;
? ? ? ? }
? ? ? ? require(value == 0, "Strings: hex length insufficient");
? ? ? ? return?string(buffer);
? ? }
? ? /**
? ? ?* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
? ? ?*/
? ? function?toHexString(address?addr) internal pure returns?(string?memory) {
? ? ? ? return?toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
? ? }
}
Address.sol
// SPDX-License-Identifier: MIT
pragma?solidity?^0.8.1;
// Address庫(kù)
library?Address {
? ? // 利用extcodesize判斷一個(gè)地址是否為合約地址
? ? function?isContract(address?account) internal view returns?(bool) {
? ? ? ? uint?size;
? ? ? ? assembly {
? ? ? ? ? ? size := extcodesize(account)
? ? ? ? }
? ? ? ? return?size > 0;
? ? }
}
ERC721.sol
// SPDX-License-Identifier: MIT
// by 0xAA
pragma?solidity?^0.8.4;
import?"./IERC165.sol";
import?"./IERC721.sol";
import?"./IERC721Receiver.sol";
import?"./IERC721Metadata.sol";
import?"./Address.sol";
import?"./String.sol";
contract?ERC721 is?IERC721, IERC721Metadata{
? ? using?Address for?address; // 使用Address庫(kù),用isContract來判斷地址是否為合約
? ? using?Strings for?uint256; // 使用String庫(kù),
? ? // Token名稱
? ? string?public?override name;
? ? // Token代號(hào)
? ? string?public?override symbol;
? ? // tokenId 到 owner address 的持有人映射
? ? mapping(uint?=> address) private?_owners;
? ? // address 到 持倉(cāng)數(shù)量 的持倉(cāng)量映射
? ? mapping(address?=> uint) private?_balances;
? ? // tokenID 到 授權(quán)地址 的授權(quán)映射
? ? mapping(uint?=> address) private?_tokenApprovals;
? ? // ?owner地址。到operator地址 的批量授權(quán)映射
? ? mapping(address?=> mapping(address?=> bool)) private?_operatorApprovals;
? ? /**
? ? ?* 構(gòu)造函數(shù),初始化`name` 和`symbol` .
? ? ?*/
? ? constructor(string?memory name_, string?memory symbol_) {
? ? ? ? name = name_;
? ? ? ? symbol = symbol_;
? ? }
? ? // 實(shí)現(xiàn)IERC165接口supportsInterface
? ? function?supportsInterface(bytes4?interfaceId)
? ? ? ? external
? ? ? ? pure
? ? ? ? override
? ? ? ? returns?(bool)
? ? {
? ? ? ? return
? ? ? ? ? ? interfaceId == type(IERC721).interfaceId ||
? ? ? ? ? ? interfaceId == type(IERC165).interfaceId ||
? ? ? ? ? ? interfaceId == type(IERC721Metadata).interfaceId;
? ? }
? ? // 實(shí)現(xiàn)IERC721的balanceOf,利用_balances變量查詢owner地址的balance。
? ? function?balanceOf(address?owner) external?view override returns?(uint) {
? ? ? ? require(owner != address(0), "owner = zero address");
? ? ? ? return?_balances[owner];
? ? }
? ? // 實(shí)現(xiàn)IERC721的ownerOf,利用_owners變量查詢tokenId的owner。
? ? function?ownerOf(uint?tokenId) public?view override returns?(address?owner) {
? ? ? ? owner = _owners[tokenId];
? ? ? ? require(owner != address(0), "token doesn't exist");
? ? }
? ? // 實(shí)現(xiàn)IERC721的isApprovedForAll,利用_operatorApprovals變量查詢owner地址是否將所持NFT批量授權(quán)給了operator地址。
? ? function?isApprovedForAll(address?owner, address?operator)
? ? ? ? external
? ? ? ? view
? ? ? ? override
? ? ? ? returns?(bool)
? ? {
? ? ? ? return?_operatorApprovals[owner][operator];
? ? }
? ? // 實(shí)現(xiàn)IERC721的setApprovalForAll,將持有代幣全部授權(quán)給operator地址。調(diào)用_setApprovalForAll函數(shù)。
? ? function?setApprovalForAll(address?operator, bool?approved) external?override {
? ? ? ? _operatorApprovals[msg.sender][operator] = approved;
? ? ? ? emit ApprovalForAll(msg.sender, operator, approved);
? ? }
? ? // 實(shí)現(xiàn)IERC721的getApproved,利用_tokenApprovals變量查詢tokenId的授權(quán)地址。
? ? function?getApproved(uint?tokenId) external?view override returns?(address) {
? ? ? ? require(_owners[tokenId] != address(0), "token doesn't exist");
? ? ? ? return?_tokenApprovals[tokenId];
? ? }
? ? // 授權(quán)函數(shù)。通過調(diào)整_tokenApprovals來,授權(quán) to 地址操作 tokenId,同時(shí)釋放Approval事件。
? ? function?_approve(
? ? ? ? address?owner,
? ? ? ? address?to,
? ? ? ? uint?tokenId
? ? ) private?{
? ? ? ? _tokenApprovals[tokenId] = to;
? ? ? ? emit Approval(owner, to, tokenId);
? ? }
? ? // 實(shí)現(xiàn)IERC721的approve,將tokenId授權(quán)給 to 地址。條件:to不是owner,且msg.sender是owner或授權(quán)地址。調(diào)用_approve函數(shù)。
? ? function?approve(address?to, uint?tokenId) external?override {
? ? ? ? address?owner = _owners[tokenId];
? ? ? ? require(
? ? ? ? ? ? msg.sender == owner || _operatorApprovals[owner][msg.sender],
? ? ? ? ? ? "not owner nor approved for all"
? ? ? ? );
? ? ? ? _approve(owner, to, tokenId);
? ? }
? ? // 查詢 spender地址是否被可以使用tokenId(他是owner或被授權(quán)地址)。
? ? function?_isApprovedOrOwner(
? ? ? ? address?owner,
? ? ? ? address?spender,
? ? ? ? uint?tokenId
? ? ) private?view returns?(bool) {
? ? ? ? return?(spender == owner ||
? ? ? ? ? ? _tokenApprovals[tokenId] == spender ||
? ? ? ? ? ? _operatorApprovals[owner][spender]);
? ? }
? ? /*
? ? ?* 轉(zhuǎn)賬函數(shù)。通過調(diào)整_balances和_owner變量將 tokenId 從 from 轉(zhuǎn)賬給 to,同時(shí)釋放Tranfer事件。
? ? ?* 條件:
? ? ?* 1. tokenId 被 from 擁有
? ? ?* 2. to 不是0地址
? ? ?*/
? ? function?_transfer(
? ? ? ? address?owner,
? ? ? ? address?from,
? ? ? ? address?to,
? ? ? ? uint?tokenId
? ? ) private?{
? ? ? ? require(from == owner, "not owner");
? ? ? ? require(to != address(0), "transfer to the zero address");
? ? ? ? _approve(owner, address(0), tokenId);
? ? ? ? _balances[from] -= 1;
? ? ? ? _balances[to] += 1;
? ? ? ? _owners[tokenId] = to;
? ? ? ? emit Transfer(from, to, tokenId);
? ? }
? ? // 實(shí)現(xiàn)IERC721的transferFrom,非安全轉(zhuǎn)賬,不建議使用。調(diào)用_transfer函數(shù)
? ? function?transferFrom(
? ? ? ? address?from,
? ? ? ? address?to,
? ? ? ? uint?tokenId
? ? ) external?override {
? ? ? ? address?owner = ownerOf(tokenId);
? ? ? ? require(
? ? ? ? ? ? _isApprovedOrOwner(owner, msg.sender, tokenId),
? ? ? ? ? ? "not owner nor approved"
? ? ? ? );
? ? ? ? _transfer(owner, from, to, tokenId);
? ? }
? ? /**
? ? ?* 安全轉(zhuǎn)賬,安全地將 tokenId 代幣從 from 轉(zhuǎn)移到 to,會(huì)檢查合約接收者是否了解 ERC721 協(xié)議,以防止代幣被永久鎖定。調(diào)用了_transfer函數(shù)和_checkOnERC721Received函數(shù)。條件:
? ? ?* from 不能是0地址.
? ? ?* to 不能是0地址.
? ? ?* tokenId 代幣必須存在,并且被 from擁有.
? ? ?* 如果 to 是智能合約, 他必須支持 IERC721Receiver-onERC721Received.
? ? ?*/
? ? function?_safeTransfer(
? ? ? ? address?owner,
? ? ? ? address?from,
? ? ? ? address?to,
? ? ? ? uint?tokenId,
? ? ? ? bytes?memory _data
? ? ) private?{
? ? ? ? _transfer(owner, from, to, tokenId);
? ? ? ? require(_checkOnERC721Received(from, to, tokenId, _data), "not ERC721Receiver");
? ? }
? ? /**
? ? ?* 實(shí)現(xiàn)IERC721的safeTransferFrom,安全轉(zhuǎn)賬,調(diào)用了_safeTransfer函數(shù)。
? ? ?*/
? ? function?safeTransferFrom(
? ? ? ? address?from,
? ? ? ? address?to,
? ? ? ? uint?tokenId,
? ? ? ? bytes?memory _data
? ? ) public?override {
? ? ? ? address?owner = ownerOf(tokenId);
? ? ? ? require(
? ? ? ? ? ? _isApprovedOrOwner(owner, msg.sender, tokenId),
? ? ? ? ? ? "not owner nor approved"
? ? ? ? );
? ? ? ? _safeTransfer(owner, from, to, tokenId, _data);
? ? }
? ? // safeTransferFrom重載函數(shù)
? ? function?safeTransferFrom(
? ? ? ? address?from,
? ? ? ? address?to,
? ? ? ? uint?tokenId
? ? ) external?override {
? ? ? ? safeTransferFrom(from, to, tokenId, "");
? ? }
? ? /**
? ? ?* 鑄造函數(shù)。通過調(diào)整_balances和_owners變量來鑄造tokenId并轉(zhuǎn)賬給 to,同時(shí)釋放Tranfer事件。鑄造函數(shù)。通過調(diào)整_balances和_owners變量來鑄造tokenId并轉(zhuǎn)賬給 to,同時(shí)釋放Tranfer事件。
? ? ?* 這個(gè)mint函數(shù)所有人都能調(diào)用,實(shí)際使用需要開發(fā)人員重寫,加上一些條件。
? ? ?* 條件:
? ? ?* 1. tokenId尚不存在。
? ? ?* 2. to不是0地址.
? ? ?*/
? ? function?_mint(address?to, uint?tokenId) internal virtual {
? ? ? ? require(to != address(0), "mint to zero address");
? ? ? ? require(_owners[tokenId] == address(0), "token already minted");
? ? ? ? _balances[to] += 1;
? ? ? ? _owners[tokenId] = to;
? ? ? ? emit Transfer(address(0), to, tokenId);
? ? }
? ? // 銷毀函數(shù),通過調(diào)整_balances和_owners變量來銷毀tokenId,同時(shí)釋放Tranfer事件。條件:tokenId存在。
? ? function?_burn(uint?tokenId) internal virtual {
? ? ? ? address?owner = ownerOf(tokenId);
? ? ? ? require(msg.sender == owner, "not owner of token");
? ? ? ? _approve(owner, address(0), tokenId);
? ? ? ? _balances[owner] -= 1;
? ? ? ? delete _owners[tokenId];
? ? ? ? emit Transfer(owner, address(0), tokenId);
? ? }
? ? // _checkOnERC721Received:函數(shù),用于在 to 為合約的時(shí)候調(diào)用IERC721Receiver-onERC721Received, 以防 tokenId 被不小心轉(zhuǎn)入黑洞。
? ? function?_checkOnERC721Received(
? ? ? ? address?from,
? ? ? ? address?to,
? ? ? ? uint?tokenId,
? ? ? ? bytes?memory _data
? ? ) private?returns?(bool) {
? ? ? ? if?(to.isContract()) {
? ? ? ? ? ? return
? ? ? ? ? ? ? ? IERC721Receiver(to).onERC721Received(
? ? ? ? ? ? ? ? ? ? msg.sender,
? ? ? ? ? ? ? ? ? ? from,
? ? ? ? ? ? ? ? ? ? tokenId,
? ? ? ? ? ? ? ? ? ? _data
? ? ? ? ? ? ? ? ) == IERC721Receiver.onERC721Received.selector;
? ? ? ? } else?{
? ? ? ? ? ? return?true;
? ? ? ? }
? ? }
? ? /**
? ? ?* 實(shí)現(xiàn)IERC721Metadata的tokenURI函數(shù),查詢metadata。
? ? ?*/
? ? function?tokenURI(uint256?tokenId) public?view virtual override returns?(string?memory) {
? ? ? ? require(_owners[tokenId] != address(0), "Token Not Exist");
? ? ? ? string?memory baseURI = _baseURI();
? ? ? ? return?bytes(baseURI).length > 0?? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
? ? }
? ? /**
? ? ?* 計(jì)算{tokenURI}的BaseURI,tokenURI就是把baseURI和tokenId拼接在一起,需要開發(fā)重寫。
? ? ?* BAYC的baseURI為ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/
? ? ?*/
? ? function?_baseURI() internal view virtual returns?(string?memory) {
? ? ? ? return?"";
? ? }
}
編譯、部署合約,設(shè)置NFT名稱為HHTest,符號(hào)為HH。

合約部署成功后,進(jìn)入ABI操作頁(yè)面,開始測(cè)試合約的mint函數(shù),測(cè)試鑄造一個(gè)NFT。

鑄造完成后使用合約地址在opensea的測(cè)試鏈版中查詢,可以發(fā)現(xiàn)我們剛剛鑄造的NFT可以在其中查詢出來,由于合約用的是無聊猿的tokenURI地址,所以顯示的是無聊猿的信息。
