当前位置: 首页 > news >正文

如何使用智能合约铸造 NFT —— 以 NftMarket 合约为例

系列文章目录

使用Pinata在IPFS上存储NFT图片的实践🚪
scaffold-eth-2使用详细教程🚪


文章目录

  • 系列文章目录
  • 前言
  • 一、使用到的 OpenZeppelin 库
    • 1.1. ERC721 合约
    • 1.2. ERC721URIStorage 合约
    • 1.3. Counters 合约
  • 二、编写合约代码
    • 2.1. 准备NFT元数据
    • 2.2. 实现tokenid唯一
    • 2.3. mintToken函数铸造NFT
    • 2.4. listNft函数发布NFT
  • 三、部署合约
    • 3.1. remix部署
    • 3.2. scaffold-eth-2部署
  • 四、查看NFT
  • 总结


前言

近年来,非同质化代币(NFT)在区块链领域掀起了一股热潮,成为数字艺术、收藏品和虚拟资产的代名词。NFT 的铸造是将独一无二的数字资产记录在区块链上的过程。本文将通过一个简单的智能合约示例,带你了解如何在以太坊上铸造 NFT,并解释为什么这些 NFT 即便没有被上架,也能在平台(如 OpenSea)上看到。
在这里插入图片描述


一、使用到的 OpenZeppelin 库

OpenZeppelin 库中的 ERC721ERC721URIStorageCounters 合约是开发 ERC-721 标准代币的常用组件。下面分别介绍这几个合约:

1.1. ERC721 合约

官方文档🚪
ERC721 是 OpenZeppelin 提供的标准 ERC-721 实现。ERC-721 是不可替代代币(NFT)的标准,每一个代币都有唯一的标识符。

ERC721 实现了 ERC-721 标准中的大部分功能,包括:

  • balanceOf: 查询某个地址拥有的 NFT 数量。
  • ownerOf: 查询某个 tokenId 的所有者。
  • transferFrom: 转移 NFT。
  • approvesetApprovalForAll: 授权转移 NFT。
  • safeTransferFrom: 安全转移 NFT,确保接收方有能力处理 NFT。
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";contract MyNFT is ERC721 {constructor() ERC721("MyNFT", "MNFT") {_mint(msg.sender, 1); // 铸造一个 tokenId 为 1 的 NFT 给合约创建者}
}

1.2. ERC721URIStorage 合约

官方文档🚪
ERC721URIStorage 继承自 ERC721,并添加了对 tokenURI 的存储管理。tokenURI 通常指向一个包含该 NFT 元数据的 JSON 文件(例如存储在 IPFS 上)。相比 ERC721ERC721URIStorage 允许为每个 token 存储和更新 tokenURI

  • _setTokenURI(uint256 tokenId, string memory _tokenURI): 为某个 token 设置 URI,这个URI通常是指向链外存储的资源,如IPFS上存储的JSON文件或者图像,相当于将idURI绑定起来
  • tokenURI(uint256 tokenId): 返回指定 tokenId 的URI。每个代币都有自己独立的URI,它通常指向链外存储的元数据(例如一张图片、JSON文件等)
  • _burn: 燃烧某个 token 的时候,会删除对应的 tokenURI。
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";contract MyNFTWithURI is ERC721URIStorage {constructor() ERC721("MyNFTWithURI", "MNFTU") {}function mintWithURI(address to, uint256 tokenId, string memory tokenURI) public {_mint(to, tokenId);          // 铸造 NFT_setTokenURI(tokenId, tokenURI); // 设置对应的 URI}
}

现在用ERC721URIStorage合约就可以了,不用再继承ERC721

1.3. Counters 合约

Counters 是一个简单的计数器库,用于管理增量 ID。它可以被用于 NFT 的 tokenId 自动递增,确保每个新创建的 NFT 拥有唯一的 ID。

  • increment: 增加计数器的值。
  • decrement: 减少计数器的值。
  • current: 获取当前计数器的值。
