创建一个 Moralis Dapp

1,064 阅读8分钟

教程地址:www.youtube.com/watch?v=gAT…

Moralis 官网:admin.moralis.io/dapps

文档:v1docs.moralis.io/introductio…

npm:www.npmjs.com/package/mor…

!!!修改教程源码(V1)

源码地址: https://github.com/ttwo22/Moralis-Web.git

修改添加功能:

  1. 连接/注销钱包
  2. 查询合约数据
  3. 动态添加查询
  4. 设置数据在本地Session
  5. 修改路由
  6. 修改获取合约数据的表格

遇到的坑:

  1. 版本问题,V1版本和V2很多API不兼容,环境bug异常频出
  2. V1依赖导入好几种方法,个别api失效
  3. V2只有nextjs的教学,这个源码是v1的
  4. moralis的服务器数据库增删改查调用api也有环境问题,服务器时不时访问不稳定
  5. v1个别api调用需要安装很多缺失依赖,详细看官方文档www.npmjs.com/package/mor…

一、教程笔记(V1)

设置 Moralis

进入Moralis官网免费注册一个用户,进入仪表盘创建一个新的Dapp

设置名称创建项目成功

设置 API 速率限制

cloud.js里设置速率限制:v1docs.moralis.io/moralis-dap…

//限制用户可以向 web3Api 发出的请求数
Moralis.settings.setAPIRateLimit({
    anonymous:10, authenticated:20, windowMs:60000
  })

这会将未经身份验证的用户的请求数限制为每分钟 10 个(60000 毫秒),对于经过身份验证的用户,则限制为每分钟 20 个。

这里教程设置为anonymous:1000, authenticated:2000, windowMs:60000

在 IDE 中设置 Cloud Functions

您可以使用您最喜欢的 IDE 并通过使用moralis-admin-cli npm 包自动同步代码

  1. 首先全局安装moralis admin cli包

npm i -g moralis-admin-cli

  1. 执行 watch-cloud-file 并更改为您的云文件的正确路径
moralis-admin-cli watch-cloud-folder --moralisApiKey VKMD0iAFRrsiNQ4 --moralisApiSecret fU7nbkBx40xxBsf --moralisSubdomain 9ctqwh3fgrja.usemoralis.com --autoSave 1 --moralisCloudfolder D:\Project\Moralis\Test\cf

//替换一下为你自己的路径
D:\Project\Moralis\Test\cf

成功

点击头像返回旧版界面

点击test右边箭头拉开点击Cloud Functions ,查看文件是否同步

NFT 传输数据处理

切换到stats文件夹,使用命令行npm init 初始化package.json,新建stats.js

cd stats
npm init 
npm i moralis 
npm i fs

配置文件

到dapp里复制Dapp凭证,配置参数。

调用Web3API进行查询

/nft/{address}/transfersadmin.moralis.io/web3apis

获取与给定参数匹配的令牌传输。 返回 NFT 转账的集合

代码

const Moralis = require("moralis-v1/node");
const fs = require("fs");

const serverUrl = "https://9ctqwh3fgrja.usemoralis.com:2053/server";
const appId = "XhOTGb28N5lTeowwqTmJiRAJdtQFP8OJw3Mhj6yi";
const contractAddress = "0x23581767a106ae21c074b2276D25e5C3e136a68b"; //Moralis

