NFT开发指南

NFT开发指南

NFT(非同质化代币)是区块链上独特的数字资产。本章将介绍NFT的开发标准、实现方法和最佳实践。

NFT标准

1. ERC721标准

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 1. 基础ERC721合约
contract MyNFT is ERC721, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    // 基础URI
    string private _baseTokenURI;

    constructor(string memory name, string memory symbol) 
        ERC721(name, symbol) 
    {}

    // 设置基础URI
    function setBaseURI(string memory baseURI) external onlyOwner {
        _baseTokenURI = baseURI;
    }

    // 获取基础URI
    function _baseURI() internal view override returns (string memory) {
        return _baseTokenURI;
    }

    // 铸造NFT
    function mint(address to) public onlyOwner returns (uint256) {
        _tokenIds.increment();
        uint256 newTokenId = _tokenIds.current();
        _mint(to, newTokenId);
        return newTokenId;
    }
}

// 2. 带元数据的ERC721
contract NFTWithMetadata is ERC721URIStorage {
    mapping(uint256 => string) private _tokenURIs;

    function setTokenURI(uint256 tokenId, string memory uri) public {
        require(_exists(tokenId), "Token does not exist");
        _setTokenURI(tokenId, uri);
    }

    function tokenURI(uint256 tokenId) public view override
        returns (string memory)
    {
        require(_exists(tokenId), "Token does not exist");
        return _tokenURIs[tokenId];
    }
}

// 3. 可销毁的ERC721
contract BurnableNFT is ERC721Burnable {
    function burn(uint256 tokenId) public override {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "Not approved");
        _burn(tokenId);
    }
}

2. ERC1155标准

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// 1. 基础ERC1155合约
contract MyMultiToken is ERC1155 {
    // 代币名称映射
    mapping(uint256 => string) private _tokenNames;

    constructor(string memory uri) ERC1155(uri) {}

    // 批量铸造
    function mintBatch(
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) public {
        _mintBatch(to, ids, amounts, data);
    }

    // 设置代币名称
    function setTokenName(uint256 id, string memory name) public {
        _tokenNames[id] = name;
    }

    // 获取代币名称
    function tokenName(uint256 id) public view returns (string memory) {
        return _tokenNames[id];
    }
}

// 2. 游戏物品NFT
contract GameItems is ERC1155 {
    uint256 public constant SWORD = 0;
    uint256 public constant SHIELD = 1;
    uint256 public constant ARMOR = 2;

    constructor() ERC1155("https://game.example/api/item/{id}.json") {
        _mint(msg.sender, SWORD, 1000, "");
        _mint(msg.sender, SHIELD, 2000, "");
        _mint(msg.sender, ARMOR, 500, "");
    }

    function createItem(uint256 id, uint256 amount) public {
        _mint(msg.sender, id, amount, "");
    }
}

3. 元数据标准

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// 1. 链上元数据
contract OnChainMetadata is ERC721 {
    struct TokenMetadata {
        string name;
        string description;
        string image;
        mapping(string => string) attributes;
    }

    mapping(uint256 => TokenMetadata) private _metadata;

    function setMetadata(
        uint256 tokenId,
        string memory name,
        string memory description,
        string memory image
    ) public {
        require(_exists(tokenId), "Token does not exist");
        TokenMetadata storage metadata = _metadata[tokenId];
        metadata.name = name;
        metadata.description = description;
        metadata.image = image;
    }

    function setAttribute(
        uint256 tokenId,
        string memory key,
        string memory value
    ) public {
        require(_exists(tokenId), "Token does not exist");
        _metadata[tokenId].attributes[key] = value;
    }
}

