通过React和Node.js API与智能合约进行互动

485 阅读9分钟

在构建DApp时,你和你的用户都需要一个第三方的、针对浏览器的插件或扩展,以便用户与你的智能合约互动。在撰写本文时,没有任何主要浏览器具有与区块链节点上的智能合约互动的内置能力。

如果不依靠你的用户安装所需的扩展程序,而是让他们使用你的DApp立即与智能合约互动,那会怎么样?在这篇文章中,我们将建立一个Node.js API,它将使用Web3.js与区块链网络进行交互并检索数据,然后使用React将其送回给浏览器应用程序上的用户。

要跟上这篇文章,你可以在这个GitHub仓库访问完整的代码。但在我们进入项目之前,让我们考虑一下区块链技术的一些基本原理。

Node API Layer Project Overview

区块链技术的基本原理

区块链技术是基于一个去中心化的系统。为了理解去中心化系统,我们首先需要理解中心化系统。大多数在线服务,如Facebook、谷歌、Twitter、Instagram和WhatsApp都使用集中式系统,这意味着用户数据存储在他们的服务器中,组织可以在集中式服务器或数据库中完全访问这些数据。

在一个去中心化的系统中,用户数据分布在区块链网络的几个节点中,每个节点都包含该数据的完整副本。这些节点不是由单一实体所拥有,而是由世界各地的个人所拥有。

要了解区块链,你需要了解三个重要的概念,区块、矿工和节点。

一个区块链网络由链组成,每条链都由区块组成。区块有三个元素:区块中的数据、Nonce(一个32位的整数)和Hash(一个256位的数字)。

矿工利用挖矿的过程,在链上创建新的区块。挖掘一个区块是一个相当困难的过程,因为每个区块都有一个对链上前一个区块的哈希值的引用。

节点指的是区块链中的计算机或任何其他电子设备。去中心化是区块链技术的核心。没有一台计算机或组织可以拥有这条链;相反,它是通过连接到链上的节点分布的。

开始工作

为了开始我们的项目,我们需要设置Ganache和Truffle,它们允许我们与智能合约一起工作

设置工具

首先,从其官方网站下载Truffle,然后用下面的命令安装它。

npm i truffle -g

要安装Ganache,你可以按照官方文档的要求进行。打开后,你会看到一个类似下面的屏幕。

Ganache Install Homepage

设置Metamask

继续前进,将Metamask扩展添加到谷歌浏览器。一旦Metamask被添加到你的Chrome浏览器中,点击扩展图标,你应该看到类似于下面的屏幕。请记住,如果这是你第一次打开Metamask,你可能不会在列表中看到任何网络。

Metamask Extension Chrome Homepage

现在,点击私人网络。你应该看到下面的屏幕,其中列出了所有不同的网络。

Metamask Private Network

点击 "添加网络",你应该会被转到一个不同的页面,看起来像下面的图片。

Metamask Add Network Fields

在表格中填写以下细节。

Metamask New Network Form Entry Fields

请忽略链ID的错误。Metamask将接受原样。现在,你可以点击保存,当你在Chrome中点击Metamask扩展时,你应该在列表中看到你新创建的网络,如下图所示。

Newly Created Metamask Network

设置后端

要开始建立我们的后端,首先要确保你的机器上已经全局安装了pnpm。我们将使用pnpm而不是npm或Yarn。如果你还没有安装pnpm,运行下面的命令来安装它。

npm install pnpm -g

接下来,确保你已经全局安装了nodemon;如果没有,运行下面的命令来安装它。

npm install nodemon -g

启动Ganache,然后打开你的终端,按照下面的命令操作。

mkdir blockchain-node
cd blockchain-node
mkdir blockchain-node-api
cd blockchain-node-api
pnpm init -y

在你喜欢的代码编辑器中打开你的项目,打开package.json 文件,然后在该文件中添加以下代码。

{
  "name": "blockchain-node-api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon server.js",
    "build": "node server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@truffle/contract": "^4.4.1",
    "cors": "^2.8.5",
    "express": "^4.17.2",
    "mongodb": "^4.2.2",
    "nodemon": "^2.0.15",
    "web3": "^1.6.1"
  }
}

要安装上述所有的依赖项,请运行以下命令。

pnpm install

编写你的第一个智能合约

要将你的项目初始化为一个Truffle项目,请在blockchain-node-api 内运行以下命令。

truffle init

上面的命令将生成一些文件夹。在contracts 文件夹内,创建一个名为Contacts.sol 的新文件,并在其中粘贴以下代码。

