不止 Gas 优化:ERC721、ERC721A、ERC721C 功能与场景全维度对决

38 阅读7分钟

前言

本文将针对ERC721、ERC721A与ERC721C这三个标准,从核心差异、关键机制、选型建议等多个维度进行系统梳理,并通过代码实现给出直观的对比分析。补充说明关于ERC721和ERC721A智能合约的开发测试部署可以参考博主的另外两篇文章《快速实现一个标准的NFT合约(实操篇)》《ERC721与ERC721A:NFT标准的对比与特性分析》,本文着重对ERC721C合约的实现;

概述

ERC721 是 NFT 基础标准,ERC721A 是其批量铸造的 Gas 优化版,ERC721C 则聚焦创作者版税强制与转移管控,三者在核心目标、实现机制与适用场景上差异显著。当然了三个标准并非简单替代关系,而是针对不同痛点的专业化解决方案;

核心差异对比表

维度ERC721(OpenZeppelin)ERC721A(Azuki)ERC721C(Creator Token)
定位NFT 基础标准批量铸造 Gas 优化实现创作者版税与转移管控增强
数据结构每个 tokenId 独立存储所有者连续 ID 区间共享所有者(惰性初始化)基于 ERC721 扩展转移限制与支付处理器
批量铸造 Gas线性增长(10 个≈62 万)近似常数(10 个≈14 万,优化 77%)与 ERC721 相当,额外版税逻辑略增
首次转移成本稳定(≈3.5 万)略高(补齐存储),后续转移正常随管控级别变化
核心特性所有权与转移基础接口批量铸造、低成本分发强制版税、平台白名单、转移规则
兼容市场全兼容主流兼容依赖支持 CAPS 的市场(OpenSea、Magic Eden)
适用场景通用 NFT、单枚铸造大型集合、批量空投创作者经济、IP 管控、版税依赖项目

关键机制解析

  1. ERC721(基础标准)

    • 定义 NFT 核心接口(ownerOf、transferFrom 等),每个 tokenId 独立记录所有者,逻辑清晰但批量操作 Gas 成本高。
    • 适合单枚铸造、小体量收藏或需要高度自定义的项目。
  2. ERC721A(Gas 优化)

    • 通过 “区间所有者” 模型,连续 tokenId 仅在起始位置记录所有者,后续 ID 通过向前查找确定归属。
    • 批量铸造成本近乎固定,适合 10000 + 规模 PFP、游戏道具空投等高频分发场景。
    • 注意:首次转移非连续 ID 时需补齐存储,Gas 略高于 ERC721。
  3. ERC721C(版税与管控)

    • 引入 PaymentProcessor 合约强制版税,通过_beforeTokenTransfer 钩子限制非白名单平台交易。
    • 支持创作者设置转移规则(如禁止 P2P、最低售价),并可与市场分润激励推广。
    • 适合依赖版税收入的艺术家、IP 方及需要严格转移管控的项目。

选型建议

  • 优先选ERC721:通用 NFT、小批量铸造、追求最大兼容性。
  • 优先选ERC721A:大型集合、批量分发、对 Gas 成本敏感。
  • 优先选ERC721C:创作者经济、IP 保护、需强制版税与转移管控。

智能合约开发、测试、部署

智能合约

1.ERC721智能合约:

关于ERC721智能合约可以查看博主的此篇文章《快速实现一个标准的NFT合约(实操篇)》;包含了完整的开发、测试、部署全流程,相关内容就不赘述了;

2.ERC721A智能合约

关于ERC721A智能合约可以查看博主的此篇文章《ERC721与ERC721A:NFT标准的对比与特性分析》;包含了完整的开发、测试、部署全流程,相关内容就不赘述了;

3.ERC721C智能合约

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

/*
 * 依赖:
 *   @openzeppelin/contracts@5.4.0
 *   solady@^0.1.26
 */
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {ERC2981} from "@openzeppelin/contracts/token/common/ERC2981.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {LibString} from "solady/src/utils/LibString.sol";

/**
 * @title  ERC721C
 * @notice 极简 ERC-721 + ERC-2981 实现,使用 solady 优化字符串与签名
 */