// 2. IPFS元数据
contract IPFSMetadata is ERC721 {
    using Strings for uint256;

    // IPFS哈希前缀
    string private constant IPFS_PREFIX = "ipfs://";

    // 元数据哈希映射
    mapping(uint256 => string) private _tokenIPFSHashes;

    function setIPFSHash(uint256 tokenId, string memory hash) public {
        require(_exists(tokenId), "Token does not exist");
        _tokenIPFSHashes[tokenId] = hash;
    }

    function tokenURI(uint256 tokenId) public view override
        returns (string memory)
    {
        require(_exists(tokenId), "Token does not exist");
        return string(abi.encodePacked(
            IPFS_PREFIX,
            _tokenIPFSHashes[tokenId]
        ));
    }
}

NFT功能实现

1. 铸造机制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// 1. 预售铸造
contract PresaleMint is ERC721 {
    uint256 public constant PRESALE_PRICE = 0.08 ether;
    uint256 public constant PUBLIC_PRICE = 0.1 ether;

    bool public presaleActive = false;
    mapping(address => bool) public whitelisted;

    function setWhitelist(address[] calldata addresses) external onlyOwner {
        for (uint i = 0; i < addresses.length; i++) {
            whitelisted[addresses[i]] = true;
        }
    }

    function presaleMint() external payable {
        require(presaleActive, "Presale is not active");
        require(whitelisted[msg.sender], "Not whitelisted");
        require(msg.value >= PRESALE_PRICE, "Insufficient payment");

        // 铸造NFT
        _mint(msg.sender, totalSupply());
    }
}

// 2. 批量铸造
contract BatchMint is ERC721 {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

    function mintBatch(uint256 amount) external {
        require(amount > 0 && amount <= 20, "Invalid amount");

        for (uint256 i = 0; i < amount; i++) {
            _tokenIds.increment();
            _mint(msg.sender, _tokenIds.current());
        }
    }
}

// 3. 懒铸造
contract LazyMint is ERC721 {
    // 签名数据结构
    struct NFTVoucher {
        uint256 tokenId;
        uint256 price;
        string uri;
        bytes signature;
    }

    function redeem(address redeemer, NFTVoucher calldata voucher)
        public payable returns (uint256)
    {
        // 验证签名
        address signer = _verify(voucher);
        require(signer == owner(), "Invalid signature");
        require(msg.value >= voucher.price, "Insufficient payment");

        // 铸造NFT
        _mint(redeemer, voucher.tokenId);
        _setTokenURI(voucher.tokenId, voucher.uri);

        // 支付铸造费用
        payable(owner()).transfer(msg.value);

        return voucher.tokenId;
    }

    function _verify(NFTVoucher calldata voucher)
        internal view returns (address)
    {
        bytes32 digest = _hash(voucher);
        return ECDSA.recover(digest, voucher.signature);
    }

    function _hash(NFTVoucher calldata voucher)
        internal view returns (bytes32)
    {
        return _hashTypedDataV4(keccak256(abi.encode(
            keccak256("NFTVoucher(uint256 tokenId,uint256 price,string uri)"),
            voucher.tokenId,
            voucher.price,
            keccak256(bytes(voucher.uri))
        )));
    }
}

2. 交易功能

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
// 1. NFT市场
contract NFTMarket {
    struct Listing {
        address seller;
        uint256 price;
        bool active;
    }

    // 市场费率
    uint256 public constant MARKET_FEE = 250; // 2.5%

    // 商品列表
    mapping(address => mapping(uint256 => Listing)) public listings;

    // 上架NFT
    function listNFT(
        address nftContract,
        uint256 tokenId,
        uint256 price
    ) external {
        IERC721 nft = IERC721(nftContract);
        require(
            nft.ownerOf(tokenId) == msg.sender,
            "Not token owner"
        );
        require(
            nft.getApproved(tokenId) == address(this),
            "Not approved for marketplace"
        );

        listings[nftContract][tokenId] = Listing(
            msg.sender,
            price,
            true
        );
    }

    // 购买NFT
    function buyNFT(address nftContract, uint256 tokenId) external payable {
        Listing storage listing = listings[nftContract][tokenId];
        require(listing.active, "Not listed");
        require(msg.value >= listing.price, "Insufficient payment");

        // 计算费用
        uint256 marketFee = (msg.value * MARKET_FEE) / 10000;
        uint256 sellerProceeds = msg.value - marketFee;

        // 转移NFT
        IERC721(nftContract).safeTransferFrom(
            listing.seller,
            msg.sender,
            tokenId
        );

        // 支付费用
        payable(listing.seller).transfer(sellerProceeds);
        payable(owner()).transfer(marketFee);

        // 删除listing
        delete listings[nftContract][tokenId];
    }
}

