跳转至

11.NFT Market后端开发

思路

1.获取title和description和图片

2.上传图片

3.生成metadata并上传ipfs

4.mint NFT给用户

5.返回metadata给用户

初始化后端

初始化项目

npm init -y

安装expressjs和模板引擎ejs

npm install express
npm install ejs
npm install --save-dev nodemon
{
  "main": "app.js",
  "scripts": {
    "start":"nodemon app.js"
  },

启动

npm run start

前端准备

先准备简单的前端用于交互

// views/home.ejs
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>NFT Market</title>
</head>
<body>
    <h1>Upload file to IPFS</h1>
    <form action="/upload" method="POST" enctype="multipart/form-data">
        <label>Title</label>
        <input type="text" name="title">
        <br/>
        <label>Description</label>
        <input type="text" name="description">
        <br/>
        <input type="file" name="file">
        <br/>
        <input type="submit" name="Submit">
    </form>
</body>
</html>

渲染到页面

// const express=require('express')
import express from 'express';//需要在package.json增加type:module

const app=express();
app.set('view engine','ejs')

app.get('/',function (req,res){
    res.render("home")
})


const port=3002;
app.listen(port,()=>{
    console.log(`Server is running on ${port}..`)
})

image-20240103173912170

upload接口

body-parser: 解析body的中间件

express-fileupload:处理上传的文件

npm install body-parser
npm i express-fileupload

配置

import bodyParser from 'body-parser';
import fileUpload from 'express-fileupload'
...
// body处理配置
app.use(bodyParser.urlencoded({extended:true}))
app.use(fileUpload())

接口

app.post('/upload',function (req,res){
    console.log(req.body)
})

测试

image-20240103174930575

细化接口

IPFS处理图片

用户上传图片,我需要将图片先保存到本地,再上传IPFS

图片保存本地

app.post('/upload',function (req,res){
    const title=req.body.title;
    const description=req.body.description;

    const file=req.files.file;
    const filename=file.name;
    const filePath="files/"+filename;
    //用户图片转移到本地
    file.mv(filePath,(err)=>{
        if(err){
            console.log(err);
            res.status(500).send("error occured")
        }
    })
    res.json({
        message:"file upload successfully"
    })
})

IPFS引入

library: https://github.com/ipfs/js-kubo-rpc-client

安装

npm i kubo-rpc-client

启动本地ipfs节点

代码

// ipfs-uploader.js
import { create } from 'kubo-rpc-client'
import fs from 'fs'
// connect using a URL
const ipfs = create(new URL('http://127.0.0.1:5001'))

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/MetaMask.png");

测试

node ipfs-uploader.js
{
  path: 'files',
  cid: CID(QmRitRTAXnADAsKdeJXn3cFeY4ZRW2s3Td8rm4d9HZfhPm),
  size: 35499
}

访问localhost:8080/ipfs/QmRitRTAXnADAsKdeJXn3cFeY4ZRW2s3Td8rm4d9HZfhPm

image-20240103183207293

这个case中,我们要封装metadata给用户

把这两个函数export出去,到app.js调用

import { create } from 'kubo-rpc-client'
import fs from 'fs'
// connect using a URL
const ipfs = create(new URL('http://127.0.0.1:5001'))

export async function uploadFileToIPFS(filePath){
    const file=fs.readFileSync(filePath);
    const result=await ipfs.add({
        path:filePath,
        content: file
    })

    return result;
}

export async function uploadJSONToIPFS(json){
    const result =await ipfs.add(JSON.stringify(json));
    return result;
}

app.js引入

import { uploadFileToIPFS,uploadFileToIPFS } from './ipfs-uploader.js';
app.post('/upload',function (req,res){
    const title=req.body.title;
    const description=req.body.description;

    const file=req.files.file;
    const filename=file.name;
    const filePath="files/"+filename;
    //用户图片转移到本地
    file.mv(filePath,async(err)=>{
        if(err){
            console.log(err);
            res.status(500).send("error occured")
        }
        const fileResult=await uploadFileToIPFS(filePath);
        const fileCid=fileResult.cid.toString();
        const metadata={
            title:title,
            description:description,
            image:'http://127.0.0.1:8080/ipfs/'+fileCid
        }
        const metadataResult=await uploadJSONToIPFS(metadata);
        const metadataCid=metadataResult.cid.toString();
        console.log(metadataCid);
        res.json({
            message:"file upload successfully",
            data:metadata
        })
    })

})

转发NFT

ethers.js和智能合约交互

npm install ethers

部署合约,copy abi放在abis/MyNFT.json

创建nft-minter.js

import {ethers,JsonRpcProvider} from "ethers"
import fs from 'fs'
async function mint(to,uri){
    const provider=new JsonRpcProvider("http://127.0.0.1:8545");
    const signer=await provider.getSigner();
    const contractAddress="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266";
    const abi=JSON.parse(fs.readFileSync("./abis/MyNFT.json"));
    const contract=new ethers.Contract(contractAddress,abi,signer);
    const result=await contract.safeMint(to,uri);
    console.log(result);
}
mint("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266","http://github,com")

启动本地节点测试

npx hardhat node

node nft-minter.js
ContractTransactionResponse {
  provider: JsonRpcProvider {},
  blockNumber: 6,
  blockHash: '0x5d980b2c1d0601508e350f1f6597182fc07f0a30c7cbe76eb664629ae196ae4e',
  index: undefined,
  hash: '0xde57168afbf8fcd89955e4b36ec5825c5653156ded37a40c275f1dff19c3fe31',
  type: 2,
  to: '0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9',
  from: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  nonce: 4,
  gasLimit: 151230n,
  gasPrice: 1482295161n,
  maxPriorityFeePerGas: 1000000000n,
  maxFeePerGas: 1964590322n,
  data: '0xd204c45e000000000000000000000000f39fd6e51aad88f6f4ce6ab8827279cfffb9226600000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000011687474703a2f2f6769746875622c636f6d000000000000000000000000000000',
  value: 0n,
  chainId: 31337n,
  signature: Signature { r: "0xc8d9f6ff56eac75ac971929173b5263b58185668ee95b2813cace45de96ab6d4", s: "0x3f816ad824ca989c2d0d58d3148c198a7de4bc203a8ad1b9ad3e62fcbfea4769", yParity: 1, networkV: null },
  accessList: []
}

整理代码,export出去

import {ethers,JsonRpcProvider} from "ethers"
import fs from 'fs'
export async function mint(to,uri){
    const provider=new JsonRpcProvider("http://127.0.0.1:8545");
    const signer=await provider.getSigner();
    const contractAddress="0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9";
    const abi=JSON.parse(fs.readFileSync("./abis/MyNFT.json"));
    const contract=new ethers.Contract(contractAddress,abi,signer);
    const result=await contract.safeMint(to,uri);
    console.log(result);
}
// app.js
import { mint } from './nft-minter.js';
...
await mint("0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266","http://127.0.0.1:8080/ipfs/"+metadataCid)

优化

把环境变量提出出来,易于后续修改

管理环境变量的库:dotenv

npm install dotenv --save

跨域

cors

npm install cors
import cors from 'cors';
app.use(cors())

完整代码