pragma solidity ^0.8.10;

contract Contacts {
  uint public count = 0; // state variable

  struct Contact {
    uint id;
    string name;
    string phone;
  }

  constructor() public {
    createContact('Zafar Saleem', '123123123');
  }

  mapping(uint => Contact) public contacts;

  function createContact(string memory _name, string memory _phone) public {
    count++;
    contacts[count] = Contact(count, _name, _phone);
  }
}

现在,你有了你的第一个使用 Solidity 的智能合约。我们使用contract 关键字创建了一个智能合约,并将其命名为Contacts 。在Contacts 内,我们创建了一个名为count 的状态公共变量。

接下来,我们使用struct 关键字创建了一个结构,并将其命名为Contact 。我们添加了id,name, 和phone 作为属性。之后,我们做了一个构造函数。在该函数中,我们通过调用createContact 函数向合同中添加了一个联系人,该函数是在该合同类的末尾声明的。

我们创建了map ,以便在我们的合同中添加联系人。我们声明了createContact ,并将namephone 作为参数传给了对方。注意,这是一个公共函数。然后,我更新了状态变量count ,我把它作为id ,在contacts map

就这样,我们已经完成了我们的第一个智能合约的编写。现在,我们将把我们的智能合约部署到Truffle。在migrations 文件夹中创建一个新文件,名称为2_deploy_contacts.js ,并粘贴下面的代码。

const Contacts = artifacts.require("./Contacts.sol");

module.exports = function(deployer) {
  deployer.deploy(Contacts);
};

接下来,打开你的truffle-config.js 文件,将下面的代码粘贴在里面。

module.exports = {
  networks: {
    development: {
      host: "127.0.0.1",
      port: 7545,
      network_id: "*"
    }
  },
  compilers: {
    solc: {
      version: "0.8.10",
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
  }
}

确保上述所有信息与你的Ganache网络设置相一致,特别是hostport 。然后,运行以下命令。

truffle migrate

该命令可能需要几秒钟的时间来迁移你的智能合约。

编写Node.js API

写好智能合约并部署到Truffle后,我们可以编写API,作为我们的前端应用程序和智能合约之间的一个层。在blockchain-node-api 文件夹中,创建名为routes.js,server.js, 和config.js 的文件。然后,打开server.js 文件,粘贴下面的代码。

const express = require('express');
const app = express();
const cors = require('cors');
const routes = require('./routes');
const Web3 = require('web3');
const mongodb = require('mongodb').MongoClient;
const contract = require('@truffle/contract');
const artifacts = require('./build/contracts/Contacts.json');
const CONTACT_ABI = require('./config');
const CONTACT_ADDRESS = require('./config');

app.use(cors());
app.use(express.json());

if (typeof web3 !== 'undefined') {
        var web3 = new Web3(web3.currentProvider); 
} else {
        var web3 = new Web3(new Web3.providers.HttpProvider('http://localhost:7545'));
}

mongodb.connect('mongodb://127.0.0.1:27017/blockchain-node-api',
        {
                useUnifiedTopology: true,
        }, async (err, client) => {
        const db =client.db('Cluster0');
        const accounts = await web3.eth.getAccounts();
        const contactList = new web3.eth.Contract(CONTACT_ABI.CONTACT_ABI, CONTACT_ADDRESS.CONTACT_ADDRESS);

        routes(app, db, accounts, contactList);
        app.listen(process.env.PORT || 3001, () => {
                console.log('listening on port '+ (process.env.PORT || 3001));
        });
});

server.js 是作为Node.js服务器运行的主文件。我在文件的开头要求所有的依赖项。然后,使用cors 和Express,我检查Web3.js,并通过提供一个localhost 地址使其与区块链网络互动。

接下来,我连接到一个MongoDB数据库。虽然我们在本文中没有使用任何数据库,但为将来的使用准备好这个是很好的。在mongodb.connect() 函数的回调函数里面,我正在与Cluster0 连接,并从以太坊区块链网络中获得accounts

使用web3 Contract 函数,在那里我传递CONTACT_ABICONTACT_ADDRESS ,我得到了与智能合约的连接。我们将把这些信息添加到我们之前创建的config.js 文件中。

接下来,我们将调用带有所有参数的routes() 。我将在我们之前创建的routes.js 文件中创建这个routes 函数。你可以在port 3001 上收听这个应用程序。让我们转到config.js 文件,打开它,并添加下面的代码。

const CONTACT_ADDRESS = '0xB7fC6C3DFebD24EAe16E307Ea39EdF7c93ff7866';

const CONTACT_ABI = [
        {
    "inputs": [],
    "stateMutability": "nonpayable",
    "type": "constructor"
  },
  {
    "inputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "contacts",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "id",
        "type": "uint256"
      },
      {
        "internalType": "string",
        "name": "name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "phone",
        "type": "string"
      }
    ],
    "stateMutability": "view",
    "type": "function",
    "constant": true
  },
  {
    "inputs": [],
    "name": "count",
    "outputs": [
      {
        "internalType": "uint256",
        "name": "",
        "type": "uint256"
      }
    ],
    "stateMutability": "view",
    "type": "function",
    "constant": true
  },
  {
    "inputs": [
      {
        "internalType": "string",
        "name": "_name",
        "type": "string"
      },
      {
        "internalType": "string",
        "name": "_phone",
        "type": "string"
      }
    ],
    "name": "createContact",
    "outputs": [],
    "stateMutability": "nonpayable",
    "type": "function"
  }
];