// 2. NFT拍卖
contract NFTAuction {
    struct Auction {
        address seller;
        uint256 startPrice;
        uint256 endTime;
        address highestBidder;
        uint256 highestBid;
        bool ended;
    }

    mapping(address => mapping(uint256 => Auction)) public auctions;

    // 创建拍卖
    function createAuction(
        address nftContract,
        uint256 tokenId,
        uint256 startPrice,
        uint256 duration
    ) external {
        IERC721 nft = IERC721(nftContract);
        require(
            nft.ownerOf(tokenId) == msg.sender,
            "Not token owner"
        );
        require(
            nft.getApproved(tokenId) == address(this),
            "Not approved for auction"
        );

        auctions[nftContract][tokenId] = Auction(
            msg.sender,
            startPrice,
            block.timestamp + duration,
            address(0),
            0,
            false
        );
    }

    // 出价
    function bid(address nftContract, uint256 tokenId) external payable {
        Auction storage auction = auctions[nftContract][tokenId];
        require(!auction.ended, "Auction ended");
        require(block.timestamp < auction.endTime, "Auction expired");
        require(
            msg.value > auction.highestBid,
            "Bid too low"
        );

        // 退还之前的最高出价
        if (auction.highestBidder != address(0)) {
            payable(auction.highestBidder).transfer(auction.highestBid);
        }

        // 更新最高出价
        auction.highestBidder = msg.sender;
        auction.highestBid = msg.value;
    }

    // 结束拍卖
    function endAuction(address nftContract, uint256 tokenId) external {
        Auction storage auction = auctions[nftContract][tokenId];
        require(!auction.ended, "Auction already ended");
        require(
            block.timestamp >= auction.endTime,
            "Auction not yet ended"
        );

        auction.ended = true;

        // 转移NFT
        IERC721(nftContract).safeTransferFrom(
            auction.seller,
            auction.highestBidder,
            tokenId
        );

        // 支付卖家
        payable(auction.seller).transfer(auction.highestBid);
    }
}

3. 版税机制

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 1. 版税合约
contract RoyaltyNFT is ERC721 {
    // 版税信息
    struct RoyaltyInfo {
        address recipient;
        uint96 royaltyFraction; // 以10000为基数
    }

    mapping(uint256 => RoyaltyInfo) private _royalties;

    // 设置版税
    function setTokenRoyalty(
        uint256 tokenId,
        address recipient,
        uint96 fraction
    ) public {
        require(fraction <= 10000, "Invalid royalty");
        _royalties[tokenId] = RoyaltyInfo(recipient, fraction);
    }

    // 计算版税
    function royaltyInfo(uint256 tokenId, uint256 salePrice)
        external view returns (address receiver, uint256 royaltyAmount)
    {
        RoyaltyInfo memory royalty = _royalties[tokenId];
        return (
            royalty.recipient,
            (salePrice * royalty.royaltyFraction) / 10000
        );
    }
}

