Solidity极简入门 | ERC721专题第三讲:ERC721主合约

本文详细介绍了ERC721主合约的结构,包括6个状态变量(如_name,_symbol,_owners等)和28个函数(如constructor,approve,transferFrom等),这些构成了NFT的标准接口。合约还涉及了授权管理、安全转账和NFT生命周期的操作,如mint和burn。
摘要由CSDN通过智能技术生成

ERC721 主合约

我在 ERC721 前两讲介绍了它的相关库和接口,终于这讲可以介绍主合约了。ERC721 主合约包含 6 个状态变量和 28 个函数,我们将会一一介绍。并且,我给 ERC721 代码增加了中文注释,方便大家使用。

状态变量

0b18c98c7b4bf5490649fe0595d8c066.jpeg

· _name 和_symbol 是两个 string,存储 Token 的名称和代号。 

· _owners 是 tokenId 到 owner 地址的 Mapping,存储每个 Token 的持有人。 

· _balances 是 owner 地址到持币数量的 Mapping,存储每个地址的持仓量。 

· _tokenApprovals 是 tokenId 到授权地址的 Mapping,存储每个 token 的授权信息。 

· _operatorApprovals 是 owner 地址到是否批量批准的 Mapping,存储每个 owner 的批量授权信息。注意,批量授权会把你钱包持有这个系列的所有 nft 都授权给另一个地址,别人可以随意支配。

函数

· constructor:构造函数,设定 ERC721Token 的名字和代号(_name 和_symbol 变量)。

ab24b897def8d11edad715facc3ea68a.jpeg

· supportsInterface:实现 IERC165 接口 supportsInterface

dae50f92b2d478233f098ba97cd8aa50.jpeg

· balanceOf:实现 IERC721 的 balanceOf,利用_balances 变量查询 owner 地址的 balance。

1f3115e8c55123c5eccab38bbf08b124.jpeg

· ownerOf:实现 IERC721 的 ownerOf,利用_owners 变量查询 tokenId 的 owner。

0e3b25a7e87a70a1e9a5d19e6bfaa6fd.jpeg

· name:实现 IERC721Metadata 的 name,查询 Token 名称。

c552780687d43ff426e8917e05e083df.jpeg

· symbol:实现 IERC721Metadata 的 symbol,查询 Token 代号。

3c1e9b79c89126fb46d6d0d3085d7f9c.jpeg

· tokenURI:实现 IERC721Metadata 的 tokenURI,查询 Tokenmetadata 存放的网址。Opensea 还有小狐狸钱包显示你 NFT 的图片,调用的就是这个函数。

e4cb052fe2913ec20595b4deb5c4afa0.jpeg

· _baseURI:基 URI,会被 tokenURI() 调用,跟 tokenId 拼成 tokenURI,默认为空,需要子合约重写这个函数。

d4bbd769e8efaf1bc633ed9be5e29ca7.jpeg

· approve:实现 IERC721 的 approve,将 tokenId 授权给 to 地址。条件:to 不是 owner,且 msg.sender 是 owner 或授权地址。调用_approve 函数。

f3ac54ac47292700f2f32a53389f4bfe.jpeg

· getApproved:实现 IERC721 的 getApproved,利用_tokenApprovals 变量查询 tokenId 的授权地址。

ff5c4b16679580ecd4d056172c5626f7.jpeg

· setApprovalForAll:实现 IERC721 的 setApprovalForAll,将持有 Token 全部授权给 operator 地址。调用_setApprovalForAll 函数。

858ef0750a798480d2ea28446600ff94.jpeg

· isApprovedForAll:实现 IERC721 的 isApprovedForAll,利用_operatorApprovals 变量查询 owner 地址是否将所持 NFT 批量授权给了 operator 地址。

ddc0ea81b097e4e1ef6f749bc2aae4c2.jpeg

· transferFrom:实现 IERC721 的 transferFrom,非安全转账,不建议使用。调用_transfer 函数。

4f2e6f431670b4b8bfb9058b159d0939.jpeg

· safeTransferFrom:实现 IERC721 的 safeTransferFrom,安全转账,调用了_safeTransfer 函数。

bb9137571a7fcaf61bc4fbddc6e72c06.jpeg

· _safeTransfer:安全转账,安全地将 tokenId Token 从 from 转移到 to,会检查合约接收者是否了解 ERC721 协议,以防止 Token 被永久锁定。调用了_transfer 函数和_checkOnERC721Received 函数。条件: 

1.from 不能是 0 地址. 

2.to 不能是 0 地址. 

3.tokenId Token 必须存在,并且被 from 拥有. 

4.如果 to 是智能合约, 他必须支持 IERC721Receiver-onERC721Received.

ebee3d6bd9b70b1c73a8efe9c3244032.jpeg

· _exists:查询 tokenId 是否存在(等价于查询他的 owner 是否为非 0 地址)。

d8ed77f79b50c331e9fc3b65b5d355f4.jpeg

· _isApprovedOrOwner:查询 spender 地址是否被可以使用 tokenId(他是 owner 或被授权地址)。

b794ef9a0013686d68398c3220cc3286.jpeg

· _safeMint:安全 mint 函数,铸造 tokenId 并转账给 to 地址。条件: tokenId 尚不存在, 若 to 为智能合约,他要支持 IERC721Receiver-onERC721Received 接口。

98f72881d30cfd1d26494a15f925bf55.jpeg

· _safeMint 的实现,调用了_checkOnERC721Received 函数和_mint 函数