module.exports = {
        CONTACT_ABI,
        CONTACT_ADDRESS,
};

我们将需要这些信息来与server.js 文件中使用Web3.js的智能合约连接,就像我们之前做的那样。接下来,我们将在我们的routes.js 文件中添加以下代码。

function routes(app, db, accounts, contactList) {
        app.get('/contacts', async (request, response) => {
                let cache = [];
                const COUNTER = await contactList.methods.count().call();

                for (let i = 1; i <= COUNTER; i++) {
      const contact = await contactList.methods.contacts(i).call();
      cache = [...cache, contact];
    }

    response.json(cache);
  });
}

module.exports = routes

在这个文件中,我正在创建一个带有所有必要参数的routes 函数。接下来,我将使用GET 路由来实现/contacts 端点。在回调函数中,我正在创建一个缓存变量。然后,我们通过调用count() 函数从智能合约中获得一个COUNTER ,这个函数是在我们创建一个名称为count 的公共状态变量时自动创建的。

然后,我们循环浏览所有的COUNTER ,并从contactList ,逐一获得contacts ,我把它放在cache 变量中,最后发送到前面的响应。

在文件的最后,我们将导出routes 函数,这样我们就可以在其他函数中使用它,比如在本例中,server.js

现在,用下面的命令运行服务器。

nodemon server.js

上面的命令将运行服务器,它现在已经准备好接收来自我们React应用的请求。

用React构建我们的前端

blockchain-node 现在我们的智能合约、Node.js服务器和API已经准备好了,我们可以编写前端的React应用。CD ,然后运行下面的命令来创建一个React项目。

pnpx create-react-app blockchain-node-api-react

一旦新项目加载,打开你的App.js 文件,用下面的代码替换现有的代码。

import { useEffect, useState } from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
        const [contacts, setContacts] = useState([]);

        useEffect(() => {
                async function fetcher() {
                        const response = await fetch('http://localhost:3001/contacts');        
                        const contacts = await response.json();

                        setContacts(contacts);
                }

                fetcher();
        }, []);

  return (
    <div>
            <ul>
      {
              contacts.map(contact => (
                      <li key={contact.id}>
                              <p>Name: {contact.name}</p>
                              <span>Phone: {contact.phone}</span>
                      </li>
              ))
      }
      </ul>
    </div>
  );
}

export default App;

上面的代码生成了一个简单的React功能组件,我在其中声明了本地状态变量contacts

useEffect Hook里面,我们向我们刚刚创建的Node.js服务器发出请求。我们的服务器使用Web3.js从区块链网络中获取所有的联系人,然后将这些联系人发送到我们的React应用程序。我们将这些来自服务器的联系人放入一个contacts 变量,并将其设置在本地状态变量contacts 。在jsx ,我们循环浏览所有的联系人,并在屏幕上渲染他们。

使用下面的命令运行你的React应用程序。

yarn start

上面的命令会打开你的浏览器,你会看到像下面这样的输出。

Browser Output Final Output

结论

在这篇文章中,我们创建了一个Node.js API,允许你的用户与智能合约互动,而无需安装第三方扩展。我们涵盖了一些理解区块链技术的基础知识,用Node.js构建我们的后端,编写智能合约,最后,用React构建我们的前端。

我希望你喜欢这个教程,如果你有任何问题,请务必留下评论。另外,别忘了在我的简历中链接的社交媒体账户上关注我并与我联系。

The postInteract with smart contracts via React and a Node.js APIappeared first onLogRocket Blog.