使用 React & Network.js 建立 Dapp 前端

使用 React & Network.js 建立的 Dapp 前端,可使用 Infura & Metamask 連接並從 Ethereum Mainnet 要求資料

在 Ethereum 開發服務或商務並不容易,而這是由於區塊鏈使用的複雜程度以及新出現的 UX / UI 問題所致。此系列指南的目的,是提供 Ethereum Dapp 開發的快速入門,同時也著眼在前端解決方案之上。好消息是,您不需要重塑鏈結。您有許多既存工具和服務可以使用,協助您快速開始執行 Dapp,其中包括:

  • Infura,可做為 JSON-RPC 與 Mainnet 或測試網的連線;
  • MetaMask,可做為使用者的錢包和 web3 提供者;
  • OpenZeppelin,可做為合約與 web3 專用 Javascript 資料庫 Network JS (意即區塊鏈開發)。此程式碼教學課程就是以其 Dapp 建立指南為靈感而設計。

搭配使用這些產品,可以取代資料庫設定和使用者認證管理,讓您得以快速建立區塊鏈基礎結構。在本教學課程中,我們將使用 Network JS 建立 React Dapp,這可與使用 Infura 做為其與 Ethereum Mainnet 連線的 Metamask 互動。

環境設定

開始之前,您必須先安裝 Node.js 與 npm。讓我們從建立一個新專案開始...

mkdir web3-infura && cd web3-infura

npm init -y

...然後安裝 OpenZeppelin 的 Network JS,以取得搭配 web3 使用的資料庫方式:

npm install @openzeppelin/network

現在我們準備好開始建立 Dapp

使用稱為 create-react-app 的 React 樣板,我們就可以建立 Dapp 的初始版本。使用 npx (npm 的套件執行程式) 就可輕鬆設定。只要一行,我們就有 Dapp 的初始檔案:

npx create-react-app client

為了要在 Dapp 內使用 Infura,您必須註冊才能設定一個帳戶並建立一個專案

在 client/src/App.js 檔案中,我們可以找到 React 專案,並尋找以 App.js 撰寫的預留位置程式碼。我們要使用下列取代預留位置程式碼:

import React from 'react';
import './App.css';
import { useWeb3 } from '@openzeppelin/network/react';
const infuraProjectId = '<YOUR_INFURA_PROJECT_ID';
function App() {
const web3Context = useWeb3(`wss://mainnet.infura.io/ws/v3/${infuraProjectId}`);
const { networkId, networkName, providerName } = web3Context;
return (
<div className="App">
	<div>
	<h1>Infura/MetaMask/OpenZeppelin Dapp</h1>
		<div>
    	Network: {networkId ? `${networkId} – ${networkName}` : 'No connection'}
		</div>
		<div>
		Provider: {providerName}
		</div>
	</div>
</div>
);
}
export default App;

現在我們的 Dapp 將顯示其目前連接的是哪一個 Ethereum 網路 (Mainnet 或測試網),以及其使用的 web3 提供者。

讓我們在 /client 目錄中執行 npm start,透過儲存和啟動瀏覽器的方式開始測試。將 Infura Websocket URL 中的 mainnet 取代為 rinkeby 以進行測試。如果 MetaMask 已經安裝,由於您會發現其為優先使用,因此請在此停用擴充。

這種運作方式,是在上述程式碼中,我們從 Network JS (@openzeppelin/network/react) 的 React 實作中匯入 useWeb3 以取得 web3Context。這種 Javascript 勾點會嘗試擷取插入的 web3 提供者,而根據預設,此提供者為 MetaMask。如果沒有插入提供者,則系統會使用 Infura URL 組為 web3Context。「插入」一詞的意思,是指程式碼或資料來自使用者瀏覽器,而且可供網站使用。

請注意,多種不同的 Ethereum web3 資料庫都有提供許多不同語言版本,而且由於實作詳細規格有所不同,全部都會透過「提供者」或「簽署人」建立 web3 連線,因此請務必了解此步驟。

useWeb3 勾點會先嘗試取得插入的 web3 提供者,然後才會回到網路連線。另外也可以使用插入 web3 提供者專用 useWeb3Injected,或 Infura 或不公開節點等網路提供者專用 useWeb3Network。

新增 React 元件

我們的下一個目標,是移動目前的 Ethereum 網路顯示至一個元件,然後查看元件如何在網路等變更後重新轉譯。

若要執行此操作,第一部就是選擇要插入的 web3 提供者,然後進行安裝。在此教學課程中,我們將使用 MetaMask 做為我們的 web3 提供者,因此請前往 metamask.io 以進行安裝。

在下方的程式碼中,Web3Data 元件會預期 web3Context。networkId、networkName 和 providerName 會從 web3Context 取得,並在元件中展示。

然後我們會在 client/src 目錄中建立一個元件目錄,並使用下列程式碼建立一個 Web3Data.js 檔案:

