区块链—NFT介绍及发行
NFT介绍
NFT 或非同质化代币一种是代表数字物品所有权的独特数字资产。与传统的数字文件不同,NFT 由区块链技术保护,使其具有防篡改和可验证性。这为在数字世界中建立所有权和真实性提供了一种新的方法。
NFT 依靠区块链技术来确保其所有权记录的完整性和安全性。NFT 交易历史和所有者顺序的完整记录安全地存储在区块链上,每个参与节点都有助于确保其准确性并防止篡改。
虽然 NFT 的元数据、监管链以及真实性记录都存储在区块链上,但 NFT 所代表的媒体往往不是这样。区块链上存储大型图像文件会很昂贵,因此许多人选择在链外存储NFT的媒体文件,并通过NFT的内存储的链接指向该文件。
去中心化媒体存储(如Arweave或星际文件系统(IPFS))作为替代方案,解决了中心化媒体存储服务相关的许多漏洞。
如要将 NFT 的媒体文件存储在星际文件系统 (IPFS) 上,用户可以通过 Pinata 或 Filecoin 等平台上传他们的文件。该文件将被分配一个唯一的密码散列,该散列与区块链上 NFT 的元数据相关联。这种去中心化的方法确保了媒体的可访问性和安全性,降低了与中心化存储相关的修改或删除风险。
IPFS介绍
星际文件系统(InterPlanetary File System,缩写为IPFS)是一个旨在实现文件的分布式存储、共享和持久化的网络传输协议。它是一种内容可寻址的对等超媒体分发协议。在IPFS网络中的节点构成一个分布式文件系统。
在IPFS系统中,内容会分块存放(如果内容很小就会直接存在DHT中),并分散存储在IPFS网络中的节点上(不过目前的IPFS实现,一个节点会完整保存内容的所有区块)。系统会给内容的每一个块计算哈希值,然后把所有块的哈希值拼凑起来,再计算一次哈希值,从而得到最终的哈希值。同时每个节点会维护一张DHT(分布式哈希表),包含数据块与目标节点的映射关系。
在IPFS中是通过哈希去请求文件的,它就会使用这个分布式哈希表找到文件所在的节点,取回文件根据哈希重新组合文件(同样也会验证文件)。
NFT发行合约
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";contract BasicNft is ERC721 {error BasicNft__TokenUriNotFound();mapping(uint256 tokenId => string tokenUri) private s_tokenIdToUri;uint256 private s_tokenCounter;constructor() ERC721("Dogie", "DOG") {s_tokenCounter = 0;}function mintNft(string memory tokenUri) public {s_tokenIdToUri[s_tokenCounter] = tokenUri;_safeMint(msg.sender, s_tokenCounter);s_tokenCounter = s_tokenCounter + 1;}function tokenURI(uint256 tokenId) public view override returns (string memory) {if (ownerOf(tokenId) == address(0)) {revert BasicNft__TokenUriNotFound();}return s_tokenIdToUri[tokenId];}function getTokenCounter() public view returns (uint256) {return s_tokenCounter;}
}
接下来我们做逐行讲解
error BasicNft__TokenUriNotFound();mapping(uint256 tokenId => string tokenUri) private s_tokenIdToUri;uint256 private s_tokenCounter;
上述代码定义了一个 自定义错误 BasicNft__TokenUriNotFound(),在没找到 tokenURI 时抛出。
s_tokenIdToUri:存储 tokenId → 元数据 URI(比如指向 IPFS 上的 JSON)。
s_tokenCounter:计数器,记录一共铸造了多少个 NFT,并作为 tokenId 使用。
constructor() ERC721("Dogie", "DOG") {s_tokenCounter = 0;
}
上述代码做了下面这些事
- 构造函数,初始化 NFT 的名字(Dogie)和符号(DOG)。
- OpenZeppelin 的 ERC721 构造函数会处理名字和符号。
- s_tokenCounter 初始化为 0,意思是目前还没有nft被初始化。
function mintNft(string memory tokenUri) public {s_tokenIdToUri[s_tokenCounter] = tokenUri;_safeMint(msg.sender, s_tokenCounter);s_tokenCounter = s_tokenCounter + 1;
}
- mintNft函数:用户调用它来铸造一个 NFT。
- 把 tokenUri 存进映射,跟 tokenId 对应。
- 调用 _safeMint(OpenZeppelin 提供的安全铸造函数),把 NFT 发给调用者(msg.sender)。
- tokenId 用 s_tokenCounter,然后自增。
_safeMint(address to, uint256 tokenId) 功能详解:
- 只是把一个新的 NFT(tokenId)分配给 to 地址。
- 如果 to 是一个智能合约地址,就会调用它的 onERC721Received 函数。
- 如果这个合约没实现 onERC721Received,整个交易会 revert(回滚),避免 NFT 被“卡死”在一个不能处理 NFT 的合约里。
function tokenURI(uint256 tokenId) public view override returns (string memory) {if (ownerOf(tokenId) == address(0)) {revert BasicNft__TokenUriNotFound();}return s_tokenIdToUri[tokenId];
}
- tokenURI:返回某个 tokenId 的元数据 URI。
- ownerOf(tokenId): 检查该 token 是否存在(如果是零地址表示没被 mint)。如果没找到,就报错。否则返回之前存储的 URI(比如 IPFS 链接)。
function getTokenCounter() public view returns (uint256) {return s_tokenCounter;
}
返回当前的计数器,也就是总共 mint 了多少个 NFT。
验证
我们可以在remix部署上述合约,或者使用foundary部署合约,然后在etherscan中调用mintNft函数,传入一个标准的部署在ipfs上的json文件,例如:
https://bafybeig37ioir76s7mg5oobetncojcm3c3hxasyd4rvid4jqhy4gkaheg4.ipfs.dweb.link/?filename=1-PUG.json
连接自己的钱包后执行并确认后,我们发现自己的钱包中出现了自己发行的NFT
测试合约地址:0xD3B0A4ECAA26cDEbab5810acb58E2BA5103E7fEc
引用
https://www.kraken.com/zh-cn/learn/what-are-non-fungible-tokens-nft
https://brave.com/zh/web3/what-are-nfts/
https://blog.csdn.net/inthat/article/details/106206591
https://learnblockchain.cn/2018/12/12/what-is-ipfs
https://github.com/Cyfrin/foundry-nft-cu