ERC721是一种以太坊上的非同质化代币(NFT,Non-FungibleToken)标准,用于表示唯一的、不可替代的数字资产。它利用了唯一的tokenId来确定每一个NFT代币,每个NFT的合约中管理着一批类似的但每个都是不同的代币:https://learnblockchain
ERC721
是一种以太坊上的 非同质化代币(NFT
, Non-Fungible Token
)标准,用于表示唯一的、不可替代的数字资产。它利用了唯一的 tokenId
来确定每一个 NFT
代币,每个 NFT
的合约中管理着一批类似的但每个都是不同的代币。
IERC165
是一个接口检测标准。提供给一个合约调用之前,接口检测的能力。
里面有个关键的接口:
function supportsInterface(bytes4 interfaceId) external view returns (bool);
实现这个接口的合约,表明提供给外部调用一个接口检测能力,防止外部调用不存在的接口进而造成 gas
浪费。
假如我们一个合约实现了 IERC165
、IERC721
则实现如下:
function supportsInterface(bytes4 interfaceId) external pure override returns (bool)
{
return
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC165).interfaceId;
}
也可以写成这样:
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return interfaceId == 0x80ac58cd || // ERC165 Interface ID for ERC721
interfaceId == 0x01ffc9a7; // ERC165 Interface ID for ERC165
}
这两个 interfaceId
看起来是不是很像函数选择器?实际上,它的确和函数选择器有关,它是由接口中所有的函数选择器进行异或(^)
操作得来的:
interfaceId = bytes4(keccak256("function1Signature"))
^ bytes4(keccak256("function2Signature"))
^ ...
^ bytes4(keccak256("functionNSignature"))
在 ERC721
标准中,有一种叫做 safeTransferFrom
的转账方法。它比 transferFrom
更安全:
如果转移 NFT
的目标地址是合约,它必须实现 IERC721Receiver
接口中的 onERC721Received
方法才能进行 NFT
的转移。(此外还需要处理逻辑,接收了但不处理也相当于进了黑洞)
如果合约不打算处理 NFT
,则无须实现该接口。发送者在使用 safeTransferFrom
发送到这个合约地址的时候,会失败并 revert
。
实现只需返回一个 魔法值
(该函数的选择器)即可。
实现如下:
// 返回这个 magic value 表示“我接受这个 NFT”
return this.onERC721Received.selector;
和 IERC20Metadata
类似,EREC721
也有类似的标准,称为 IERC721Metadata
。提供了三个查询 NFT metadata
的常用函数。
interface IERC721Metadata is IERC721 {
// 返回NFT名称
function name() external view returns (string memory);
// 返回 NFT 代号
function symbol() external view returns (string memory);
// 返回 NFT 元数据的url
function tokenURI(uint256 tokenId) external view returns (string memory);
}
ERC721
状态变量解析
// NFT 名称
string private _name;
// NFT 代号 string private _symbol; // 某个 tokenId 的拥有者 mapping(uint256 tokenId => address) private _owners; // 记录 owner 拥有这种 NFT 的数量 mapping(address owner => uint256) private _balances; // 记录某个 tokenId 被授权给谁了 mapping(uint256 tokenId => address) private _tokenApprovals; // 记录 owner 授权给 operator 所有权限(能操作他的所有 NFT ) mapping(address owner => mapping(address operator => bool)) private _operatorApprovals;
- **`ERC721` 事件和接口解析**
```js
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);
// 返回某个地址所拥有的 NFT 数量
function balanceOf(address owner) external view returns (uint256 balance);
// 返回某个 tokenId 的拥有者地址
function ownerOf(uint256 tokenId) external view returns (address owner);
// 安全转账,目的地址必须实现 IERC721Receiver 接口
function safeTransferFrom(address from, address to, uint256 tokenId) external;
// 直接转账,目标地址是合约有可能被锁死 NFT
function transferFrom(address from, address to, uint256 tokenId) external;
// 授权 to 地址操作指定的 tokenId
function approve(address to, uint256 tokenId) external;
// 获取 tokenId 被授权给谁
function getApproved(uint256 tokenId) external view returns (address operator);
// 允许 operator 操作所有 NFT
function setApprovalForAll(address operator, bool _approved) external;
// 查询是否为全权操作员(owner 为拥有者,operator 为被全权授权的操作者)
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
OZ
合约库中的 ERC721
去重写一些函数、自定义一些函数实现,要看实现的可以到 OZ
的仓库看。本次操作不做具体实现的解析。(无非也就对上面状态变量的增删改查罢了)
openzeppelin-contracts/contracts/token/ERC721/ERC721.sol
和 ERC20
代币一致,这次,我们依旧采用 foundry
来手撸一个 NFT
合约上线。
nft
项目: 该命令会在文件夹下创建一个 nft
的目录
forge init nft
OZ
库
forge install OpenZeppelin/openzeppelin-contracts --no-commit
OZ
的 ERC721
)
contract ShawnNFT is ERC721 {
constructor(string memory name_, string memory symbol_) ERC721(name_, symbol_){
// 固定发行量为 1000,全部 mint 给合约部署者
for(uint tokenId = 0; tokenId < 1000; tokenId++){
_mint(msg.sender, tokenId);
}
}
// 重写 ERC721 里面的逻辑,指向 BAYC 的 metadata 地址 function _baseURI() internal pure override returns (string memory){ return "ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/"; } }
4. **编写部署脚本**
```js
contract DeployShawnNFT is Script {
function run() external {
// 获取当前部署者地址
address deployer = vm.addr(vm.envUint("PRIVATE_KEY"));
vm.startBroadcast();
// 1. 部署合约
ShawnNFT nft = new ShawnNFT("Shawn NFT", "SNFT");
// 2. 查看部署者拥有多少 NFT
uint256 balance = nft.balanceOf(deployer);
console.log("Deployer NFT Balance:", balance);
// 3. 将第一个 tokenId 转移给另一个地址(用于演示)
address recipient = address(0xBEEF); // 可以换成你想测试的地址
nft.safeTransferFrom(deployer, recipient, 0);
console.log("Transferred tokenId 0 to recipient.");
vm.stopBroadcast();
}
}
.env
PRIVATE_KEY=your_private_key
HOLESKY_RPC_URL=https://ethereum-holesky-rpc.publicnode.com
foundry.toml
[rpc_endpoints]
holesky = "https://ethereum-holesky-rpc.publicnode.com"
source .env
forge script script/DeployShawnNFT.sol \
--rpc-url $HOLESKY_RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast \
-vvvv
如果看到以下记录,即部署成功
进入区块浏览器holesky测试网 输入合约地址,即可看到:
ps
:由于我们部署到链上测试网,所以 console.log()
并不会打印出来,所以我们要使用 cast
命令来查看 script
脚本中的 balanceOf()
和 transfer()
是否生效。
使用 cast
命令查看部署者 nft
余额
cast call 0xF45C4eE07afeDC62B2B4833F693F6cDdF27eC14d "balanceOf(address)(uint256)" 0xeD2Eb97b84386Cb74C1232e833C8ca26FcD2e1b9 \
--rpc-url $HOLESKY_RPC_URL
再去看 to
地址的余额为 1
,正确。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!