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

news2024/11/19 3:43:36

系列文章目录

使用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();                 // 增加 tokenId
        uint256 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查看nft
    function 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 account
    should 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_KEY
    with 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 arguments
    args: [],
    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的铸造过程。如果你有任何疑问或建议,欢迎在评论区留言讨论🌹

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2132783.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【重学 MySQL】二十七、七种 join 连接

【重学 MySQL】二十七、七种 join 连接 union 的使用UNION 的基本用法示例UNION ALL 的用法 七种 join 连接代码实现语法格式小结 union 的使用 UNION 在 SQL 中用于合并两个或多个 SELECT 语句的结果集&#xff0c;并默认去除重复的行。如果希望包含重复行&#xff0c;可以使…

设计模式-行为型模式-访问者模式

访问者模式难以实现&#xff0c;且应用该模式可能会导致代码可读性变差&#xff0c;可维护性变差&#xff0c;除非必要&#xff0c;不建议使用&#xff1b; 1.访问者模式定义 允许在运行时将一个或多个操作应用于一组对象&#xff0c;将操作与对象结构分离&#xff1b; 访问者…

K8s1.28 部署Dashboard获取登录信息

Kubernetes Dashboard 是一个基于 Web 的用户界面&#xff0c;用户可以通过它管理和监控 Kubernetes 集群。它提供了对容器化应用程序的概览、集群资源的状态查看、以及对服务和容器的简单操作管理。 配置 Dashboard 访问的方式&#xff1a; Kubernetes 中的服务类型默认是 C…

语音识别相关概念

声音如何保存成数字信号&#xff1f; 声音是听觉对声波产生的感知&#xff0c;而声波是一种在时间和振幅上连续的模拟量&#xff0c;本质是介质的振动&#xff0c;&#xff0c;比如空气的振动。那么只需要把这个振动信号记录下来&#xff0c;并用一串数字来表达振动信号振动的…

中学生考试成绩在线查询系统

时代在发展&#xff0c;社会在进步&#xff0c;传统的成绩发布方式已经显得力不从心了。老师们&#xff0c;是时候尝试一种更高效、更安全的成绩查询方式了。 还在为如何保护学生隐私而头疼&#xff1f;还在担心成绩的公平性和准确性&#xff1f;易查分小程序将这些这些问题都将…

vue+IntersectionObserver + scrollIntoView 实现电梯导航

一、电梯导航 电梯导航也被称为锚点导航&#xff0c;当点击锚点元素时&#xff0c;页面内相应标记的元素滚动到视口。而且页面内元素滚动时相应锚点也会高亮。电梯导航一般把锚点放在左右两侧&#xff0c;类似电梯一样。 二、scrollIntoView() 介绍 scrollIntoView() 方法会…

加密软件有哪些数据防护功能?

1.文件透明加密&#xff1a;采用透明加密技术&#xff0c;自动对指定类型的敏感文件进行实时加密&#xff0c;确保数据在存储和传输过程中的安全性。 2.权限管理与访问控制&#xff1a;通过细粒度的权限管理&#xff0c;控制员工对敏感数据的访问权限&#xff0c;包括读取、修…

基于SpringBoot+Vue的预制菜平台系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的…

蓝桥杯真题——数星星

输入样例&#xff1a; 5 1 1 5 1 7 1 3 3 5 5 输出样例&#xff1a; 1 2 1 1 0 分析&#xff1a; 根据题目&#xff0c;是逐行读入数据&#xff0c;我们要求每颗星星左下方的星星数量&#xff0c;就是要迅速求一个区间内的值 于是我们联想到树状数组来解决问题 代码演示…

商业银行零售业务数智运营探索与应用

一、商业银行零售业务面临新形势 根据国家金融监督管理总局近期发布的数据,2024年一季度商业银行净息差降至1.54%,较2023年四季度的1.69%下降15个基点。在当前经营环境复杂、客户投资预期降低等多重因素的叠加作用下,商业银行经营压力日益加大。与此同时,随着数字化转型的不…

【技术调研】三维(3)-ThreeJs-几何体、材质、贴图、灯光及案例

几何体 ​ 几何体是构建模型的基础,模型=几何体+材质。threejs中已内置了很多几何体。这里不一一介绍。 BufferGeometry 是面片、线或点几何体的有效表述。包括顶点位置,面片索引、法相量、颜色值、UV 坐标和自定义缓存属性值。使用 BufferGeometry 可以有效减少向 GPU 传输…

代码随想录训练营Day3 | 链表理论基础 | 203.移除链表元素 | 707.设计链表 | 206.反转链表

今天任务&#xff1a;学习链表理论基础 链表的类型 链表的存储方式 链表的定义…

基于SpringBoot+Vue+MySQL的招聘管理系统

系统展示 用户前台界面 管理员后台界面 企业后台界面 系统背景 在当今数字化转型的大潮中&#xff0c;企业对于高效、智能化的人力资源管理系统的需求日益增长。招聘作为人力资源管理的首要环节&#xff0c;其效率与效果直接影响到企业的人才储备与竞争力。因此&#xff0c;构建…

linux 操作系统下crontab命令及使用案例介绍

linux 操作系统下crontab命令及使用案例介绍 Linux 操作系统下的 crontab 命令用于设置周期性执行的任务 crontab 命令概述 基本语法 bash crontab [-u user] file crontab [-u user] [-l | -r | -e] [-i] [-s] 主要功能 创建、编辑和管理用户的计划任务&#xff08;cron…

基于中心点的目标检测方法CenterNet—CVPR2019

Anchor Free目标检测算法—CenterNet Objects as Points论文解析 Anchor Free和Anchor Base方法的区别在于是否在检测的过程中生成大量的先验框。CenterNet直接预测物体的中心点的位置坐标。 CenterNet本质上类似于一种关键点的识别。识别的是物体的中心点位置。 有了中心点之…

【工具】前端JavaScript代码在线执行器 方便通过网页 手机测试js代码

【工具】前端JavaScript代码在线执行器 方便通过网页 手机测试js代码 自动补全js代码格式化代码色彩打印日志清空日志待补充 <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport"…

基于SpringBoot+Vue+MySQL的热门网络游戏推荐系统

系统展示 用户前台界面 管理员后台界面 系统背景 基于SpringBootVueMySQL的热门网络游戏推荐系统&#xff0c;其背景主要源于当前网络游戏市场的蓬勃发展与用户需求的日益多样化。随着互联网的普及和技术的不断进步&#xff0c;网络游戏已成为人们休闲娱乐的重要方式之一。面对…

JAVA开源项目 校园管理系统 计算机毕业设计

本文项目编号 T 026 &#xff0c;文末自助获取源码 \color{red}{T026&#xff0c;文末自助获取源码} T026&#xff0c;文末自助获取源码 目录 一、系统介绍二、演示录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究现状5.3 可行性分析 六、核心代码6.1 管…

网络安全-intigriti-0422-XSS-Challenge Write-up

目录 一、环境 二、解题 2.1看源码 一、环境 Intigriti April Challenge 二、解题 要求&#xff1a;弹出域名就算成功 2.1看源码 我们看到marge方法&#xff0c;肯定是原型链污染题目 接的是传参&#xff0c;我们可控的点在于qs.config和qs.settings&#xff0c;这两个可…

逆向工程 反编译 C# net core

索引器访问 在您的代码中&#xff0c;您试图使用 configurationRoot.get_Item("AgileConfig:appId") 来访问配置项&#xff0c;但这里存在几个问题&#xff1a; 错误的访问方法&#xff1a;在 .NET 的 IConfigurationRoot 接口中&#xff0c;没有直接名为 get_Item 的…