contract ERC721C is ERC721, ERC2981, Ownable {
    using LibString for uint256;

    /*//////////////////////////////////////////////////////////////
                                CONFIG
    //////////////////////////////////////////////////////////////*/
    uint96 private constant _DEFAULT_ROYALTY_BPS = 500; // 5%
    string private _baseTokenURI;

    /*//////////////////////////////////////////////////////////////
                              CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/
    constructor(
        string memory name_,
        string memory symbol_,
        string memory baseURI_,
        address royaltyReceiver_
    ) ERC721(name_, symbol_) Ownable(_msgSender()) {
        _baseTokenURI = baseURI_;
        _setDefaultRoyalty(royaltyReceiver_, _DEFAULT_ROYALTY_BPS);
    }

    /*//////////////////////////////////////////////////////////////
                          PUBLIC / EXTERNAL
    //////////////////////////////////////////////////////////////*/
    /// @dev 安全铸造(仅合约拥有者)
    function safeMint(address to, uint256 tokenId) external onlyOwner {
        _safeMint(to, tokenId);
    }

    /// @dev 批量安全铸造(仅合约拥有者)
    function safeBatchMint(address to, uint256[] calldata tokenIds) external onlyOwner {
        for (uint256 i; i < tokenIds.length; ) {
            _safeMint(to, tokenIds[i]);
            unchecked { ++i; }
        }
    }

    /// @notice 更新 baseURI
    function setBaseURI(string calldata newBaseURI) external onlyOwner {
        _baseTokenURI = newBaseURI;
    }

    /// @notice 更新版税(ERC-2981)
    function setRoyaltyInfo(address receiver, uint96 feeBps) external onlyOwner {
        _setDefaultRoyalty(receiver, feeBps);
    }

    /*//////////////////////////////////////////////////////////////
                            INTERNAL / VIEW
    //////////////////////////////////////////////////////////////*/
    function _baseURI() internal view override returns (string memory) {
        return _baseTokenURI;
    }

    /// @dev 统一授权接口支持(ERC-721 + ERC-2981)
    function supportsInterface(bytes4 interfaceId)
        public
        view
        override(ERC721, ERC2981)
        returns (bool)
    {
        return super.supportsInterface(interfaceId);
    }
}

编译指令

npx hardhat compile

智能合约部署脚本

注释采用HardhatV3中viem 或者ethers.js同理语法略有不同,关于ERC721和ERC721A部署详情可以参考以上对用的文章

部署脚本

// scripts/deploy.js
import { network, artifacts } from "hardhat";
async function main() {
  // 连接网络
  const { viem } = await network.connect({ network: network.name });//指定网络进行链接
  
  // 获取客户端
  const [deployer] = await viem.getWalletClients();
  const publicClient = await viem.getPublicClient();
 
  const deployerAddress = deployer.account.address;
   console.log("部署者的地址:", deployerAddress);
  // 加载合约
  const artifact = await artifacts.readArtifact("ERC721C");
  const ipfsjsonuri="https://zygomorphic-magenta-bobolink.myfilebase.com/ipfs/QmQT8VpmWQVhUhoDCEK1mdHXaFaJ3KawkRxHm96GUhrXLB"
  // 部署(构造函数参数:recipient, initialOwner)
  const hash = await deployer.deployContract({
    abi: artifact.abi,//获取abi
    bytecode: artifact.bytecode,//硬编码
    args: ["MyRoyaltyNFT","MRNFT",ipfsjsonuri,deployerAddress],//nft名称,nft符号,ipfsjsonuri,部署者地址
  });

  // 等待确认并打印地址
  const receipt = await publicClient.waitForTransactionReceipt({ hash });
  console.log("合约地址:", receipt.contractAddress);
}

main().catch(console.error);

部署指令

npx hardhat run ./scripts/xxx.ts

智能合约测试脚本

注释关于ERC721和ERC721A智能合约测试详情参考以上对应的两篇完整

