NFT发行 - 超简单发行 NFT 到 holesky 上(包含 ERC165、ERC721Receiver 的含义)

ERC721是一种以太坊上的非同质化代币(NFT,Non-FungibleToken)标准,用于表示唯一的、不可替代的数字资产。它利用了唯一的tokenId来确定每一个NFT代币,每个NFT的合约中管理着一批类似的但每个都是不同的代币:https://learnblockchain

ERC721 是什么

ERC721 是一种以太坊上的 非同质化代币(NFT, Non-Fungible Token)标准,用于表示唯一的、不可替代的数字资产。它利用了唯一的 tokenId 来确定每一个 NFT 代币,每个 NFT 的合约中管理着一批类似的但每个都是不同的代币。

前提条件:IERC165

IERC165 是一个接口检测标准。提供给一个合约调用之前,接口检测的能力。 里面有个关键的接口:

function supportsInterface(bytes4 interfaceId) external view returns (bool);

实现这个接口的合约,表明提供给外部调用一个接口检测能力,防止外部调用不存在的接口进而造成 gas 浪费。

假如我们一个合约实现了 IERC165IERC721 则实现如下:

    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"))

前提条件:ERC721Receiver

ERC721 标准中,有一种叫做 safeTransferFrom 的转账方法。它比 transferFrom 更安全:

  • 如果转移 NFT 的目标地址是合约,它必须实现 IERC721Receiver 接口中的 onERC721Received 方法才能进行 NFT 的转移。(此外还需要处理逻辑,接收了但不处理也相当于进了黑洞)

  • 如果合约不打算处理 NFT ,则无须实现该接口。发送者在使用 safeTransferFrom 发送到这个合约地址的时候,会失败并 revert

  • 实现只需返回一个 魔法值(该函数的选择器)即可。 实现如下:

        // 返回这个 magic value 表示“我接受这个 NFT”
        return this.onERC721Received.selector;

前提条件:IERC721Metadata

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 主合约解析

  • 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

    手撸一个 NFT 并部署上线

    ERC20 代币一致,这次,我们依旧采用 foundry 来手撸一个 NFT 合约上线。

  1. 初始化一个 nft 项目: 该命令会在文件夹下创建一个 nft 的目录
    forge init nft
  2. 安装 OZ
    forge install OpenZeppelin/openzeppelin-contracts --no-commit
  3. 编写代码(继承自 OZERC721
    
    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();
    }
}
  1. 配置环境变量
    • .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"
  1. 命令部署
    • 加载环境变量
      source .env
    • 部署命令
forge script script/DeployShawnNFT.sol \
  --rpc-url $HOLESKY_RPC_URL \
  --private-key $PRIVATE_KEY \
  --broadcast \
  -vvvv
  • 如果看到以下记录,即部署成功 image.png

  • 进入区块浏览器holesky测试网 输入合约地址,即可看到: image.png ps:由于我们部署到链上测试网,所以 console.log() 并不会打印出来,所以我们要使用 cast 命令来查看 script 脚本中的 balanceOf()transfer() 是否生效。

  • 使用 cast 命令查看部署者 nft 余额

    cast call 0xF45C4eE07afeDC62B2B4833F693F6cDdF27eC14d "balanceOf(address)(uint256)" 0xeD2Eb97b84386Cb74C1232e833C8ca26FcD2e1b9 \
    --rpc-url $HOLESKY_RPC_URL

    image.png

  • 再去看 to 地址的余额为 1,正确。

image.png

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
shawn_shaw
shawn_shaw
web3潜水员、技术爱好者、web3钱包开发工程师、欢迎交流工作机会。欢迎骚扰:vx:cola_ocean