零基础学会编写仿NFT交易市场的Dapp项目(三)

发布时间:2024年01月04日

文章整体目录

一、 Dapp、Web3的初步认识及项目整体架构

二、 NFT交易市场合约开发

三、 NFT交易市场后端开发

四、 NFT交易市场前端开发



一、初始化工程项目

  • 进入一个空的文件夹
  • 执行
npm init 或者 npm init -y

之后,自动生成一个package.json文件。

  • 按照如下目录文件创建工程文件

– abis (存放合约的ABI文件)
– files (上传图片的暂存地)
– node_modules
– .env (全局环境变量配置文件)
– app.js (后端的主页)
– package.json
– README.md
– .gitignore
–package-lock.json
– ipfs-uploader.js (接下来要编写的)
– nft-minter.js(接下来要编写的)

在这里插入图片描述

构建完后大致的目录结构如上图

二、后端所需依赖安装

package.json文件加上"type": "module",表示项目使用 ECMAScript 模块系统。

因为在传统的 CommonJS 模块系统中,Node.js 使用 require 函数加载模块。
而在 ECMAScript 模块系统中(ESM),可以使用 import 和 export 语法进行模块的导入和导出。

在将script项修改为

"scripts": {
    "start": "nodemon app.js"
  },

便于nodemon的启动


"dependencies": {
    "body-parser": "^1.19.0",
    "cors": "^2.8.5",
    "dotenv": "^16.3.1",
    "ejs": "^3.1.5",
    "ethers": "^6.8.1",
    "express": "^4.17.1",
    "express-fileupload": "^1.2.0",
    "kubo-rpc-client": "^3.0.1",
    "nodemon": "^3.0.1"
  }
  1. body-parser (^1.19.0): 用于解析 HTTP 请求体的中间件。通常用于解析 POST 请求中的表单数据等。
  2. cors (^2.8.5): 用于处理跨域资源共享 (CORS) 的中间件。CORS 是一种浏览器的安全策略,限制页面从一个源加载的资源能请求到另一个源的资源。
  3. dotenv (^16.3.1): 用于从环境变量文件中加载配置变量的工具。通常在开发环境中使用,以便在不同环境中配置应用程序的参数。
  4. ejs (^3.1.5): Embedded JavaScript 模板引擎,用于在服务器端生成 HTML 页面。可以使用动态数据创建模板。
  5. ethers (^6.8.1): 以太坊(Ethereum)的 JavaScript 客户端库。用于与以太坊区块链交互,包括创建智能合约、发送交易等。
  6. express (^4.17.1): 快速而简洁的 Node.js web 框架,用于构建 web 应用程序和 API。提供了路由、中间件等功能,使得构建 web 应用变得更加容易。
  7. express-fileupload (^1.2.0): 处理文件上传的 Express 中间件。允许用户上传文件,并提供简单的接口来处理上传的文件。
  8. kubo-rpc-client (^3.0.1): 用于创建远程过程调用(RPC)客户端的库。RPC 允许在不同的计算机或进程之间进行通信。
  9. nodemon (^3.0.1): 在开发过程中监视文件更改并自动重启 Node.js 应用程序的工具。这有助于加快开发速度,因为你无需手动重新启动服务器。

平时有需要可以去https://www.npmjs.com/多看看,有很多优秀的封装的库,能不造轮子就不要造轮子


三、ipfs-uploader.js的编写

import { create } from 'kubo-rpc-client'
import fs from 'fs';
import dotenv from 'dotenv';
dotenv.config("./.env");

// connect to ipfs daemon API server
const ipfs = create(process.env.IPFS_URL); //IPFS服务器的地址


export async function uploadFileToIPFS (filePath) {
  const file = fs.readFileSync(filePath);
  const result = await ipfs.add({ path: filePath, content: file });
  console.log(result);
  return result;
}
// uploadFileToIPFS("files/a.png"); //测试

export async function uploadJSONToIPFS (json) {
  const result = await ipfs.add(JSON.stringify(json));
  // console.log(result);
  return result;
}
// uploadJSONToIPFS({ name: "test" })//测试
  • uploadFileToIPFS是将文件上传到IPFS

result的格式如下:
{
path: ‘files’,
cid: CID(QmRoRQ9E6qqP8GKSuMZzFuimYqrk6d6S8uLVB3EtTGg4t7),
size: 797914
}

  • uploadJSONToIPFS是将JSON对象上传到IPFS

result的格式如下:
{
path: ‘QmRoRQ9E6qqP8GKSuMZzFuimYqrk6d6S8uLVB3EtTGg4t7’,
cid: CID(QmRoRQ9E6qqP8GKSuMZzFuimYqrk6d6S8uLVB3EtTGg4t7),
size: 23
}

CID--可以在浏览器通过http://ip地址:端口号/ipfs/cid找到你所上传的图片和JSON对象

四、nft-minter.js的编写

import { ethers, JsonRpcProvider } from "ethers";
import fs from 'fs';
import dotenv from 'dotenv';
dotenv.config("./.env");