async function getAllOwners(){
    await Moralis.start({serverUrl:serverUrl,appId:appId});
    let cursor = null;
    let owners={};
    let res;
    let accountedTokens = [];

    do{
        const response = await Moralis.Web3API.token.getContractNFTTransfers({
            address:contractAddress,
            chain:"eth",
            limit:100,
            cursor:cursor,
        });
        res = response;
        console.log(
            `Got page ${response.page} of ${Math.ceil(response.total/response.page_size)},${response.total} total`
        )

        for(const transfer of res.result){
            if(!owners[transfer.to_address] && !accountedTokens.includes(transfer.token_id)){
                owners[transfer.to_address]={
                    address:transfer.to_address,
                    amount:Number(transfer.amount),
                    tokenId:[transfer.token_id],
                    prices:[Number(transfer.value)],
                    dates:[transfer.block_timestamp],
                }
                accountedTokens.push(transfer.token_id);
            }else if(!accountedTokens.includes(transfer.token_id)){
                owners[transfer.to_address].amount++;
                owners[transfer.to_address].tokenId.push(transfer.token_id);
                owners[transfer.to_address].prices.push(Number(transfer.value));
                owners[transfer.to_address].dates.push(transfer.block_timestamp);
                accountedTokens.push(transfer.token_id);
            }
        }

        cursor = res.cursor;

    }while(cursor != "" && cursor != null);

    const jsonContentOwners = JSON.stringify(owners);
    fs.writeFile("moonbirdsOwners.json",jsonContentOwners,"utf8",function(err){
        if(err){
            console.log("An error occured while writing JSON Object to File");
            return console.log(err);
        }
        console.log("JSON file has been saved");
    })
}

getAllOwners();

执行JS文件

node .\stats.js

运行报错的话,视频教程代码有几个bug

//版本问题,要下载旧版的moralis-v1使用
const Moralis = require("moralis-v1/node");
//合约地址不是那个Master Key: 
//是你自己铸造的,可以用视频作者,有数据就行

//简单测试代码,返回json
async function getAllOwners() {
 await Moralis.start({serverUrl:serverUrl,appId:appId});
  const options = {
    chain: "eth",
    address: "0x23581767a106ae21c074b2276D25e5C3e136a68b",
  };
  const nftTransfers = await Moralis.Web3API.token.getContractNFTTransfers(
    options
  );
  console.log(nftTransfers);
}

成功会生成json文件

继续修改json文件,添加生成一个历史记录的文件

const express = require("express");
const cors = require("cors");
const moonbirds = require("./moonbirdsOwners");
const moonbirdsH = require("./moonbirdsHistory");
const collections = {
    "0x23581767a106ae21c074b2276D25e5C3e136a68b":{
        owners: moonbirds,
        history: moonbirdsH
      },
}
const app = express();
const port = 4000;
app.use(cors());
app.get("/", (req, res) => {
  res.send("Welcome to the Whale NFT server");
});
app.get("/collection", (req, res) => {
    const slug = req.query.slug;
    res.send(collections[slug].owners);
});
app.get("/user", (req, res) => {
    const slug = req.query.slug;
    const address = req.query.address;
    res.send(collections[slug].history[address]);
  });
app.listen(port, () =>
  console.log(`Whale NFT server running on ${port}`)
);

复制两个文件的数据到server文件夹下,加个默认导出

设置节点 JS 服务器

cd server
npm init -y
npm i express
npm i cors

//新建index.js
const express = require("express");
const cors = require("cors");
const moonbirds = require("./moonbirdsOwners");
const moonbirdsH = require("./moonbirdsHistory");

const collections = {
    "0x23581767a106ae21c074b2276D25e5C3e136a68b":{
        owners: moonbirds,
        history: moonbirdsH
      },
}

const app = express();
const port = 4000;
app.use(cors());

app.get("/", (req, res) => {
  res.send("Welcome to the Whale NFT server");
});
app.get("/collection", (req, res) => {
    const slug = req.query.slug;
    res.send(collections[slug].owners);
});
app.get("/user", (req, res) => {
    const slug = req.query.slug;
    const address = req.query.address;
    res.send(collections[slug].history[address]);
  });
app.listen(port, () =>
  console.log(`Whale NFT server running on ${port}`)
);

React程序

教程的github源码配置了一下大概了解,不过用的是json死数据,还有版本问题的web3api调用没成功

二、Moralis V1

每个 Dapp 都有一个链上部分(智能合约)和一个链下部分(服务器)。服务器用于从区块链收集数据并将其提供给客户端,例如 Web 和移动应用程序

入门

什么是 Moralis Dapp?

每个 Dapp 通常分为 2 个部分:

  • 1 .链上: 智能合约、代币和 NFT 等链上资产、链上交易等。
  • 2 .链下: 从区块链收集数据的后端基础设施,为 Web 应用程序和移动应用程序等客户端提供 API,索引区块链,提供实时警报,协调不同链上发生的事件,处理用户生命周期还有更多。

