> For the complete documentation index, see [llms.txt](https://yeasy.gitbook.io/blockchain_guide/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yeasy.gitbook.io/blockchain_guide/12_web3/nft_and_collectibles.md).

# NFT 与数字收藏品

## 概述

NFT（非同质化代币，Non-Fungible Token）是指具有唯一性和不可互换性的链上代币。与 ERC-20 代币（每个代币完全相同）不同，每个 NFT 都有独特的标识和属性，可代表某个链上凭证及其约定权益，但不自动等同于底层作品、实物资产或知识产权的所有权。

## 1. 与 ERC-20 的本质区别

| 特性     | ERC-20 代币 | NFT (ERC-721)     |
| ------ | --------- | ----------------- |
| 同质性    | 完全同质，可互换  | 非同质，每个唯一          |
| 转账     | 任意数量转账    | 一次一个，不可分割         |
| 识别     | 通过数量识别    | 通过 Token ID 识别    |
| 应用场景   | 货币、支付     | 艺术品、收藏、游戏资产       |
| 智能合约标准 | ERC-20    | ERC-721, ERC-1155 |

## 2. NFT 的技术标准

### 2.1 ERC-721 标准

最基础的 NFT 标准，每个代币通过唯一的 tokenId 识别。

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

interface IERC165 {
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

// ERC-721 核心接口。name/symbol/tokenURI 属于 IERC721Metadata 扩展。
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);

    function balanceOf(address owner) external view returns (uint256 balance);
    function ownerOf(uint256 tokenId) external view returns (address owner);
    function approve(address to, uint256 tokenId) external;
    function getApproved(uint256 tokenId) external view returns (address operator);
    function setApprovalForAll(address operator, bool approved) external;
    function isApprovedForAll(address owner, address operator) external view returns (bool);
    function transferFrom(address from, address to, uint256 tokenId) external;
    function safeTransferFrom(address from, address to, uint256 tokenId) external;
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
}

interface IERC721Metadata is IERC721 {
    function name() external view returns (string memory);
    function symbol() external view returns (string memory);
    function tokenURI(uint256 tokenId) external view returns (string memory);
}
```

**安全最小实现示例**：

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

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract SimpleCollectible is ERC721, Ownable {
    uint256 private nextTokenId = 1;
    string private baseTokenURI;

    constructor(string memory baseURI_) ERC721("Simple Collectible", "SCOL") Ownable(msg.sender) {
        baseTokenURI = baseURI_;
    }

    function safeMint(address to) external onlyOwner returns (uint256 tokenId) {
        tokenId = nextTokenId++;
        _safeMint(to, tokenId);
    }

    function _baseURI() internal view override returns (string memory) {
        return baseTokenURI;
    }
}
```

### 2.2 ERC-1155 标准

支持同时发行可交换和不可交换代币，更灵活且 Gas 高效。

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

