StoneAeon NFT持有人验证 Discord Bot 开发实录

已完成,整理后开源

\small\color{red}{写在前面:笔者资历尚浅,经验欠缺,若有缺漏错误之处,恳请读者斧正。}

实录详案

详案前置

  • 开发目的
    • 验证Discord用户是否持有StoneAeon NFT
    • 为持有StoneAeon NFT用户赋予holder身份组
  • 开发工具
    • Node.js
      • Discord.js
      • MetaMask
    • WebSocket协议
    • VScode IDE
    • Discord Bot
  • 开发环境
    • 测试环境:
      • Windows10 家庭版
      • 8C16G 256GSSD
      • Node version:v14.15.5
      • npm version:6.14.11
    • 生产环境
      • Ubuntu 18.04 TLS
      • 4C8G 240GSSD
      • Node version:v16.14.0
      • npm version:8.3.1

开发思路

  • 创建Verify Channel——点击频道内信息按钮带参跳转到外部连接进行Holder Verify
  • 外部链接提供Connect Wallet按钮,点击后呼起MetaMask,连接当前用户钱包
    • \small\color{red}{仅用于连接获取用户地址,不做签名}
  • 用户确认连接并提交验证后,将用户信息传值后端处理
    • userId(Discord)
    • userAddress(MetaMask)
      • \small\color{blue}{兼容EVM底层的地址均可使用}
  • 接收前端数据,POST请求StoneAeon NFT持有人列表
  • 查询当前用户是否在列表中
    • 存在:userIsHolder = 1
    • 不在:userIsHolder = 0

文件目录

\small\color{red}{暂不提供,开发完毕后见Github}

实录详情

服务器端

  • 创建server.js监听6060端口

server.js

const http = require('http')
const fs = require('fs')
const url = require('url')
const path = require('path')
let server = http.createServer(function (request, response) {
    //获取输入的url解析后的对象
    let pathname = url.parse(request.url, true).pathname;
    if (pathname == '/') {
        pathname = '/index.html';
    }
    //static文件夹的绝对路径
    let staticPath = path.resolve(__dirname, 'public');
    //获取资源文件绝对路径
    let filePath = path.join(staticPath, pathname);
    console.log(filePath);


    //异步读取file
    fs.readFile(filePath, function (err, data) {
        if (err) {
            console.log(err);
            // 如果找不到文件资源报错可以显示准备好的 404页面
            let errPath = path.join(staticPath, '/404.html');
            fs.readFile(errPath, (err, data404) => {
                if (err) {
                    console.log('error');
                    response.write('404 Not Found');
                    response.end();
                } else {
                    response.writeHead(404, { "Content-Type": "text/html;charset='utf-8'" });
                    response.write(data404);
                    response.end();
                }
            })
        } else {
            console.log('ok');
            response.write(data);
            response.end();
        }
    })
})
server.listen(6060)
console.log('visit http://localhost:6060')

  • 页面目录【public】
    • index.html
    • js
      • index.js
    • css
      • index.css
    • images
      • favicon.ico
      • platon.png
  • \small\color{red}{浏览器访问} Your Public IP or Domain:6060

  • 采用提交form表单的方式,将前端钱包地址以POST方法传递给服务器端
  • 服务器接收地址后,通过axios异步请求StoneAeon NFT的持有人列表,做查询操作

  • \small\color{red}{axios获取的持有人列表,address参数均为Bech32的LAT地址}
    • 因此需要将用户传值做地址格式转换,从EIP55变更为Bech32,即0x转为lat
//举例
web3.utils.decodeBech32Address('lat', 'lat1zg69v7yszg69v7yszg69v7yszg69v7y30mluqx'); 
web3.utils.toBech32Address('lat', '0x1234567890123456789012345678901234567891');

:cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow::cow:

代码重构

  • express 在 node 16.*.* 不兼容 sqlite3,会出现手动 install 的情况
  • 前后端分离
    • discord bot 为前端
    • express 构建项目后端
    • sqlite3 作为两者共用的数据库