Moralis Dapp 用于加速链下基础设施的实施。Moralis Dapp 是一个捆绑解决方案,包含大多数 Dapp 所需的所有功能,以便尽快启动。

1.创建一个免费帐户

前往Moralis并注册一个免费帐户。

2.创建 Moralis 服务器

点击右上角的新建服务器

您可以使用 Moralis 开发 dApps主网、测试网本地开发链(例如 Hardhat 和 Ganache)。

现在,请选择Mainnet Server

3.设置所需的环境

出于本演示的目的,我们选择了 Ethereum、Polygon、BSC 和 Avalanche。

4. 探索 Dapp 仪表板

现在您将在仪表板中看到您的服务器,我们可以继续并创建一个与服务器对话并能够登录用户、获取用户数据(令牌、NFT、历史交易)等等的 Web 应用程序!当然默认所有跨链🤯

服务器显示几个重要指标如上图所示:

  • Network:每秒网络流量
  • CPU: 服务器的 CPU 使用率
  • RAM: 服务器的内存使用情况
  • DISK: 服务器的磁盘使用情况
  • Number of Users: 已在服务器中认证的用户数

将 Dapp 迁移到 Nitro 版本

现在,在 Moralis 中创建的每个新 Dapp 都将默认为 Nitro。但是,在 Moralis Nitro 推出之前创建的那些 Dapps 可能仍在使用旧版本。要将服务器升级到 Nitro,只需单击此处安装 coreservices 插件。

请记住,一旦添加了 coreservices 插件,就无法删除它。这意味着从 Legacy 到 Nitro 的迁移将是不可逆转的。在您的服务器上,您可以将coreservices其视为插件之一。

与SDK连接(React)

初始化Moralis框架需要服务器地址和api

//Creating React App
yarn create react-app moralis-react-app

// Install the SDK
cd moralis-react-app
yarn add moralis-v1 react-moralis

// Initialize the SDK
//初始化框架,在index.js加入react-moralis
import { MoralisProvider } from "react-moralis";