// ERC-1155 核心接口节选；实际开发应直接使用 OpenZeppelin 的 IERC1155/ERC1155。
interface IERC1155 {
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);
    event TransferBatch(address indexed operator, address indexed from, address indexed to, uint256[] ids, uint256[] values);
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    // 查询余额
    function balanceOf(address account, uint256 id) external view returns (uint256);

    // 批量查询余额
    function balanceOfBatch(address[] calldata accounts, uint256[] calldata ids)
        external view returns (uint256[] memory);

    // 转账
    function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;

    // 批量转账
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external;

    // 批量授权
    function setApprovalForAll(address operator, bool approved) external;

    // 检查授权
    function isApprovedForAll(address account, address operator) external view returns (bool);
}
```

**ERC-1155 的优势**：

* 可同时管理多种资产（纯 NFT、可交换代币、半可交换代币）
* 单次交易可处理多个 ID，Gas 费用更低
* 适合游戏中各类资源的管理（武器、皮肤、货币等）

## 3. NFT 元数据标准

NFT 的真实内容（图片、视频、文本）通常不存储在区块链上，而是存储在链外存储（如 IPFS），智能合约仅保存指向这些资源的 URI。

### 3.1 标准元数据格式

ERC-721 使用 JSON 格式描述元数据：

```json
{
  "name": "Bored Ape #1",
  "description": "A rare digital ape from the Bored Ape Yacht Club collection",
  "image": "ipfs://QmRZeF3mAL4VY5N8w8Wz8H6k1m2Zx4c7v8b9d0e1f2g3h4i",
  "attributes": [
    {
      "trait_type": "Background",
      "value": "Blue"
    },
    {
      "trait_type": "Fur",
      "value": "Tan"
    },
    {
      "trait_type": "Eyes",
      "value": "Blue Beams"
    },
    {
      "trait_type": "Mouth",
      "value": "Bored"
    },
    {
      "trait_type": "Rarity Score",
      "value": 8.5
    }
  ]
}
```

### 3.2 使用链外存储保存元数据

```javascript
// 伪代码：使用当前存储服务的官方 SDK 或 HTTP API。
// NFT.Storage Classic 的免费上传/API/SDK 已停止接受新上传，不应作为新项目模板。
async function uploadMetadata(storage, imageFile) {
  const imageCid = await storage.uploadFile(imageFile);

  const metadata = {
    name: "My NFT",
    description: "A beautiful digital art",
    image: `ipfs://${imageCid}`,
    attributes: [
      { trait_type: "Color", value: "Blue" },
      { trait_type: "Rarity", value: "Rare" }
    ]
  };

  const metadataCid = await storage.uploadJson(metadata);
  return `ipfs://${metadataCid}`;
}
```

关键点是把合约中的 `tokenURI` 指向内容寻址的元数据 URI，并为生产项目准备多个存储/固定副本，避免单一网关或单一服务影响可用性。

## 4. NFT 应用场景

### 4.1 数字艺术品与收藏品

OpenSea、Blur 等平台使开发者和艺术家无需编写代码即可创建和交易 NFT。

**特点**：

* 高度个性化和稀有性
* 社区驱动的价值发现
* 版税机制（艺术家可在每次转售中获得分成）

**代码示例 - 声明版税的 NFT**：

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

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {ERC721Royalty} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Royalty.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract RoyaltyCollectible is ERC721Royalty, Ownable {
    uint256 private nextTokenId = 1;

    constructor(address royaltyReceiver)
        ERC721("Royalty Collectible", "RCL")
        Ownable(msg.sender)
    {
        // 500 = 5%，OpenZeppelin ERC2981 默认分母为 10000。
        _setDefaultRoyalty(royaltyReceiver, 500);
    }

    function safeMint(address to) external onlyOwner returns (uint256 tokenId) {
        tokenId = nextTokenId++;
        _safeMint(to, tokenId);
    }
}
```

ERC-2981 只负责让市场通过 `royaltyInfo(tokenId, salePrice)` 查询收款地址和金额；NFT 合约不应暴露自定义销售回调或在合约内直接转账。是否支付、如何支付应由支持该标准的市场在交易结算中处理。

### 4.2 游戏资产

链上游戏（GameFi）常用 NFT 表示游戏内物品，玩家可以在链上持有相应凭证；但具体使用权、转让权和游戏内效力仍受合约、游戏规则和服务条款约束，不能简单等同于对全部资产权益的无条件所有权。

**示例架构**：

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

import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract GameAsset is ERC1155, Ownable {
    enum AssetType { Weapon, Armor, Consumable }

    uint256 private nextAssetId = 1;
    mapping(uint256 => AssetType) public assetTypes;
    mapping(uint256 => uint256) public assetStats;  // 攻击力、防御力等

    constructor(string memory baseURI) ERC1155(baseURI) Ownable(msg.sender) {}

    function mintWeapon(address to, uint256 damage) external onlyOwner returns (uint256 assetId) {
        assetId = nextAssetId++;
        assetTypes[assetId] = AssetType.Weapon;
        assetStats[assetId] = damage;

        _mint(to, assetId, 1, "");
    }

    function upgradeAsset(uint256 assetId, uint256 increaseStats) public {
        require(balanceOf(msg.sender, assetId) > 0, "Not owner");
        assetStats[assetId] += increaseStats;
    }
}
```

### 4.3 身份与凭证

可验证的链上身份、学位证书、会员证等。

**优势**：

* 防伪性强（由区块链保证）
* 可验证性（任何人可查证真伪）
* 可转移性（持有者可出售或转赠）

### 4.4 域名和地址

ENS（Ethereum Name Service）将复杂的钱包地址映射到易记的域名。

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

import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

contract SimpleDomainNFT is ERC721, Ownable {
    uint256 private nextTokenId = 1;
    mapping(string => uint256) public domainToTokenId;
    mapping(uint256 => string) public tokenDomains;

    event DomainRegistered(string indexed domain, address indexed owner);

    constructor() ERC721("Simple Domain", "SDOM") Ownable(msg.sender) {}

    function registerDomain(string memory domain, address to) external onlyOwner returns (uint256 tokenId) {
        require(bytes(domain).length > 0, "Invalid domain");
        require(domainToTokenId[domain] == 0, "Domain taken");

        tokenId = nextTokenId++;
        domainToTokenId[domain] = tokenId;
        tokenDomains[tokenId] = domain;
        _safeMint(to, tokenId);

        emit DomainRegistered(domain, to);
    }

    function resolveDomain(string memory domain) public view returns (address) {
        uint256 tokenId = domainToTokenId[domain];
        require(tokenId != 0, "Domain not registered");
        return ownerOf(tokenId);
    }
}
```

