从零开始构建 DApp - OpenZeppelin 文档

本文介绍了如何使用 OpenZeppelin 的 Network.js 库来构建一个简单的 React DApp,该 DApp 可以显示当前连接的 Ethereum 网络信息(网络 ID、网络名称、提供者名称)以及用户账户的余额。文章还介绍了如何向用户请求账户访问权限以及如何与 Gas Station Network (GSN) 集成。

Network JS 已被弃用。我们不再开发新功能或解决问题。阅读此处获取更多信息。

从零开始构建一个 dapp

在本教程中,我们将使用 Network JS 构建一个 React dapp,该 dapp 可以在具有注入的 web3 的浏览器中使用,例如在使用 MetaMask 时。

设置环境

我们首先创建一个新项目。

$ mkdir web3-dapp && cd web3-dapp
$ npm init -y

然后我们安装 Network JS

npm install @openzeppelin/network

创建 dapp

我们将使用 create-react-app 包创建我们的 dapp,该包使用 React 引导一个简单的客户端应用程序。

$ npx create-react-app client

我们的 dapp 将显示我们当前连接的以太坊网络以及正在使用的 web3 提供程序。

在下面的代码中,首先我们从 Network JS 的 React 实现(@openzeppelin/network/react)导入 useWeb3。 然后我们使用 useWeb3 获取一个 web3Context。这个 hook 将尝试检索一个注入的 web3 提供程序(例如 MetaMask),否则它将回退到一个网络连接,在这种情况下,它是一个通过 Infura 连接到主网的连接。 networkIdnetworkNameproviderNameweb3Context 获得,并在 dapp 中显示。

client/src/App.js 文件中,将 react 项目中 App.js 中的占位符代码替换为以下代码:

import React from 'react';
import './App.css';

import { useWeb3 } from '@openzeppelin/network/react';

const infuraProjectId = '95202223388e49f48b423ea50a70e336';

function App() {

  const web3Context = useWeb3(`wss://mainnet.infura.io/ws/v3/${infuraProjectId}`);
  const { networkId, networkName, providerName } = web3Context;

  return (
    <div className="App">
      <div>
        <h1>OpenZeppelin Network.js</h1>
        <div>Network: {networkId ? `${networkId} – ${networkName}` : 'No connection'}</div>
        <div>Provider: {providerName}</div>
      </div>
    </div>
  );
}

export default App;

我们可以通过从 client 目录中运行 npm start 来启动我们的 dapp。

dapp 将显示以太坊网络 ID 和网络名称,如果没有可用的注入 web3 提供程序(如 MetaMask),则显示来自我们的网络连接的主网,否则它将显示由注入的 web3 提供程序选择的当前网络,以及 web3 提供程序名称。

我们使用 useWeb3,它首先尝试获取一个注入的 web3 提供程序,然后再回退到网络连接。或者,我们可以使用 useWeb3Injected 获取注入的 web3 提供程序,或使用 useWeb3Network 获取网络提供程序,例如 Infura 或私有节点。如果需要,Dapps 也可以配置为使用多个提供程序。

如果你自己的 dapp 中使用 Infura,你将需要在 Infura 上创建一个帐户并设置一个 Infura 项目(以及相应的 Infura 项目 ID)。请参阅 Infura 网站 创建一个帐户。

添加组件

对于我们的 dapp 的第二次迭代,我们将把当前以太坊网络的显示移动到一个组件,并查看当进行更改(例如网络)时组件是如何重新渲染的。

要安装 MetaMask 作为我们注入的 web3 提供程序(如果你尚未安装注入的 web3 提供程序),请按照 MetaMask 网站 上的说明安装浏览器扩展。

在下面的代码中,我们的 Web3Info 组件需要一个 web3ContextnetworkIdnetworkNameproviderNameweb3Context 获得,并在组件中显示。

client/src 目录中,创建一个 components 目录。在 components 目录中,创建一个包含以下代码的 Web3Info.js 文件:

