目录


ERC20
- EIP: Ethereum Improvement Propose
- 任何遵从EIP-20协议(ERC20标准)的Contract都属于ERC20 Token
标准接口
// 6 REQUIRED FUNCTIONS
function totalSupply() public view returns (uint256)
function balanceOf(address _owner) public view returns (uint256 balance)
function transfer(address _to, uint256 _value) public returns (bool success)
function transferFrom(address _from, address _to, uint256 _value) public returns (bool success)
function approve(address _spender, uint256 _value) public returns (bool success)
function allowance(address _owner, address _spender) public view returns (uint256 remaining)
// 2 REQUIRED EVENTS
event Transfer(address indexed _from, address indexed _to, uint256 _value)
event Approval(address indexed _owner, address indexed _spender, uint256 _value)
// 3. OPTIONAL FUNCTIONS
function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
分析
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v3.0.0/contracts/token/ERC20/IERC20.sol
interface IERC20 {
// 6 REQUIRED FUNCTIONS
// 总发行量
function totalSupply() external view returns (uint); // -> 总发行量 100000000 * 10**decimals
// 标准decimal: 18
// USDT: 6
// WBTC: 8
function balanceOf(address account) external view returns (uint); // 指定账户的余额
// 币的持有人直接调用,进行转账
function transfer(address recipient, uint amount) external returns (bool);
// 最常用的!!
// 1. 我这个owner对合约进行approve,此时approve内部会修改allowance变量
// 2. 合约内部调用transferFrom来支配owner的token
function transferFrom( // spender就是这个合约
address sender, // owner
address recipient, // 转给谁
uint amount // 金额
) external returns (bool);
// owner: 币的持有人
// spender: 是指定帮助花费的代理人(被授权的人)
function allowance(address owner, address spender) external view returns (uint); // 授权的额度
// decimals view,这是一个public 的变量,自动提供了一个读取的方法 // 返回精度
// 持有人对spender进行授权,在approve内部,会调用msg.sender来知道owner是谁
function approve(address spender, uint amount) external returns (bool);
// 2 REQUIRED EVENTS
// 事件
event Transfer(address indexed from, address indexed to, uint value);
event Approval(address indexed owner, address indexed spender, uint value);
// 3. OPTIONAL FUNCTIONS
function name() public view returns (string)
function symbol() public view returns (string)
function decimals() public view returns (uint8)
}
以下是ERC20的案例:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
import "./IERC20.sol";
contract ERC20 is IERC20 {
uint public totalSupply;
mapping(address => uint) public balanceOf;
mapping(address => mapping(address => uint)) public allowance;
string public name = "Solidity by Example";
string public symbol = "SOLBYEX";
uint8 public decimals = 18;
function transfer(address recipient, uint amount) external returns (bool) {
balanceOf[msg.sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}
function approve(address spender, uint amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(
address sender,
address recipient,
uint amount
) external returns (bool) {
allowance[sender][msg.sender] -= amount;
balanceOf[sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(sender, recipient, amount);
return true;
}
function mint(uint amount) external {
balanceOf[msg.sender] += amount;
totalSupply += amount;
emit Transfer(address(0), msg.sender, amount);
}
function burn(uint amount) external {
balanceOf[msg.sender] -= amount;
totalSupply -= amount;
emit Transfer(msg.sender, address(0), amount);
}
}
可以使用openzeppelin库进行创建自己的token:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
// import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v4.0.0/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract MyToken is ERC20 {
constructor(string memory name, string memory symbol) ERC20(name, symbol) {
// Mint 100 tokens to msg.sender
// Similar to how
// 1 dollar = 100 cents
// 1 token = 1 * (10 ** decimals)
_mint(msg.sender, 100 * 10**uint(decimals()));
}
// 默认是18,可以进行override
function decimals() public view override returns (uint8) {
return 6;
}
}
ERC721
对于Opeasea而言,测试网目前仅支持Rinkeby,即部署在Rinkeby网络上的nft合约会自动展示在opensea中。
举例教程:https://mp.weixin.qq.com/s/T9GEgaqubHAftpMsdLAMIg
ipfs服务:https://app.pinata.cloud/pinmanager
EIP-721: https://eips.ethereum.org/EIPS/eip-721
标准接口
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721.sol)
pragma solidity ^0.8.0;
interface ERC165 {
function supportsInterface(bytes4 interfaceID) external view returns (bool);
}
interface IERC721 is IERC165 {
// 3 EVENTS
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);
// 9 REQUIRED FUNCTIONS
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);
// 3 OPTIONAL FUNCTIONS
function name() external view returns (string _name);
function symbol() external view returns (string _symbol);
function tokenURI(uint256 _tokenId) external view returns (string);
}
部署
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
/*
1. 一个ERC721合约也是一个集合,可以有N多个token,但是每一个都是不一样的,有唯一的id
2. 每个tokenid可以关联一个URI,一般是.json文件,里面有三个字段,进而变成一个独一无二的展示,所以是NFT,三个字段为:
- description:nft描述
- url:nft图片存储在ipfs上的哈希值
- name:nft名字
*/
contract SimpleCollectible is ERC721URIStorage {
uint256 public tokenCounter;
constructor () public ERC721 ("Dogie", "DOG"){
tokenCounter = 0;
}
function createCollectible(string memory tokenURI) public returns (uint256) {
uint256 newItemId = tokenCounter;
_safeMint(msg.sender, newItemId);
_setTokenURI(newItemId, tokenURI);
tokenCounter = tokenCounter + 1;
return newItemId;
}
function _baseURI() internal view override returns (string memory) {
return "https://gateway.pinata.cloud/ipfs/";
}
//1. createCollectible(QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta)
//2. tokenURI(0)
//3. 浏览器请求:https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta
}
接受者为合约时
to如果是合约地址,则to合约必须实现onERC721Received接口,因为有回调校验,如果to是EOA则不需要实现该接口。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
contract ERC721Holder is IERC721Receiver {
function onERC721Received(
address,
address,
uint256,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC721Received.selector;
}
}
完整代码
// SPDX-License-Identifier: MIT
pragma solidity 0.8.13;
// 里面继承了IERC721Receiver.sol接口
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
/*
1. 一个ERC721合约也是一个集合,可以有N多个token,但是每一个都是不一样的,有唯一的id
2. 每个tokenid可以关联一个图片(URI),进而变成一个独一无二的展示,所以是NFT
*/
contract SimpleCollectible is ERC721URIStorage {
uint256 public tokenCounter;
constructor () public ERC721 ("Dogie", "DOG"){
tokenCounter = 0;
}
function createCollectible(string memory tokenURI) public returns (uint256) {
uint256 newItemId = tokenCounter;
_safeMint(msg.sender, newItemId);
_setTokenURI(newItemId, tokenURI);
tokenCounter = tokenCounter + 1;
return newItemId;
}
// 注释掉
// function _baseURI() internal view override returns (string memory) {
// return "https://gateway.pinata.cloud/ipfs/";
// }
}
contract ERC721Holder is IERC721Receiver {
function onERC721Received(
address,
address,
uint256,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC721Received.selector;
}
}
测试
// QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB是json文件的hash
//1. createCollectible(QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB)
//2. tokenURI(0)
//3. 浏览器请求:https://gateway.pinata.cloud/ipfs/QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB
其中的hash值为这个nft图片的描述文件的哈希:metadata.json,
- json文件上传后的哈希为:QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB,这个哈希用于作为tokenuri,在合约内部会和baseUrl组装起来。
- 图片的哈希为:QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta
{
"description": "this is a nft1155 metadata json desc",
"image": "https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta",
"name": "duke nft"
}
总结
- 在页面展示时,url为:https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta
- 实际上的tokenuri为:https://gateway.pinata.cloud/ipfs/QmSiaLLeuBKPvqAQz2VQWJHUJMz6xjMgagePha4kPd38TB
授权比较
- ERC721
// 对单个id进行approve
function approve(address to, uint256 tokenId) public virtual override;
// 对某个地址授权所有的token
function setApprovalForAll(address operator, bool approved) public virtual override;
- ERC1155
// 每个对单个id的授权!!
// what -》不支持!
// 对某个地址授权所有的token
function setApprovalForAll(address operator, bool approved) external;
ERC1155
- 1155是每个id为一个token组,每个组里面可以包含多个erc20
- mint(to, id, amount)
- setURI(id, uri)
- setURI的时候,填写的是metadata.json,不是图片的url,使用pinata服务存储图片和metadata.json:
{
"description": "this is a nft1155 metadata json desc",
//"image": "ipfs://QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta",
"image": "https://gateway.pinata.cloud/ipfs/QmTfK2CeRBkRqSZHmnekdZYSBrsKLQ6U5Px8MWtGf1Eqta",
"name": "duke nft"
}
- opensea测试网是rinkeby,链接为:https://testnets.opensea.io
- opensea会自动将当前链接的地址账户的nft扫描出来展示在页面上。
- 自己部署(示例):https://testnets.opensea.io/assets/rinkeby/0x54f1a0ad311e00b68fa940bc969c191863ac84fd/1
更好的合约:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155URIStorage.sol";
contract MockErc1155Token is ERC1155URIStorage {
string public name;
string public symbol;
uint256 public tokenCounter;
constructor(string memory _name, string memory _symbol) ERC1155("") {
name = _name;
symbol = _symbol;
}
function createCollectible(string memory tokenURI, uint256 _amount)
external
returns (uint256)
{
uint256 newItemId = tokenCounter;
_mint(_msgSender(), newItemId, _amount, "");
_setURI(newItemId, tokenURI);
tokenCounter = tokenCounter + 1;
return newItemId;
}
// function mint(uint256 _id, uint256 _amount) external {
// _mint(_msgSender(), _id, _amount, "");
// }
// function mintBatch(uint256[] memory _ids, uint256[] memory _amounts)
// external
// {
// _mintBatch(_msgSender(), _ids, _amounts, "");
// }
function burn(uint256 _id, uint256 _amount) external {
_burn(msg.sender, _id, _amount);
}
function burnBatch(uint256[] memory _ids, uint256[] memory _amounts)
external
{
_burnBatch(msg.sender, _ids, _amounts);
}
function burnForMint(
address _from,
uint256[] memory _burnIds,
uint256[] memory _burnAmounts,
uint256[] memory _mintIds,
uint256[] memory _mintAmounts
) external {
_burnBatch(_from, _burnIds, _burnAmounts);
_mintBatch(_from, _mintIds, _mintAmounts, "");
}
function setURI(uint256 _id, string memory _uri) external {
_setURI(_id, _uri);
}
}
ERC165
- 没看到interfaceId在哪里定义了,目前没有找到(这个值哪来的?)
- 没见到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);
}
ERC165的目的-一种发布和检测智能合约实现的接口的标准。
防止用户传递给接口的地址是无效的(如EOA地址),该协议会在合约内部做地址检查。
本教程仅供学习,如有疑问,请移步Telegram