## 5. NFT 市场合约

二级市场允许持有者之间交易 NFT。

**简单的 NFT 市场实现**：

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

import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract NFTMarketplace is ReentrancyGuard {
    struct Listing {
        address seller;
        uint256 price;
    }

    IERC721 public immutable nft;
    mapping(uint256 => Listing) public listings;
    mapping(address => uint256) public proceeds;

    event Listed(uint256 indexed tokenId, address indexed seller, uint256 price);
    event Sold(uint256 indexed tokenId, address indexed seller, address indexed buyer, uint256 price);
    event Unlisted(uint256 indexed tokenId);
    event ProceedsWithdrawn(address indexed seller, uint256 amount);

    constructor(IERC721 nft_) {
        nft = nft_;
    }

    // 卖家上架 NFT
    function list(uint256 tokenId, uint256 price) external {
        require(price > 0, "Invalid price");
        require(nft.ownerOf(tokenId) == msg.sender, "Not owner");
        require(
            nft.getApproved(tokenId) == address(this) ||
                nft.isApprovedForAll(msg.sender, address(this)),
            "Marketplace not approved"
        );

        listings[tokenId] = Listing({
            seller: msg.sender,
            price: price
        });

        emit Listed(tokenId, msg.sender, price);
    }

    // 买家购买 NFT：检查 -> 更新状态 -> 外部交互
    function buy(uint256 tokenId) external payable nonReentrant {
        Listing memory listing = listings[tokenId];
        require(listing.seller != address(0), "Not listed");
        require(msg.value == listing.price, "Wrong payment");

        delete listings[tokenId];
        proceeds[listing.seller] += msg.value;

        nft.safeTransferFrom(listing.seller, msg.sender, tokenId);

        emit Sold(tokenId, listing.seller, msg.sender, listing.price);
    }

    // 卖家撤销上架
    function unlist(uint256 tokenId) external {
        require(listings[tokenId].seller == msg.sender, "Not seller");
        delete listings[tokenId];
        emit Unlisted(tokenId);
    }

    // 提款模式避免购买流程中直接向卖家转账
    function withdrawProceeds() external nonReentrant {
        uint256 amount = proceeds[msg.sender];
        require(amount > 0, "No proceeds");

        proceeds[msg.sender] = 0;
        (bool ok, ) = payable(msg.sender).call{value: amount}("");
        require(ok, "Withdraw failed");

        emit ProceedsWithdrawn(msg.sender, amount);
    }
}
```

## 6. NFT 的风险和挑战

**技术风险**：

* 智能合约漏洞可导致资产被盗
* 元数据链接失效（IPFS 节点关闭，中心化服务器关闭）
* 跨链桥接风险（跨链 NFT 时可能丢失）

**市场风险**：

* 价格波动剧烈，投机成分大
* 洗盘行为（人为抬高价格）
* 地板价格（底价）可能长期低迷

**法律风险**：

* 知识产权问题（NFT 不等于版权）
* 监管不确定性
* 税收处理复杂

## 7. 最佳实践

1. **使用经审计的 ERC-721 标准库**

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

   import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";

   contract MyNFT is ERC721 {
       constructor() ERC721("MyNFT", "MNFT") {}
   }
   ```
2. **元数据持久化**：使用内容寻址 URI，并准备多节点 pinning、Filecoin、Arweave 等备份，避免依赖单一网关或单一服务
3. **版税支持**：实现 ERC-2981 标准以获得市场支持
4. **提供升级机制**：使用代理模式以应对安全发现

这样深入理解 NFT 技术和应用，将为进一步的区块链应用开发打下坚实基础。


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://yeasy.gitbook.io/blockchain_guide/12_web3/nft_and_collectibles.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