import React from 'react';

export default function Web3Info(props) {
  const { web3Context } = props;
  const { networkId, networkName, providerName } = web3Context;

  return (
    <div>
      <h3> {props.title} </h3>
      <div>Network: {networkId ? `${networkId} – ${networkName}` : 'No connection'}</div>
      <div>Provider: {providerName}</div>
    </div>
  );
}

在我们的 App 中,我们使用我们的 Web3Info 组件并提供一个 web3Context

client/src/App.js 文件中,为我们的 Web3Info 组件添加一个导入:

import Web3Info from './components/Web3Info.js';

然后将 App.jsApp 函数的内容替换为以下代码:

  const web3Context = useWeb3(`wss://mainnet.infura.io/ws/v3/${infuraProjectId}`);

  return (
    <div className="App">
      <div>
        <h1>OpenZeppelin Network.js</h1>
        <Web3Info title="Web3 Info" web3Context={web3Context} />
      </div>
    </div>
  );

通过从 client 目录中再次运行 npm start 来启动我们的 dapp。

Network JS 会在帐户、网络和连接等更改时重新渲染 React 组件。

尝试在 MetaMask 中更改网络,Network JS 将导致组件使用网络 ID 和网络名称进行更新。

添加请求访问帐户地址

对于我们的 dapp 的第三次迭代,我们将添加一个机制来请求访问用户的地址,并在用户连接后显示帐户地址。

在用户授予权限之前,注入的 web3 提供程序不会让 dapp 访问用户的地址。这是为了保护用户的隐私,否则网站可能会根据其帐户地址跟踪用户。有关详细信息,请参见 EIP-1102

最佳实践是 dapp 等待请求访问用户的地址,直到用户想要执行只有他们才能执行的操作(需要此访问权限)。<br>请记住,如果需要,可以将 dapp 配置为使用多个 web3 提供程序,例如网络提供程序。

在下面的代码中,我们从 web3Context 获取 accounts,如果可用,则用户的地址将显示在 dapp 中。 如果 accounts 不可用,我们将显示一个按钮,供用户请求 dapp 访问用户的地址。按下后,将调用 web3Context 中的 requestAuth 函数,并且注入的 web3 提供程序可以向用户显示一个对话框来请求访问。 我们还使用 react 功能 useCallback 来建立请求访问的回调。

Web3Info.js 文件中,将组件的内容替换为以下代码:

import React, { useCallback } from 'react';

export default function Web3Info(props) {
  const { web3Context } = props;
  const { networkId, networkName, accounts, providerName } = web3Context;

  const requestAuth = async web3Context => {
    try {
      await web3Context.requestAuth();
    } catch (e) {
      console.error(e);
    }
  };

  const requestAccess = useCallback(() => requestAuth(web3Context), []);

  return (
    &lt;div>
      &lt;h3> {props.title} &lt;/h3>
      &lt;div>Network: {networkId ? `${networkId} – ${networkName}` : 'No connection'}&lt;/div>
      &lt;div>Your address: {accounts && accounts.length ? accounts[0] : 'Unknown'}&lt;/div>
      &lt;div>Provider: {providerName}&lt;/div>
      {accounts && accounts.length ? (
        &lt;div>Accounts & Signing Status: Access Granted&lt;/div>
      ) : !!networkId && providerName !== 'infura' ? (
        &lt;div>
          &lt;button onClick={requestAccess}>Request Access&lt;/button>
        &lt;/div>
      ) : (
        &lt;div>&lt;/div>
      )}
    &lt;/div>
  );
}

通过从 client 目录中再次运行 npm start 来启动我们的 dapp。

一旦 dapp 在浏览器中加载,请按 Request Access 以请求访问用户的地址,然后接受请求(在 MetaMask 中按“连接”按钮)。然后将显示用户的地址。 要重新启动该过程,可以在 MetaMask 中注销,用户将需要再次请求访问。