ReactDOM.render(
  <React.StrictMode>
    <MoralisProvider serverUrl="https://9ctqwh3fgrja.usemoralis.com:2053/server" appId="XhOTGb28N5lTeowwqTmJiRAJdtQFP8OJw3Mhj6yi">
      <App />
    </MoralisProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

会报错很多缺失依赖库,webpack打包的环境配置项缺失

安装依赖完后配置完后还是会弹出一个Right-hand side of instanceof is not callable这个bug

官网这个bug也没有修复,找到解决的方法

连接钱包

<script src="https://unpkg.com/moralis@1.5.9/dist/moralis.js"></script>

<script>
  window.Moralis = Moralis;
</script>
/** Add from here down */
async function login() {
  let user = Moralis.User.current();
  if (!user) {
    try {
      user = await Moralis.authenticate({ signingMessage: "Hello World!" });
      console.log(user.get("ethAddress"));
    } catch (error) {
      console.log(error);
    }
  }
}

async function logOut() {
  await Moralis.User.logOut();
  console.log("logged out");
}

 useEffect(() => {
    const appId = "XhOTGb28N5lTeowwqTmJiRAJdtQFP8OJw3Mhj6yi";
    const serverUrl = "https://9ctqwh3fgrja.usemoralis.com:2053/server";
    window.Moralis.start({
      serverUrl: serverUrl,
      appId: appId,
    });
  }, []);

document.getElementById("btn-login").onclick = login;
document.getElementById("btn-logout").onclick = logOut;

\

三、新版Moralis(V2)

Moralis 是将 web3 功能添加到任何技术堆栈中的最简单、最可靠的方法。Moralis 默认是跨链的,支持以太坊、BNB 链、Polygon、Avalanche、Fantom、Solana 和 Elrond 等许多链和 L2。

(一)使用NodeJS实现基本查询dapp

启动您的第一个 dapp!提前准备:

  1. 创建一个 Moralis 帐户
  2. 安装并设置您选择的编辑器( VsCode)
  3. 安装 NodeJs

1. 创建 NodeJs Web3 应用程序

创建一个应用程序,显示任何地址和 EVM 链的原生余额、ERC20 代币和 NFT!

mkdir Moralis nodejs
cd Moralis nodejs
npm init -y
npm install moralis express

//设置脚本调用
"scripts": {
  "start": "node index.js"
},

2. 导入并设置最新的 Moralis NodeJs SDK

Moralis 仪表板获取您的信息Web3 Api Key

//Moralis->Accounts Setting->KEY

5To9SQIceq7nHKILbYGAXhyLd08fAgWMa4hhD8XUvMNgHNzefwmqDT1jwM7wlTpL

3. 设置一个简单的 Express 服务器

const express = require("express");
//Import Moralis
const Moralis =  require('moralis').default;
//Import the EvmChain dataType
const {EvmChain} =  require("@moralisweb3/evm-utils");

const app = express();
const port=4000;

const MORALIS_API_KEY = "5To9SQIceq7nHKILbYGAXhyLd08fAgWMa4hhD8XUvMNgHNzefwmqDT1jwM7wlTpL";
const address = "";
const chain = EvmChain.ETHEREUM;

app.get("/",(req,res)=>{
    res.send("Hello world")
})

// Add this a startServer function that initialises Moralis
const startServer = async ()=>{
    await Moralis.start({
        apiKey:MORALIS_API_KEY,
    })
}

app.listen(port,()=>{
    console.log(`http://localhost:${port}`)
})

startServer();

4. 将您的应用程序与 Moralis Services 集成

获取并显示原生余额getNativeBalance()

  const nativeBalance = await Moralis.EvmApi.account.getNativeBalance({
    address,
    chain,
  })

获取并显示 ERC20 余额getTokenBalances()

  const tokenBalance = await Moralis.EvmApi.account.getTokenBalances({
    address,
    chain
  });
  const tokens = tokenBalance.result.map((token) => token.display());

获取并显示带有元数据的 NFTgetNFTs()

const nftsBalance = await Moralis.EvmApi.account.getNFTs({
    address,
    chain,
    limit:10,
  })
  const nfts = nftsBalance.result.map((nft)=>({
    name:nft.result.name,
    amount:nft.result.amount,
    metadata:nft.result.metadata,
  }));

5. 从任何区块链读取任何区块链数据

您已经使用 Express 和 Moralis 创建了一个简单的 NodeJs 服务器,用于获取所提供地址和链的加密数据。

const express = require("express");
//Import Moralis
const Moralis = require("moralis").default;
//Import the EvmChain dataType
const { EvmChain } = require("@moralisweb3/evm-utils");

const app = express();
const port = 4000;

const MORALIS_API_KEY =
  "5To9SQIceq7nHKILbYGAXhyLd08fAgWMa4hhD8XUvMNgHNzefwmqDT1jwM7wlTpL";
const address = "0x3692415e6f4aaeA2d3B356D83415c53A06b15Fec";
const chain = EvmChain.ETHEREUM;

app.get("/", (req, res) => {
  res.send("Hello world");
});

// Add this a startServer function that initialises Moralis
const startServer = async () => {
  await Moralis.start({
    apiKey: MORALIS_API_KEY,
  });
};

async function getBalancedata() {
  const nativeBalance = await Moralis.EvmApi.account.getNativeBalance({
    address,
    chain,
  });
  const native = nativeBalance.result.balance.ether;

  const tokenBalance = await Moralis.EvmApi.account.getTokenBalances({
    address,
    chain
  });
  const tokens = tokenBalance.result.map((token) => token.display());

  const nftsBalance = await Moralis.EvmApi.account.getNFTs({
    address,
    chain,
    limit:10,
  })
  const nfts = nftsBalance.result.map((nft)=>({
    name:nft.result.name,
    amount:nft.result.amount,
    metadata:nft.result.metadata,
  }));

  return {native,tokens,nfts};
}

app.get("/balance", async (req, res) => {
  try {
    const data = await getBalancedata();
    res.status(200);
    res.json(data)
  } catch (error) {
    console.log(error);
    res.status(500);
    res.json({ error: error.message });
  }
});

app.listen(port, () => {
  console.log(`http://localhost:${port}`);
});

startServer();

(二)使用NextJS实现Web3身份验证

创建一个允许用户使用他们的 web3 钱包登录的 NextJS 应用程序

在 web3 钱包身份验证之后,next-auth库创建一个会话 cookie,其中存储了加密的JWT (JWE)。它包含用户浏览器中的会话信息(例如地址、签名消息和到期时间)。这是在没有数据库的情况下存储用户信息的安全方式,没有密钥就不可能读取/修改 JWT 。

用户登录后,他们将能够访问显示所有用户数据的页面。

您可以在此处找到包含最终代码的存储库:https ://github.com/MoralisWeb3/demo-apps/tree/main/nextjs_moralis_auth

1. 创建一个Next.js应用并安装依赖

yarn create next-app moralis-app --typescript
cd moralis-app
//安装moralis、next-auth库创建一个会话cookie、axios发起请求
yarn add moralis next-auth axios
//使用 web3 钱包(例如 Metamask)实现身份验证,我们需要使用 web3 库
yarn add wagmi ethers

//wagmi是 React Hooks 的集合,包含开始使用以太坊所需的一切。wagmi 可以轻松“连接钱包”、显示 ENS 和余额信息、签署消息、与合约交互等等——所有这些都具有缓存、请求重复数据删除和持久性。

npm run dev

2. 在应用根目录的文件中添加新的环境变量:.env

  • APP_DOMAIN:请求签名的 RFC 4501 DNS 授权
  • MORALIS_API_KEY : 你可以在这里获取
  • NEXTAUTH_URL:您的应用地址。在开发阶段使用http://localhost:3000
  • NEXTAUTH_SECRET:用于加密用户的 JWT 令牌。您可以在此处输入任何值或在这里生成它generate-secret.now.sh/32

APP_DOMAIN=amazing.finance

MORALIS_API_KEY=xxx

NEXTAUTH_URL=http://localhost:3000

NEXTAUTH_SECRET=84b047cbf06e8e6f299f1f50dc0e0a09

3.配置入口首页index.js添加一个按钮登录进行路由跳转

import Link from "next/link";

export default function HomePage() {
  return (
    <>
      <div>Welcome to my Next.js dApp!</div>
      <Link href="/user">
        <button>登录</button>
      </Link>
    </>
  );
}

4. 创建一个登录页面

import { InjectedConnector } from 'wagmi/connectors/injected';
import { signIn } from 'next-auth/react';
import { useAccount, useConnect, useSignMessage, useDisconnect } from 'wagmi';
import { useRouter } from 'next/router';
import axios from 'axios';

export default function SignIn() {
    const { connectAsync } = useConnect();
    const { disconnectAsync } = useDisconnect();
    const { isConnected } = useAccount();
    const { signMessageAsync } = useSignMessage();
    const { push } = useRouter();

    const handleAuth = async () => {
        //判断钱包是否连接,如果已连接就断开连接
        if (isConnected) {
            await disconnectAsync();
        }
        //从窗口注入的钱包扩展属性获取钱包账户地址和链
        const { account, chain } = await connectAsync({ connector: new InjectedConnector() });
        //处理信息包装成一个对象
        const userData = { address: account, chain: chain.id, network: 'evm' };
        //用axios发送用户数据请求api进行签名验证
        const { data } = await axios.post('/api/auth/request-message', userData, {
            headers: {
                'content-type': 'application/json',
            },
        });
        const message = data.message;
        //弹出签署消息弹窗
        const signature = await signMessageAsync({ message });
        // 成功认证后将用户重定向到“/user”页面,传递(信息,签名结果,重定向,路径)
        const { url } = await signIn('credentials', { message, signature, redirect: false, callbackUrl: '/user' });
        //我们从回调中获取 url 并将其推送到路由器以避免页面刷新
        push(url);
    };

    return (
        <div>
            <h3>Web3 验证</h3>
            <button onClick={() => handleAuth()}>通过 Metamask 进行身份验证</button>
        </div>
    );
}

请求connectAsync返回对象,就是钱包扩展注入的全局属性

请求request-message返回一个对象

5. api/auth文件夹下的request-message.js

//导入Moralis
import Moralis from 'moralis';
//配置
const config = {
    domain: process.env.APP_DOMAIN,
    statement: 'Please sign this message to confirm your identity.',
    uri: process.env.NEXTAUTH_URL,
    timeout: 60,
};
//处理程序
export default async function handler(req, res) {
    //结构传过来的账户地址、链、网络
    const { address, chain, network } = req.body;
    //传入api.key
    await Moralis.start({ apiKey: process.env.MORALIS_API_KEY });
    //请求信息,身份认证的一个API,返回请求回来的数据
    try {
        const message = await Moralis.Auth.requestMessage({
            address,
            chain,
            network,
            ...config,
        });
        res.status(200).json(message);
    } catch (error) {
        res.status(400).json({ error });
        console.error(error);
    }
}

6. 用户界面,签名成功后把结果显示在用户页面

import { getSession, signOut } from "next-auth/react";

//获取本地的CSRF令牌请求Session展示到页面
export async function getServerSideProps(context) {
  const session = await getSession(context);
  //登出session删除了重定向页面
  if (!session) {
    return {
      redirect: {
        destination: "/signin",
        permanent: false,
      },
    };
  }
  //返回session
  return {
    props: { user: session.user },
  };
}

export default function User({ user }) {
  return (
    <div>
      <h4>User session:</h4>
      <pre>{JSON.stringify(user, null, 2)}</pre>
      {/* 通过删除会话 cookie 将用户注销。 自动将 CSRF 令牌添加到请求中。 */}
      <button onClick={() => signOut({ redirect: "/signin" })}>Sign out</button>
    </div>
  );
}

7. 动态路由声明文件[...nextauth].js

import CredentialsProvider from 'next-auth/providers/credentials';
import NextAuth from 'next-auth';
import Moralis from 'moralis';

export default NextAuth({
    providers: [
        CredentialsProvider({
            name: 'MoralisAuth',
            credentials: {
                message: {
                    label: 'Message',
                    type: 'text',
                    placeholder: '0x0',
                },
                signature: {
                    label: 'Signature',
                    type: 'text',
                    placeholder: '0x0',
                },
            },
            async authorize(credentials) {
                try {
                    const { message, signature } = credentials;

                    await Moralis.start({ apiKey: process.env.MORALIS_API_KEY });

                    const { address, profileId, expirationTime } = (await Moralis.Auth.verify({ message, signature, network: 'evm' })).raw;

                    const user = { address, profileId, expirationTime, signature };

                    return user;
                } catch (e) {
                    // eslint-disable-next-line no-console
                    console.error(e);
                    return null;
                }
            },
        }),
    ],
    callbacks: {
        async jwt({ token, user }) {
            user && (token.user = user);
            return token;
        },
        async session({ session, token }) {
            session.expires = token.user.expirationTime;
            session.user = token.user;
            return session;
        },
    },
    session: {
        strategy: 'jwt',
    },
});

总结:

  1. 创建一个Nextjs应用,安装依赖moralis next-auth axios wagmi ethers
  2. 注册moralis服务器账户获取web3.api,创建配置文件.env
  3. 调用库依赖的api进行查询交互
 `disconnectAsync`断开钱包连接
 `connectAsync`获取钱包信息
 `signMessageAsync`进行钱包签名连接
 `signIn`登入自动将 CSRF 令牌添加到请求中
 `signOut`删除会话 cookie 将用户注销
 `getSession`获取CSRF发起请求获取session
 `await Moralis.start({ apiKey: process.env.MORALIS_API_KEY });`调用Moralis.API之前启用
 `Moralis.Auth.requestMessage`身份认证的一个API,返回签名认证的信息
 `Moralis.EvmApi.account.getNativeBalance`查询合约余额