// 2. 二级市场版税
contract SecondaryMarket {
    function executeSecondaryTrade(
        address nftContract,
        uint256 tokenId,
        uint256 price
    ) external payable {
        require(msg.value >= price, "Insufficient payment");

        // 获取版税信息
        (address royaltyReceiver, uint256 royaltyAmount) = 
            IERC2981(nftContract).royaltyInfo(tokenId, price);

        // 计算卖家收益
        uint256 sellerProceeds = price - royaltyAmount;

        // 转移NFT
        IERC721(nftContract).safeTransferFrom(
            msg.sender,
            address(this),
            tokenId
        );

        // 支付版税
        if (royaltyAmount > 0) {
            payable(royaltyReceiver).transfer(royaltyAmount);
        }

        // 支付卖家
        payable(msg.sender).transfer(sellerProceeds);
    }
}

NFT应用开发

1. 游戏NFT

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
// 1. 游戏角色NFT
contract GameCharacter is ERC721 {
    struct Character {
        uint256 level;
        uint256 experience;
        uint256 strength;
        uint256 agility;
        uint256 intelligence;
    }

    mapping(uint256 => Character) public characters;

    // 创建角色
    function createCharacter() external {
        uint256 tokenId = totalSupply() + 1;
        _mint(msg.sender, tokenId);

        characters[tokenId] = Character(
            1, // 初始等级
            0, // 初始经验
            10, // 初始力量
            10, // 初始敏捷
            10  // 初始智力
        );
    }

    // 升级角色
    function levelUp(uint256 tokenId) external {
        require(ownerOf(tokenId) == msg.sender, "Not owner");
        Character storage character = characters[tokenId];
        require(
            character.experience >= levelUpRequirement(character.level),
            "Insufficient experience"
        );

        character.level += 1;
        character.strength += 2;
        character.agility += 2;
        character.intelligence += 2;
    }

    // 获取升级所需经验
    function levelUpRequirement(uint256 level) public pure returns (uint256) {
        return level * 1000;
    }
}

// 2. 游戏装备NFT
contract GameEquipment is ERC1155 {
    struct Equipment {
        string name;
        uint256 level;
        uint256 attack;
        uint256 defense;
        bool isEquipped;
    }

    mapping(uint256 => Equipment) public equipment;

    // 创建装备
    function createEquipment(
        string memory name,
        uint256 attack,
        uint256 defense
    ) external returns (uint256) {
        uint256 tokenId = totalSupply() + 1;
        _mint(msg.sender, tokenId, 1, "");

        equipment[tokenId] = Equipment(
            name,
            1,
            attack,
            defense,
            false
        );

        return tokenId;
    }

    // 强化装备
    function enhanceEquipment(uint256 tokenId) external {
        require(balanceOf(msg.sender, tokenId) > 0, "Not owner");
        Equipment storage item = equipment[tokenId];

        item.level += 1;
        item.attack = item.attack * 110 / 100;  // 增加10%攻击力
        item.defense = item.defense * 110 / 100; // 增加10%防御力
    }
}

2. 艺术品NFT

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// 1. 艺术品NFT
contract ArtNFT is ERC721URIStorage {
    struct Artwork {
        string title;
        string artist;
        uint256 creationDate;
        string medium;
        string[] tags;
    }

    mapping(uint256 => Artwork) public artworks;

    // 创建艺术品
    function createArtwork(
        string memory title,
        string memory artist,
        string memory medium,
        string[] memory tags,
        string memory tokenURI
    ) external returns (uint256) {
        uint256 tokenId = totalSupply() + 1;
        _mint(msg.sender, tokenId);
        _setTokenURI(tokenId, tokenURI);

        artworks[tokenId] = Artwork(
            title,
            artist,
            block.timestamp,
            medium,
            tags
        );

        return tokenId;
    }

    // 获取艺术品信息
    function getArtwork(uint256 tokenId)
        external view returns (Artwork memory)
    {
        require(_exists(tokenId), "Token does not exist");
        return artworks[tokenId];
    }
}