3608b660777eea279030080161c10821.jpeg

· _mint:internal 铸造函数。通过调整_balances 和_owners 变量来铸造 tokenId 并转账给 to,同时释放 Tranfer 事件。条件: tokenId 尚不存在。to 不是 0 地址.

647d4a9acfc85c0b92a14f5f9ceb30fb.jpeg

· _burn:internal 销毁函数,通过调整_balances 和_owners 变量来销毁 tokenId,同时释放 Tranfer 事件。条件:tokenId 存在。

a70296936f2e4cf0f82ccd02947ca65f.jpeg

· _transfer:转账函数。通过调整_balances 和_owner 变量将 tokenId 从 from 转账给 to,同时释放 Tranfer 事件。条件: tokenId 被 from 拥有 to 不是 0 地址

1e4153d4ea063167b1b167bbc54ab155.jpeg

· _approve:授权函数。通过调整_tokenApprovals 来,授权 to 地址操作 tokenId,同时释放 Approval 事件。

50899b4fe9f191108d7ff9b9997caf8f.jpeg

· _setApprovalForAll:批量授权函数。通过调整_operatorApprovals 变量,批量授权 to 来操作 owner 全部代币,同时释放 ApprovalForAll 事件。

d9352d887b8c936c9d7a494cca2b18fd.jpeg

· _checkOnERC721Received:函数,用于在 to 为合约的时候调用 IERC721Receiver-onERC721Received, 以防 tokenId 被不小心转入黑洞。

1f5e13965addb294a5e179f5441933d4.jpeg

· _beforeTokenTransfer:这个函数在转账之前会被调用(包括 mint 和 burn)。默认为空,子合约可以选择重写。

c393416c9fb10bb50c6c56ebd3a11418.jpeg

· _afterTokenTransfer:这个函数在转账之后会被调用(包括 mint 和 burn)。默认为空,子合约可以选择重写。

874313560c9aab41069a8f1e0d04e290.jpeg

总结

本文是 ERC721 专题的第三讲,我介绍了 ERC721 主合约的全部变量和函数,并给出了合约的中文注释。有了 ERC721 标准,NFT 项目方只需要把 mint 函数包装一下,就可以发行 NFT 了。

ca708417a09cc48a0030e48478506271.jpeg

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个比较省gas费的ERC721合约示例代码: ``` pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol"; import "@openzeppelin/contracts/utils/Address.sol"; contract MyNFT is IERC721 { using Address for address; mapping (uint256 => address) private _owners; mapping (address => uint256) private _balances; mapping (uint256 => address) private _tokenApprovals; mapping (address => mapping (address => bool)) private _operatorApprovals; function balanceOf(address owner) public view override returns (uint256) { require(owner != address(0), "ERC721: balance query for the zero address"); return _balances[owner]; } function ownerOf(uint256 tokenId) public view override returns (address) { address owner = _owners[tokenId]; require(owner != address(0), "ERC721: owner query for nonexistent token"); return owner; } function safeTransferFrom(address from, address to, uint256 tokenId) public override { safeTransferFrom(from, to, tokenId, ""); } function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory _data) public override { require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved"); _safeTransfer(from, to, tokenId, _data); } function transferFrom(address from, address to, uint256 tokenId) public override { require(_isApprovedOrOwner(msg.sender, tokenId), "ERC721: transfer caller is not owner nor approved"); _transfer(from, to, tokenId); } function approve(address to, uint256 tokenId) public override { address owner = ownerOf(tokenId); require(to != owner, "ERC721: approval to current owner"); require(msg.sender == owner || isApprovedForAll(owner, msg.sender), "ERC721: approve caller is not owner nor approved for all" ); _tokenApprovals[tokenId] = to; emit Approval(owner, to, tokenId); } function setApprovalForAll(address operator, bool approved) public override { require(operator != msg.sender, "ERC721: approve to caller"); _operatorApprovals[msg.sender][operator] = approved; emit ApprovalForAll(msg.sender, operator, approved); } function isApprovedForAll(address owner, address operator) public view override returns (bool) { return _operatorApprovals[owner][operator]; } function getApproved(uint256 tokenId) public view override returns (address) { require(_owners[tokenId] != address(0), "ERC721: approved query for nonexistent token"); return _tokenApprovals[tokenId]; } 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"); } function _transfer(address from, address to, uint256 tokenId) internal virtual { require(ownerOf(tokenId) == from, "ERC721: transfer of token that is not own"); require(to != address(0), "ERC721: transfer to the zero address"); _owners[tokenId] = to; _balances[from] -= 1; _balances[to] += 1; emit Transfer(from, to, tokenId); } function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) { require(_owners[tokenId] != address(0), "ERC721: operator query for nonexistent token"); address owner = ownerOf(tokenId); return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender)); } function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory _data) private returns (bool) { if (!to.isContract()) { return true; } bytes memory returndata = to.functionCall(abi.encodeWithSelector( IERC721Receiver(to).onERC721Received.selector, msg.sender, from, tokenId, _data ), "ERC721: transfer to non ERC721Receiver implementer"); bytes4 retval = abi.decode(returndata, (bytes4)); return (retval == _ERC721_RECEIVED); } bytes4 private constant _ERC721_RECEIVED = 0x150b7a02; } ``` 这个合约代码只包含了ERC721标准规定的基本函数,没有额外的复杂逻辑,因此省去了一些不必要的计算和存储操作,从而降低了gas费用。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值