export async function mint (to, uri) {
  const provider = new JsonRpcProvider(process.env.RPC);
  const signer = await provider.getSigner();
  const abi = JSON.parse(fs.readFileSync("./abis/MyNFT.json"));
  const contract = new ethers.Contract(process.env.NFT, abi, signer);
  const result = await contract.safeMint(to, uri); //mint一个nft
  console.log("mint sucessfully!!", result.hash);
  

上面是通过ABI+地址+singer构建NFT合约,并调用safeMint方法去构造一个NFT给to用户

注意!!此时NFT仅仅是存在于用户手里,并没有上传到Market去。简单来说就是与Market合约没有交互,现在去查看Market合约的信息肯定都为空。


那么要想将NFT合约给上架该怎么办呢?
这就需要用到balanceOftokenOfOwnerByIndexsafeTransferFrom三个方法

tokenOfOwnerByIndex 负责获取用户总共拥有的nft数量

tokenOfOwnerByIndex 负责去最新的tokenid

safeTransferFrom 将刚Mint出来的NFT给上架

  const marketContractAddress = process.env.MARKET;
  const total_nft = await contract.balanceOf(to);
  // 授权market使用nft
  const approveResult = await contract.connect(signer).approve(marketContractAddress, Number(total_nft) - 1); // Assuming tokenId is 1, adjust accordingly
  console.log('Approval for transfer to market contract:', approveResult.hash);
  // 获取最新的nft
  const tokenID = await contract.tokenOfOwnerByIndex(to, Number(total_nft) - 1);
  console.log('tokenID', tokenID);
  // 将nft上架
  const res = await contract.safeTransferFrom(to, marketContractAddress, Number(total_nft) - 1, "0x0000000000000000000000000000000000000000000000000001c6bf52634000", { gasLimit: 1000000 });

易错点:由于ERC721合约有两个同名的safeTransferFrom方法,在调用的时候,容易报ambiguous一类的错误,所以必须加上{ gasLimit: 1000000 }!!!

第二段代码可有可无,因为后面前端的编写,也会有上架功能,最主要的还是上架原理的理解!!!

五、app.js的编写

/**
 * express 一种web框架,用于启动服务器
 * ejs 一种语法,类似于HTML
 * 中间件:
 * nodemon 一种托管项目的工具,修改代码后就不用来回重启项目了
 * body-parser 获取用户前端页面传过来的参数 通过req.body获取
 * express-fileupload 处理上传的图片
 * kubo-rpc-client 启动服务器 
 * dotenv 配置总体环境变量
 */
import express from 'express';
import cors from 'cors'; //跨域请求资源 保证前端能给后端发送数据
import bodyParser from 'body-parser';
import fileUpload from 'express-fileupload';
import { mint } from './nft-minter.js'
import { uploadFileToIPFS, uploadJSONToIPFS } from './ipfs-uploader.js';
import dotenv from 'dotenv';
dotenv.config("./.env");

const app = express();
app.set('view engine', 'ejs'); //使用ejs模板引擎
app.use(bodyParser.urlencoded({ extended: true }));
app.use(fileUpload());
app.use(cors());

app.post('/upload', (req, res) => {

  if (!req.files || Object.keys(req.files).length === 0) {
    return res.status(400).json({ message: 'No files were uploaded.' });
  }
  
  /**这两个变量的t和d是与前端input框的name属性值相对应 */
  const title = req.body.title;
  const description = req.body.description;
  const address = req.body.address

  console.log('title=', title);
  console.log('description=', description);
  console.log('address=', address);


  const file = req.files.file;//用express-fileupload处理的
  const filename = file.name;
  const filePath = "files/" + filename;

  /**1.将用户上传的图片转移到本地存储起来,这里将文件名修改为cid,方便以后读取,后面再传递给IPFS */
  file.mv(filePath, async (err) => {
    if (err) {
      console.log(err);
      res.status(500).send("error occured"); //500后端处理失败
    }
    /**2.上传本地文件到IPFS */
    const fileResult = await uploadFileToIPFS(filePath);
    const fileCid = fileResult.cid.toString();

    const metadata = {
      title: title,
      description: description,
      image: process.env.IPFS_NET + fileCid + filePath.slice(5)
    }

    const metadataResult = await uploadJSONToIPFS(metadata);
    const metadataCid = metadataResult.cid.toString();
    console.log(metadataCid);

    // 给用户挖一个NFT
    const userAddress = address || process.env.ADDRESS;
    // console.log('当前mint的用户', userAddress);

    await mint(userAddress, process.env.IPFS_NET + metadataCid)
    res.json({
      message: "file upload sucessfully!!",
      data: metadata
    }
    )
  })
});

const PORT = process.env.PORT

//启动一个端口为`PORT`的后端服务器
app.listen(PORT, () => {
  console.log(`Example app listening on port ${PORT}!`);
});

  • metadata – 具有titledescriptionimage三个属性的JSON格式的对象
  • cors – 当前端页面与后端接口不在同一个域下时,浏览器会阻止跨域请求,保证前端能给后端发送Ajax请求,防止跨域问题
  • image: process.env.IPFS_NET + fileCid + filePath.slice(5) 写成这个格式也是为了方便前端读取ipfs中的图片

六、后端项目的启动

  • nodemon ./app.js 启动后端服务器
  • ipfs daemon 启动IPFS服务器

Finally

第一次写博客,有什么问题欢迎大家指出
有涉及到侵权请及时联系我!
接下来请浏览继续学习Dapp项目的开发(四)

文章来源:https://blog.csdn.net/qq_63902022/article/details/135327220
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。