// 2. 限量版NFT
contract LimitedEditionNFT is ERC721 {
    using Counters for Counters.Counter;

    uint256 public constant MAX_SUPPLY = 1000;
    Counters.Counter private _tokenIds;

    mapping(uint256 => uint256) public editionNumbers;

    // 铸造限量版NFT
    function mint() external returns (uint256) {
        require(_tokenIds.current() < MAX_SUPPLY, "Max supply reached");

        _tokenIds.increment();
        uint256 tokenId = _tokenIds.current();
        _mint(msg.sender, tokenId);

        editionNumbers[tokenId] = tokenId;

        return tokenId;
    }

    // 获取版本号
    function getEditionNumber(uint256 tokenId)
        external view returns (uint256)
    {
        require(_exists(tokenId), "Token does not exist");
        return editionNumbers[tokenId];
    }
}

3. 身份凭证NFT

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// 1. 身份凭证NFT
contract IdentityNFT is ERC721 {
    struct Identity {
        string name;
        uint256 birthDate;
        string nationality;
        bytes32 documentHash;
        bool verified;
    }

    mapping(uint256 => Identity) public identities;
    mapping(address => uint256) public userIdentities;

    // 创建身份凭证
    function createIdentity(
        string memory name,
        uint256 birthDate,
        string memory nationality,
        bytes32 documentHash
    ) external returns (uint256) {
        require(
            userIdentities[msg.sender] == 0,
            "Identity already exists"
        );

        uint256 tokenId = totalSupply() + 1;
        _mint(msg.sender, tokenId);

        identities[tokenId] = Identity(
            name,
            birthDate,
            nationality,
            documentHash,
            false
        );

        userIdentities[msg.sender] = tokenId;
        return tokenId;
    }

    // 验证身份
    function verifyIdentity(uint256 tokenId) external onlyVerifier {
        require(_exists(tokenId), "Token does not exist");
        identities[tokenId].verified = true;
    }
}

// 2. 会员卡NFT
contract MembershipNFT is ERC721 {
    struct Membership {
        uint256 level;
        uint256 joinDate;
        uint256 expiryDate;
        uint256 points;
    }

    mapping(uint256 => Membership) public memberships;

    // 创建会员卡
    function createMembership(address to) external returns (uint256) {
        uint256 tokenId = totalSupply() + 1;
        _mint(to, tokenId);

        memberships[tokenId] = Membership(
            1, // 初始等级
            block.timestamp,
            block.timestamp + 365 days,
            0  // 初始积分
        );

        return tokenId;
    }

    // 更新会员等级
    function updateLevel(uint256 tokenId, uint256 newLevel) external {
        require(_exists(tokenId), "Token does not exist");
        memberships[tokenId].level = newLevel;
    }

    // 添加积分
    function addPoints(uint256 tokenId, uint256 points) external {
        require(_exists(tokenId), "Token does not exist");
        memberships[tokenId].points += points;
    }

    // 续期会员
    function renewMembership(uint256 tokenId) external {
        require(_exists(tokenId), "Token does not exist");
        memberships[tokenId].expiryDate += 365 days;
    }
}

练习题

  1. 实现一个带版税的NFT合约:
1
2
3
4
// 练习:实现版税NFT
contract RoyaltyNFT {
    // 你的代码
}
  1. 创建游戏装备NFT系统:
1
2
3
4
// 练习:实现游戏装备系统
contract GameItems {
    // 你的代码
}
  1. 实现NFT市场合约:
1
2
3
4
// 练习:实现NFT市场
contract NFTMarketplace {
    // 你的代码
}
  1. 创建会员卡NFT系统:
1
2
3
4
// 练习:实现会员卡系统
contract MembershipSystem {
    // 你的代码
}
  1. 实现艺术品NFT合约:
1
2
3
4
// 练习:实现艺术品NFT
contract ArtworkNFT {
    // 你的代码
}

参考资源

  1. OpenZeppelin NFT文档
  2. ERC721标准
  3. ERC1155标准
  4. NFT开发最佳实践
  5. IPFS文档