import assert from "node:assert/strict";
import { describe, it,beforeEach  } from "node:test";
import { formatEther,parseEther } from 'viem'
import { network } from "hardhat";
describe("ERC721C", async function () {
    let viem: any;
    let publicClient: any;
    let owner: any, user1: any, user2: any, user3: any;
    let deployerAddress: string;
    let MyERC721C: any;
    beforeEach (async function () {
        const { viem } = await network.connect();
         publicClient = await viem.getPublicClient();//创建一个公共客户端实例用于读取链上数据(无需私钥签名)。
         [owner,user1,user2,user3] = await viem.getWalletClients();//获取第一个钱包客户端 写入联合交易
        deployerAddress = owner.account.address;//钱包地址
       const ipfsjsonuri="https://zygomorphic-magenta-bobolink.myfilebase.com/ipfs/QmQT8VpmWQVhUhoDCEK1mdHXaFaJ3KawkRxHm96GUhrXLB";
       
        MyERC721C = await viem.deployContract("ERC721C", [
            "My Royalty NFT",
            "MRNFT",
            ipfsjsonuri,
            deployerAddress
        ]);//部署合约
        console.log("MyRoyaltyNFT合约地址:", MyERC721C.address); 
    });
    it("测试ERC721C", async function () {
        //查询nft名称和符号
       const name= await publicClient.readContract({
            address: MyERC721C.address,
            abi: MyERC721C.abi,
            functionName: "name",
            args: [],
        });
       const symbol= await publicClient.readContract({
            address: MyERC721C.address,
            abi: MyERC721C.abi,
            functionName: "symbol",
            args: [],
        });
        //查询合约拥有者
       const ownerAddress= await publicClient.readContract({
            address: MyERC721C.address,
            abi: MyERC721C.abi,
            functionName: "owner",
            args: [],
        });
        console.log(name,symbol,ownerAddress)
        //铸造单个nft
       const safeMintHash=await owner.writeContract({
            address: MyERC721C.address,
            abi: MyERC721C.abi,
            functionName: "safeMint",
            args: [user1.account.address,0],
        });
        console.log("铸造单个nft交易哈希:",`${safeMintHash} eth`)
        //等待交易确认
        const receipt = await publicClient.getTransactionReceipt({
        hash: safeMintHash,
        });
        console.log("铸造单个nft交易确认gas:",formatEther(receipt.gasUsed))
        //批量铸造nft
        const nftIds=[1,2,3]
       const safeBatchMinthash =await owner.writeContract({
            address: MyERC721C.address,
            abi: MyERC721C.abi,
            functionName: "safeBatchMint",
            args: [user1.account.address,nftIds],
        });
        //估算gas费用
        console.log("批量铸造nft交易哈希gas:",`${safeBatchMinthash} eth`)
        //等待交易确认
        const receipt1 = await publicClient.getTransactionReceipt({
        hash: safeBatchMinthash,
        });
        
        console.log("批量铸造nft交易确认:",formatEther(receipt1.gasUsed))
        //查询单个nft的tokenURI
        const TokenURI= await publicClient.readContract({
            address: MyERC721C.address,
            abi: MyERC721C.abi,
            functionName: "tokenURI",
            args: [0],
        });
        console.log(TokenURI)
        //查询余额和拥有者
        const balanceOf=await publicClient.readContract({
            address: MyERC721C.address,
            abi: MyERC721C.abi,
            functionName: "balanceOf",
            args: [user1.account.address],
        });
        console.log(balanceOf)
        //查询nft的拥有者
        const ownerOf=await publicClient.readContract({
            address: MyERC721C.address,
            abi: MyERC721C.abi,
            functionName: "ownerOf",
            args: [0],
        });
        console.log(ownerOf)
        //查询版税信息
        const royaltyInfo=await publicClient.readContract({
            address: MyERC721C.address,
            abi: MyERC721C.abi,
            functionName: "royaltyInfo",
            args: [0,parseEther("2")],
        });
        console.log(royaltyInfo)
        const GETAPPROVED=await publicClient.readContract({
            address: MyERC721C.address,
            abi: MyERC721C.abi,
            functionName: "getApproved",
            args: [0],
        });
        console.log(GETAPPROVED)
        //设置BaseURI
        const ipfsjsonuri1="https://zygomorphic-magenta-bobolink.myfilebase.com/ipfs/QmcN49MKt4MbSXSGckAcpvFqtea43uuPD2tvmuER1mG67s";
       const setBaseURI=await owner.writeContract({
            address: MyERC721C.address,
            abi: MyERC721C.abi,
            functionName: "setBaseURI",
            args: [ipfsjsonuri1],
        });
        console.log(setBaseURI)
        //查询更新后的tokenURI
        const TokenURI1= await publicClient.readContract({
            address: MyERC721C.address,
            abi: MyERC721C.abi,
            functionName: "tokenURI",
            args: [0],
        });
        console.log("更新后",TokenURI1)
        //设置默认版税
        // const SETDEFAULTROYALTY=await owner.writeContract({
        //     address: MyRoyaltyNFT.address,
        //     abi: MyRoyaltyNFT.abi,
        //     functionName: "setDefaultRoyalty",
        //     args: [user3.account.address,"500"],
        // });
        // console.log(SETDEFAULTROYALTY)
        //设置版税
       const setRoyaltyInfo = await owner.writeContract({
            address: MyERC721C.address,
            abi: MyERC721C.abi,
            functionName: "setRoyaltyInfo",
            args: [user3.account.address,"200"],
        });
        //查询版税信息
        const royaltyInfo1=await publicClient.readContract({
            address: MyERC721C.address,
            abi: MyERC721C.abi,
            functionName: "royaltyInfo",
            args: [0,parseEther("3")],
        });
        console.log("更新后版税信息",royaltyInfo1)
        //转账nft
        const TRANSFERFROM=await user1.writeContract({
            address: MyERC721C.address,
            abi: MyERC721C.abi,
            functionName: "transferFrom",
            args: [user1.account.address,user2.account.address,0],
        });
        //查询nft的新拥有者
        const ownerOf1=await publicClient.readContract({
            address: MyERC721C.address,
            abi: MyERC721C.abi,
            functionName: "ownerOf",
            args: [0],
        });
        console.log(ownerOf1)
    });

});

测试指令

npx hardhat test ./test/xxx.ts

总结

至此,关于 ERC721、ERC721A、ERC721C 三类 NFT 标准合约的功能特性、适用场景对比,以及基于 Hardhat V3 与 Viem 框架的开发、测试、部署全流程工作 已全部完成。