添加帐户余额

对于我们的 dapp 的最后一次迭代,我们将添加到我们的组件以显示帐户余额。

在下面的代码中,我们从 web3Context 获取 lib,它是 web3.js 的已初始化实例。我们使用 lib (web3.js) 来 getBalance 帐户并将 fromWei 转换为以太单位。 我们还使用 react 功能 useState 来跟踪帐户余额的状态,并使用 useEffectaccountsnetworkId 更改时获取余额。

Web3Info.js 文件中,将组件的内容替换为以下代码:

import React, { useState, useEffect, useCallback } from 'react';

export default function Web3Info(props) {
  const { web3Context } = props;
  const { networkId, networkName, accounts, providerName, lib } = web3Context;

  const [balance, setBalance] = useState(0);

  const getBalance = useCallback(async () => {
    let balance =
      accounts && accounts.length > 0 ? lib.utils.fromWei(await lib.eth.getBalance(accounts[0]), 'ether') : 'Unknown';
    setBalance(balance);
  }, [accounts, lib.eth, lib.utils]);

  useEffect(() => {
    getBalance();
  }, [accounts, getBalance, networkId]);

  const requestAuth = async web3Context => {
    try {
      await web3Context.requestAuth();
    } catch (e) {
      console.error(e);
    }
  };

  const requestAccess = useCallback(() => requestAuth(web3Context), []);

  return (
    &lt;div>
      &lt;h3> {props.title} &lt;/h3>
      &lt;div>Network: {networkId ? `${networkId} – ${networkName}` : 'No connection'}&lt;/div>
      &lt;div>Your address: {accounts && accounts.length ? accounts[0] : 'Unknown'}&lt;/div>
      &lt;div>Your ETH balance: {balance}&lt;/div>
      &lt;div>Provider: {providerName}&lt;/div>
      {accounts && accounts.length ? (
        &lt;div>Accounts & Signing Status: Access Granted&lt;/div>
      ) : !!networkId && providerName !== 'infura' ? (
        &lt;div>
          &lt;button onClick={requestAccess}>Request Access&lt;/button>
        &lt;/div>
      ) : (
        &lt;div>&lt;/div>
      )}
    &lt;/div>
  );
}

通过从 client 目录中再次运行 npm start 来启动我们的 dapp。

该 dapp 现在显示帐户余额。

Gas Station Network

Network JS 可以与 Gas Station Network (GSN) 一起使用。下面的示例使用网络提供程序 (Infura),并生成一个临时密钥,用于签署中继到 GSN 的请求。

const web3Context = useWeb3Network(`wss://rinkeby.infura.io/ws/v3/${infuraProjectId}`, {
  gsn: { signKey: useEphemeralKey() }
});

要快速开始使用 Network JS 和 Gas Station Network,你可以使用 OpenZeppelin GSN Starter Kit

要解压缩入门工具包,请在空项目目录中运行以下命令,然后按照说明进行操作。

openzeppelin unpack @openzeppelin/starter-kit-gsn

Vanilla JavaScript 和非 React 框架

Network JS 可以与(原生)JavaScript 和非 React 框架一起使用。请注意,导入是 @openzeppelin/network 而不是 @openzeppelin/network/react,并且是 fromInjected 而不是 useWeb3Injected

import { fromInjected, fromConnection } from '@openzeppelin/network';

const web3Context = await fromInjected();

function updateNetwork(networkId, networkName) {}
function updateAccounts(accounts) {}
function updateConnection(connected) {}

web3Context.on(Web3Context.NetworkIdChangedEventName, updateNetwork);
web3Context.on(Web3Context.AccountsChangedEventName, updateAccounts);
web3Context.on(Web3Context.ConnectionChangedEventName, updateConnection);

← 概述

API 参考 →

  • 原文链接: docs.openzeppelin.com/ne...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
OpenZeppelin
OpenZeppelin
江湖只有他的大名,没有他的介绍。