import "@openzeppelin/contracts/utils/Counters.sol";contract MyNFTWithAutoId is ERC721URIStorage {using Counters for Counters.Counter;Counters.Counter private _tokenIds;constructor() ERC721("MyNFTWithAutoId", "MNFTA") {}function mintWithAutoId(address to, string memory tokenURI) public {_tokenIds.increment();                 // 增加 tokenIduint256 newTokenId = _tokenIds.current(); // 获取新的 tokenId_mint(to, newTokenId);                 // 铸造 NFT_setTokenURI(newTokenId, tokenURI);    // 设置 URI}
}

二、编写合约代码

2.1. 准备NFT元数据

在之前的博客🚪中我讲了如何存储文件到IPFS中,现在要上传一个json文件与图片文件在IPFS中,一个NFT有自己唯一的tokenid,由于通过_setTokenURI函数将tokenidtokenURI绑定在一起,这个tokenURI指的就是json元数据文件在IPFS中的网址URL,而在元数据文件中又存在图片文件的IPFS网址URL

在这里插入图片描述
在这里插入图片描述

json文件格式如下,name为NFT的名字,description为描述,image为存储在IPFS中的图片URLattributes为特征:

{"name": "hjyToken #0","description": "this is my first nft number 0","image": "https://你自己的ipfs网关/ipfs/图片文件的cid","attributes": [{"trait_type": "language","value": "solidity"},{"trait_type": "os","value": "window"},{"trait_type": "speed","value": "fast"}]
}

2.2. 实现tokenid唯一

import "@openzeppelin/contracts/utils/Counters.sol"引入 OpenZeppelin 库中的 Counters 工具,为每个新铸造的NFT生成唯一的tokenId。
每铸造一次,就对计数器调用increment()方法自增就可以实现每个NFT的tokenid都是唯一的

2.3. mintToken函数铸造NFT

铸造 NFT 的过程就是创建一个新的代币并将其记录在区块链上。我们的智能合约通过 mintToken 函数来实现这一过程,这里的传入参数tokenURI实际上为json元数据文件的网址URL

// 铸造function mintToken(string memory tokenURI) public returns (uint256) {require(!tokenURIExists(tokenURI), "Token URI already exists");_tokenIds.increment();uint256 newTokenId = _tokenIds.current();_safeMint(msg.sender, newTokenId);_setTokenURI(newTokenId, tokenURI);_usedTokenURIs[tokenURI] = true;_idToNftItem[newTokenId] = NftItem(newTokenId, 0, msg.sender, false);return newTokenId;}

在这个过程中,我们做了以下几件事:

  • 检查 tokenURI 是否已经被使用,确保每个 NFT 都是独一无二的。
  • 调用 _safeMint 将新的 tokenId 铸造给调用者(即 msg.sender)。
  • 使用 _setTokenURI 关联每个 tokenId 和其元数据(tokenURI),这些数据通常包含了 NFT 的图片、描述等信息。
  • 记录这个 NFT 的 tokenId 和其他信息,比如创作者地址和是否上架(初始值为 false)。

2.4. listNft函数发布NFT

如果用户想将铸造好的 NFT 发布并设置出售价格

// 发布function listNft(uint256 tokenId, uint256 price) public payable {require(ownerOf(tokenId) == msg.sender, "You are not the owner of this NFT");require(price > 0, "Price must be at least 1 wei");require(msg.value == listingPrice, "Price must be equal to listing price");_idToNftItem[tokenId].price = price;_idToNftItem[tokenId].isListed = true;_listedItems.increment();emit NftItemCreated(tokenId, price, msg.sender);}

这个函数的核心是确保 NFT 的合法拥有者才可以发布,并且用户必须支付发布费用(listingPrice)。当 NFT 被成功发布时,合约会触发 NftItemCreated 事件,通知前端监听器或其他区块链观察者该 NFT 已被列为可出售。

完整的 NFT市场 合约代码如下:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "@openzeppelin/contracts/utils/Counters.sol";contract NftMarket is ERC721URIStorage {using Counters for Counters.Counter;struct NftItem {uint256 tokenId;uint256 price; // 价格address creator; // 创作者bool isListed; // 是否上架}// 上架费用uint256 public listingPrice = 0.025 ether;Counters.Counter private _listedItems; // 已发布的nft数目Counters.Counter private _tokenIds; // 已铸造的nft数目mapping(string => bool) private _usedTokenURIs;mapping(uint256 => NftItem) private _idToNftItem;event NftItemCreated(uint256 tokenId,uint256 price,address owner);constructor() ERC721("HJYToken", "HJYT") {}// 根据tokenid查看nftfunction getNftItem(uint256 tokenId) public view returns (NftItem memory) {return _idToNftItem[tokenId];}// 已上架nft数量function listedItemsCount() public view returns (uint256) {return _listedItems.current();}// tokenURI是否重复function tokenURIExists(string memory tokenURI) public view returns (bool) {return _usedTokenURIs[tokenURI] == true;}// 铸造function mintToken(string memory tokenURI) public returns (uint256) {require(!tokenURIExists(tokenURI), "Token URI already exists");_tokenIds.increment();uint256 newTokenId = _tokenIds.current();_safeMint(msg.sender, newTokenId);_setTokenURI(newTokenId, tokenURI);_usedTokenURIs[tokenURI] = true;_idToNftItem[newTokenId] = NftItem(newTokenId, 0, msg.sender, false);return newTokenId;}// 发布function listNft(uint256 tokenId, uint256 price) public payable {require(ownerOf(tokenId) == msg.sender, "You are not the owner of this NFT");require(price > 0, "Price must be at least 1 wei");require(msg.value == listingPrice, "Price must be equal to listing price");_idToNftItem[tokenId].price = price;_idToNftItem[tokenId].isListed = true;_listedItems.increment();emit NftItemCreated(tokenId, price, msg.sender);}
}

三、部署合约

有两种部署合约的方法,一个是直接用remix在线网站部署,另一个则是像hardhattruffle框架一样部署合约

3.1. remix部署

打开remix网站🚪新建一个NftMarket.sol文件,放入上面的完整代码,在第三个选项中编译完合约后到,第四个部署操作中,连接到metamask钱包的Sepolia测试网络中
在这里插入图片描述
输入json元数据文件的网址URL,提交之后就可以在OpenSea测试网网站上查看了
在这里插入图片描述

3.2. scaffold-eth-2部署

scaffold-eth详细教程🚪,新建项目基础使用我都已经在之前的博客中写好了,在放入自己的NftMarket合约之后同样和remix网站的操作几乎一样,都是调用mintToken函数

  1. 拉取项目代码并下载依赖:
git clone https://github.com/scaffold-eth/scaffold-eth-2.git
cd scaffold-eth-2
yarn install
  1. NftMarket.sol01_deploy_NftMarket.ts分别放在packages/hardhat/contracts/packages/hardhat/deploy/文件夹下
    在这里插入图片描述
// 01_deploy_NftMarket.ts
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
import { Contract } from "ethers";/*** Deploys a contract named "YourContract" using the deployer account and* constructor arguments set to the deployer address** @param hre HardhatRuntimeEnvironment object.*/
const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {/*On localhost, the deployer account is the one that comes with Hardhat, which is already funded.When deploying to live networks (e.g `yarn deploy --network sepolia`), the deployer accountshould have sufficient balance to pay for the gas fees for contract creation.You can generate a random account with `yarn generate` which will fill DEPLOYER_PRIVATE_KEYwith a random private key in the .env file (then used on hardhat.config.ts)You can run the `yarn account` command to check your balance in every network.*/const { deployer } = await hre.getNamedAccounts();const { deploy } = hre.deployments;await deploy("NftMarket", {from: deployer,// Contract constructor argumentsargs: [],log: true,// autoMine: can be passed to the deploy function to make the deployment process faster on local networks by// automatically mining the contract deployment transaction. There is no effect on live networks.autoMine: true,});// Get the deployed contract to interact with it after deploying.const yourContract = await hre.ethers.getContract<Contract>("NftMarket", deployer);
//   console.log("👋 Initial contract:", await yourContract.target);console.log("👋 Initial listedItemsCount:", await yourContract.listedItemsCount());
};export default deployYourContract;// Tags are useful if you have multiple deploy files and only want to run one of them.
// e.g. yarn deploy --tags NftMarket
deployYourContract.tags = ["YourContract"];
  1. 修改配置文件
    把hardhat部署网络从本地链改为测试网Sepolia
    在这里插入图片描述
    把前端显示的网络从hardhat改为Sepolia
    在这里插入图片描述
  2. 创建以供测试网使用的账户,转点余额到里面
    输入yarn generate生成账户,私钥保存在packages/hardhat/.env文件中
    在这里插入图片描述
    在metamask钱包中用私钥导入新账户,转1个币到账户里面(没有币可以用这个网站🚪每天领取0.1个)
    在这里插入图片描述
  3. 启动项目,分三个终端窗口输入命令
yarn chain
yarn deploy
yarn start

浏览器输入http://localhost:3000/debug,就可以看到测试的窗口,调用mintToken函数,输入json元数据文件的网址URL
在这里插入图片描述
在这里插入图片描述

四、查看NFT

为什么铸造后的 NFT 可以被 OpenSea 看到?
OpenSea 能够显示铸造完成的 NFT,主要依赖于 ERC-721 标准中的 Transfer 事件。OpenSea 监听这个事件,并自动将新的 NFT 索引到它的平台上,在执行 _safeMint 时,会触发一个 Transfer 事件,从 0x0 地址(表示合约中创建的新代币)转移到用户的地址。这是 ERC-721 标准的一部分:
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

打开metamask钱包,导入NFT
在这里插入图片描述
部署的合约地址和tokenid,代币ID为1是因为这是第一个铸造的NFT
在这里插入图片描述
打开刚刚导入的NFT,选择在OpenSea测试网中查看
在这里插入图片描述
在这里插入图片描述


总结

通过本文,我们详细介绍了NFT铸造的过程,并探讨了将铸造和上架分开的重要性。文章从基础概念入手,讲解了NFT的创建流程,特别是在以太坊网络上如何通过智能合约实现安全、透明的铸造。我们还详细说明了如何将铸造和上架这两个步骤分离,确保在NFT铸造后能够灵活决定何时上架销售,并提供了相关的代码示例和操作指南。希望这篇文章能帮助你更好地理解NFT的铸造过程。如果你有任何疑问或建议,欢迎在评论区留言讨论🌹

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 部署自己的对话大模型,使用Ollama + Qwen2 +FastGPT 实现
  • 机器学习中的数据详解:数据类型、划分、属性等
  • 蚂蚁数科,独行的170天和未来新征程
  • Python 线程池:并发编程的高效工具
  • 跟《经济学人》学英文:2024年09月07日这期 How fashion conquered television
  • 一文300字从0到1使用Postman轻松搞定文件上传测试!
  • 传承中华文脉·弘扬北疆文化“四季内蒙古演出季”区内外文艺院团交流演出活动即将启动
  • 基于SpringBoot的在线购物平台
  • VS2022中文字符输出为乱码的解决
  • Linux中的scp 如何使用
  • Android自动化2️⃣元素定位工具
  • C++系列-STL中find相关的算法
  • 【Java】多态性【主线学习笔记】
  • qt QGraphicsScene场景坐标和场景内GraphicsItem局部坐标的相互转换
  • 2024.9 学习笔记
  • 时间复杂度分析经典问题——最大子序列和
  • [js高手之路]搞清楚面向对象,必须要理解对象在创建过程中的内存表示
  • [笔记] php常见简单功能及函数
  • 【5+】跨webview多页面 触发事件(二)
  • 【comparator, comparable】小总结
  • 【Redis学习笔记】2018-06-28 redis命令源码学习1
  • 345-反转字符串中的元音字母
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • hadoop入门学习教程--DKHadoop完整安装步骤
  • javascript 总结(常用工具类的封装)
  • JavaScript创建对象的四种方式
  • js正则,这点儿就够用了
  • Objective-C 中关联引用的概念
  • Theano - 导数
  • Three.js 再探 - 写一个跳一跳极简版游戏
  • vue 个人积累(使用工具,组件)
  • win10下安装mysql5.7
  • 分布式事物理论与实践
  • 七牛云假注销小指南
  • 我的业余项目总结
  • 写给高年级小学生看的《Bash 指南》
  • 学习笔记:对象,原型和继承(1)
  • d²y/dx²; 偏导数问题 请问f1 f2是什么意思
  • JavaScript 新语法详解:Class 的私有属性与私有方法 ...
  • 新海诚画集[秒速5センチメートル:樱花抄·春]
  • ​总结MySQL 的一些知识点:MySQL 选择数据库​
  • # 计算机视觉入门
  • # 执行时间 统计mysql_一文说尽 MySQL 优化原理
  • #android不同版本废弃api,新api。
  • #LLM入门|Prompt#3.3_存储_Memory
  • (4)STL算法之比较
  • (4)通过调用hadoop的java api实现本地文件上传到hadoop文件系统上
  • (C#)Windows Shell 外壳编程系列9 - QueryInfo 扩展提示
  • (C语言)fread与fwrite详解
  • (Spark3.2.0)Spark SQL 初探: 使用大数据分析2000万KF数据
  • (附源码)springboot码头作业管理系统 毕业设计 341654
  • (四十一)大数据实战——spark的yarn模式生产环境部署
  • (推荐)叮当——中文语音对话机器人
  • ***检测工具之RKHunter AIDE
  • .DFS.