import React from 'react';
export default function Web3Data(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.js,我們將新增 Web3Data 元件以提供 web3Context。在 client/src/App.js 檔案中,新增此匯入以供 Web3Data 元件使用:

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

然後在 App.js 中使用以下程式碼取代應用程式功能的所有內容:

const web3Context = useWeb3(`wss://mainnet.infura.io/ws/v3/${infuraProjectId}`);
return (
<div className="App">
	<div>
	<h1>Infura React Dapp with Components!</h1>
	<Web3Data title="Web3 Data" web3Context={web3Context} />
	</div>
</div>
);

讓我們從用戶端目錄內執行 npm start 以再次啟動 Dapp,以便查看 Network JS 在帳戶、連線類型或網路變更時如何重新轉譯 React 元件。您可以在 MetaMask 中變更網路以進行測試。

要求存取帳戶位址

要求使用者的帳戶位址並顯示,是 Dapp 中最常見的模式之一。但是插入的 web3 提供者無法讓 Dapp 存取使用者的位址,除非該使用者已提供權限。這點限制的設計目的,是為了保護使用者隱私,也避免惡意執行者使用帳戶位址在網際網路中追蹤使用者。您可以在這裡進一步了解此通訊協議。

在我們的 Web3Data.js 檔案中,我們會使用下列程式碼取代元件的內容:

import React, { useCallback } from 'react';
export default function Web3Data(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 (
<div>
<h3> {props.title} </h3>
	<div>
    Network: {networkId ? `${networkId} – ${networkName}` : 'No connection'}
	</div>
	<div>
	Your address: {accounts && accounts.length ? accounts[0] : 'Unknown'}
	</div>
	<div>
	Provider: {providerName}
	</div>

	{accounts && accounts.length ? (
	<div>
	Accounts & Signing Status: Access Granted
	</div>
	) : !!networkId && providerName !== 'infura' ? (
	<div>
	<button onClick={requestAccess}>Request Access</button>
	</div>
	) : (
	<div></div>
	)}
</div>
);
}

以下說明上述程式碼的作用:

  • web3Context 擷取帳戶,而且如果可用,使用者的位址也會在 Dapp 中顯示;
  • 如果我們要求的帳戶無法使用,則 Dapp 將產生一個按鈕,讓使用者可允許 Dapp 存取他們的位址;
  • 當使用者按下此按鈕時,web3Context 中的 requestAuth 功能就會受到呼叫,然後插入的 web3 提供者就可以向使用者顯示對話以要求存取。使用 React 功能,系統就會使用 useCallback 以設定帳戶要求存取的回呼。

現在,我們要從用戶端目錄中執行 npm start 以再次啟動 Dapp。

在瀏覽器中,我們按下「Request Access」(要求存取) 以要求存取使用者的位址,然後在 Metamask 彈出式視窗 (如果沒有彈出式視窗則為擴充) 中接受該要求。然後使用者的位址就會顯示。若要重新開始此程序,登出 Metamask 就可以再次執行使用者要求存取。

帳戶餘額

最後,我們第三個目標是新增 React 元件以顯示帳戶餘額。

在下列程式碼中,我們有 lib 物件,此物件包含我們需要的 web3Context 區塊鏈資料,而這就是 web3.js (Network.js 仰賴使用的資料庫) 的初始化執行個體。

我們會使用 lib 做為方式 getBalance 以要求取得帳戶餘額,然後系統就會以 wei 為單位傳回餘額的值。使用乙太單位會比較簡單,所以我們要使用方式 fromWei 進行轉換。

由於我們要追蹤帳戶餘額狀態,我們會使用 React 特定功能,稱為 useState。我們也會使用 useEffect 來追蹤帳戶位置或 networkId 是否已經變更,然後據此進行更新。

若要讓 Dapp 顯示帳戶餘額,我們會使用下列程式碼取代我們 Web3Data.js 檔案中的元件內容:

import React, { useState, useEffect, useCallback } from 'react';
export default function Web3Data(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((web3Context) => requestAuth(web3Context), []);
return (
<div>
<h3> {props.title} </h3>
	<div>
    Network: {networkId ? `${networkId} – ${networkName}` : 'No connection'}
    </div>
	<div>
    Your address: {accounts && accounts.length ? accounts[0] : 'Unknown'}
    </div>
	<div>
    Your ETH balance: {balance}
    </div>
	<div>
    Provider: {providerName}
    </div>
	{accounts && accounts.length ? (
	<div>
    Accounts & Signing Status: Access Granted
    </div>
	) : !!networkId && providerName !== 'infura' ? (
	<div>
	<button onClick={requestAccess}>Request Access</button>
	</div>
	) : (
	<div></div>
	)}
</div>
);
}

我們要從用戶端目錄中執行 npm start 以再次啟動 Dapp,然後就完成了!Dapp 現在應可顯示帳戶餘額。

現在您已經建立 Dapp 前端 (使用Network.js),可連接並從 Ethereum Mainnet 要求資料 (使用 Infura)。使用者「登入」(使用 Metamask),然後 Dapp 就會顯示使用者的重要資訊,並在狀態變更時做出反應。

繼續學習

我們下一個教學課程將說明如何顯示使用者的 ERC20 餘額,並允許使用者轉移。更深入的 web3 開發指南將介紹使用 Ethers.js 建立、使用 OpenZeppelin 的入門套件等主題,並且說明更複雜的整合,例如使用 Truffle 的 Drizzle 架構設定 Eth Logto 篩選器,以在前端及時針對區塊鏈資料變更做出反應。


千萬別錯過教學課程!訂閱我們的電子報以取得最新資訊。準備好要測試 Infura 了嗎?註冊以開始免費建立!