二、 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"
}
- body-parser (^1.19.0): 用于解析 HTTP 请求体的中间件。通常用于解析 POST 请求中的表单数据等。
- cors (^2.8.5): 用于处理跨域资源共享 (CORS) 的中间件。CORS 是一种浏览器的安全策略,限制页面从一个源加载的资源能请求到另一个源的资源。
- dotenv (^16.3.1): 用于从环境变量文件中加载配置变量的工具。通常在开发环境中使用,以便在不同环境中配置应用程序的参数。
- ejs (^3.1.5): Embedded JavaScript 模板引擎,用于在服务器端生成 HTML 页面。可以使用动态数据创建模板。
- ethers (^6.8.1): 以太坊(Ethereum)的 JavaScript 客户端库。用于与以太坊区块链交互,包括创建智能合约、发送交易等。
- express (^4.17.1): 快速而简洁的 Node.js web 框架,用于构建 web 应用程序和 API。提供了路由、中间件等功能,使得构建 web 应用变得更加容易。
- express-fileupload (^1.2.0): 处理文件上传的 Express 中间件。允许用户上传文件,并提供简单的接口来处理上传的文件。
- kubo-rpc-client (^3.0.1): 用于创建远程过程调用(RPC)客户端的库。RPC 允许在不同的计算机或进程之间进行通信。
- nodemon (^3.0.1): 在开发过程中监视文件更改并自动重启 Node.js 应用程序的工具。这有助于加快开发速度,因为你无需手动重新启动服务器。
平时有需要可以去https://www.npmjs.com/多看看,有很多优秀的封装的库,能不造轮子就不要造轮子
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
是将文件上传到IPFSresult的格式如下:
{
path: ‘files’,
cid: CID(QmRoRQ9E6qqP8GKSuMZzFuimYqrk6d6S8uLVB3EtTGg4t7),
size: 797914
}
uploadJSONToIPFS
是将JSON对象上传到IPFSresult的格式如下:
{
path: ‘QmRoRQ9E6qqP8GKSuMZzFuimYqrk6d6S8uLVB3EtTGg4t7’,
cid: CID(QmRoRQ9E6qqP8GKSuMZzFuimYqrk6d6S8uLVB3EtTGg4t7),
size: 23
}
CID--可以在浏览器通过http://ip地址:端口号/ipfs/cid找到你所上传的图片和JSON对象
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合约给上架该怎么办呢?
这就需要用到balanceOf
、tokenOfOwnerByIndex
和safeTransferFrom
三个方法
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 }!!!
第二段代码可有可无,因为后面前端的编写,也会有上架功能,最主要的还是上架原理的理解!!!
/**
* 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}!`);
});
title
、description
和image
三个属性的JSON格式的对象image: process.env.IPFS_NET + fileCid + filePath.slice(5)
写成这个格式也是为了方便前端读取ipfs中的图片nodemon ./app.js
启动后端服务器ipfs daemon
启动IPFS服务器第一次写博客,有什么问题欢迎大家指出
有涉及到侵权请及时联系我!
接下来请浏览继续学习Dapp项目的开发(四)