“携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第7天,点击查看活动详情”
现现在是时候集成到自己的前端应用了。就以前面做好的helloworld 合约为例。
我们先选用一个前端框架,这里就用React.js。React 就不具体展开了,掘金上有太多优秀的教程和笔记了。
一共分12步完整的拆解。
1、创建一个前端工程,检查
npm install -g create-react-app
create-react-app helloworld
然后添加两个文件
然后检查启动环境OK。
npm start
2、 创建、HelloWorld.js
接着我们将要处理一个要 React 组件。
都在 HelloWorld.js里
我们有几个导入语句是让我们的项目运行所必需的,包括 React 库、useEffect 和 useState hooks。(./util/interact.js)
import React from "react";
import { useEffect, useState } from "react";
import {
helloWorldContract,
connectWallet,
updateMessage,
loadCurrentMessage,
getCurrentWalletConnected,
} from "./util/interact.js";
import alchemylogo from "./img/logo.svg";
接下来,我们有我们的状态变量,我们将更新后的特定事件。
//先声明几个状态变量
const [walletAddress, setWallet] = useState("");
const [status, setStatus] = useState("");
const [message, setMessage] = useState("No connection to the network.");
const [newMessage, setNewMessage] = useState("");
以下是每个变量所代表的内容:
walletAddress- 存储用户钱包地址的字符串
status- 存储有用消息的字符串,指导用户如何与 dApp 交互
message- 在智能合约中存储当前消息的字符串
new message- 存储将写入智能合约的新消息的字符串
在状态变量之后,您会看到五个未实现的函数:useEffect、addSmartContractListener、
connectWalletPressed和``onUpdatePressed
这些下面都会一一解释:
//仅调用一次
useEffect(async () => { //TODO: implement
}, []);
function addSmartContractListener() { //TODO: implement
}
function addWalletListener() { //TODO: implement
}
const connectWalletPressed = async () => { //TODO: implement
};
const onUpdatePressed = async () => { //TODO: implement
};
3、创建utils/interact.js (工具类)
我们需要一个单独的文件,其中包含我们所有的函数来管理 dApp 的逻辑、数据和规则,然后能够将这些函数导出到我们的前端组件HelloWorld.js里
👆🏽这就是utils/interact.js 文件的确切目的!
interact.js
//export const helloWorldContract;
export const loadCurrentMessage = async () => {
};
export const connectWallet = async () => {
};
const getCurrentWalletConnected = async () => {
};
export const updateMessage = async (message) => {
};
接着我们对四个未实现的函数做具体操作说明:
loadCurrentMessage- 处理加载存储在智能合约中的当前消息。
里将会用Alchmy Web3 API 对 Hello World 智能合约进行读取调用。
connectWallet- 这个函数会将我们的当前登录的 Metamask 账户,连接到我们的 dApp。
getCurrentWalletConnected- 此功能将在页面加载时检查钱包帐户是否已连接到我们的 dApp,并相应地通知我们的 UI组件。
updateMessage 此功能将更新存储在智能合约中的消息。它将对 Hello World 智能合约进行写调用,因此用户的 Metamask 钱包将必须签署以太坊交易的更新消息。。
4、用Alchmy Web3 API读取智能合约
要读取您的智能合约,您需要成功设置:
-
与以太坊链的 API 连接
-
您的智能合约的加载实例
-
调用您的智能合约函数的函数
-
当您从智能合约中读取的数据发生更改时,用于监视更新的侦听器
建立与以太坊链的 API 连接
还记得前天的教程么? 我们如何使用我们的Alchemy Web3 密钥来读取我们的智能合约吗?
在前端工程里也是一样的,我们安装 Alchemy Web3。
在终端中运行以下命令。
npm install @alch/alchemy-web3
[
Alchemy Web3
[
提供增强的 API 方法,让您作为 web3 开发人员的生活更轻松。
](web3js.readthedocs.io/en/v1.2.9/)
它旨在要求最少的配置,因此您可以立即在您的应用程序中开始使用它!
我们还需要在项目目中安装 dotenv (读取配置文件,因为我们会用到私钥,私钥这些信息尽量不写在代码里) Web3 API 以及其他一切敏感信息 都可以存放在.env 文件里。
npm install dotenv --save
我们将使用我们的 Websockets API 密钥
复制 websockets 密钥后,在项目的根目录中创建一个
.env
文件并将 Alchemy Websockets url 添加到其中。
.env
文件应如下所示:
REACT_APP_ALCHEMY_KEY = wss://eth-goerli.ws.alchemyapi.io/v2/
并在文件interact.js顶部添加以下代码:
require('dotenv').config();
const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY;
const { createAlchemyWeb3 } = require("@alch/alchemy-web3");
const web3 = createAlchemyWeb3(alchemyKey);
//export const helloWorldContract;
准备好这个之后,就开始加载我们的智能合约了!
5、加载你的 Hello World 智能合约
要加载你的 Hello World 智能合约,你需要它的合约地址和 ABI,如果你完成了前面的教程,你可以remix 里面找到他。然后copy其内容 并在工程里创建 加contract-abi.josn (后面调用时会引用该文件)。
有了我们的合约地址、ABI 和 Alchemy Web3 的EndPoint,我们可以使用contract 方法来加载我们的智能合约的实例。
在interact.js 将您合约 ABI 导入,并添加合约地址。
interact.js 里代码如下
const contractABI = require("../contract-abi.json");
const contractAddress = "0x5f61076C4B174620B5018dBf090a8D967f07c3Bc";
把合约赋予给helloWorldContract变量,并使用我们的 Alchemy Web3 API加载读取合约,代码如下:
export const helloWorldContract = new web3.eth.Contract(
contractABI,
contractAddress
);
所以,interact.js 里的代码现在如下:
require('dotenv').config();
const alchemyKey = process.env.REACT_APP_ALCHEMY_KEY;
const { createAlchemyWeb3 } = require("@alch/alchemy-web3");
const web3 = createAlchemyWeb3(alchemyKey);
const contractABI = require('../contract-abi.json')
const contractAddress = "0x5f61076C4B174620B5018dBf090a8D967f07c3Bc";
export const helloWorldContract = new web3.eth.Contract(
contractABI,
contractAddress
);
6、在您的 interact.js 文件中实现 loadCurrentMessage
完成这些,调用合约的准备工作基本完成了。
接着,我们就进行一个简单的异步 web3 调用来读取我们的合约。
我们靠这个方法 loadCurrentMessage,来存储调用智能合约返回的消息:
更新interact.js 里的这个方法,代码如下:
export const loadCurrentMessage = async () => {
const message = await helloWorldContract.methods.message().call();
return message;
};
由于我们要在 UI 中显示这个智能合约,所以让useEffect我们将HelloWorld.js 组件中的函数更新。
HelloWorld.js 代码如下:
//called only once
useEffect(async () => {
const message = await loadCurrentMessage();
setMessage(message);
}, []);
注意:我们希望我们 loadCurrentMessage 在组件的第一次渲染期间被调用一次。
然后我们实现 addSmartContractListener 在智能合约中的消息更改后自动更新我们链接钱包的状态。
然后 npm instart
http://localhost:3000 (这里虽然调用了合约,但是钱包状态还更新)
7、实现 addSmartContractListener,实现合约中的方法。
回顾一下 HelloWorld.sol 这个合约,我们在里面实现了一个update的函数。 我们就在前端实现这个方法的调用。
使用addSmartContractListener 函数将专门监听我们的 Hello World 智能合约的UpdatedMessages 事件,并更新我们的 UI 以显示新消息。
helloWorld.js
addSmartContractListener 如下:
function addSmartContractListener() {
helloWorldContract.events.UpdatedMessages({}, (error, data) => {
if (error) {
setStatus("😥 " + error.message);
} else {
setMessage(data.returnValues[1]);
setNewMessage("");
setStatus("🎉 Your message has been updated!");
}
});
}
最后,让我们在函数中调用我们的侦听器,以便在组件首次渲染useEffect时对其进行初始化。
HelloWorld.js 文件 代码如下:
useEffect(async () => {
const message = await loadCurrentMessage();
setMessage(message);
addSmartContractListener();
}, []);
8:设置钱包链接到UI
helloworld.js 文件,代码如下:
export const connectWallet = async () => {
if (window.ethereum) {
try {
const addressArray = await window.ethereum.request({
method: "eth_requestAccounts",
});
const obj = {
status: "👆🏽 Write a message in the text-field above.",
address: addressArray[0],
};
return obj;
} catch (err) {
return {
address: "",
status: "😥 " + err.message,
};
}
} else {
return {
address: "",
status: ({" "} 🦊{" "}您必须在浏览器中安装虚拟以太坊钱包 Metamask。), };
} };
这段逻辑是:它会检查您的浏览器是否已启用。
window.ethereum 如果不存在,则表示未安装 Metamask。这会导致返回一个 JSON 对象,其中返回的是一个空字符串,并且JSX 对象中继用户必须安装 Metamask。
window.ethereum address status 如果 存在,那就得到 window.ethereum
9、将 connectWallet 函数添加到您的 HelloWorld.js UI 组件
导航到 中的 connectWalletPressed 函数
HelloWorld.js 方法代码如下:
const connectWalletPressed = async () => {
const walletResponse = await connectWallet();
setStatus(walletResponse.status);
setWallet(walletResponse.address);
};
请注意我们的大部分功能是如何 遵守 MVC 范式的!
HelloWorld.js
interact.js
在connectWalletPressed我们简单地对我们导入的
connectWallet
函数进行等待调用,并使用它的响应,
我们通过它们的状态钩子更新我们的 status 和变量walletAddress
现在,我们保存并测试UI了。
HelloWorld.js
interact.js
页面打开浏览器 ,然后点击页面右上角的“连接钱包”按钮。
如果您安装了 Metamask,系统会提示您将钱包连接到 dApp。 接受连接邀请。
您应该会看到钱包按钮现在反映您的地址已连接!
10、更新getCurrentWalletConnected方法
getCurrentWalletConnected 更新为以下内容:
interact.js
export const getCurrentWalletConnected = async () => {
if (window.ethereum) {
try {
const addressArray = await window.ethereum.request({
method: "eth_accounts",
});
if (addressArray.length > 0) {
return {
address: addressArray[0],
status: "👆🏽 Write a message in the text-field above.",
};
} else {
return {
address: "",
status: "🦊 Connect to Metamask using the top right button.",
};
}
} catch (err) {
return {
address: "",
status: "😥 " + err.message,
};
}
} else {
return {
address: "",
status: ({" "} 🦊{" "}您必须在浏览器中安装虚拟以太坊钱包 Metamask。),
};
} };
这段代码与我们在上一步中刚刚编写的函数非常相似。
主要区别在于,我们调用不是 eth_requestAccounts打开 Metamask 连接钱包,而是调用eth_accounts方法,它只是返回一个数组,其中包含当前连接到我们的 dApp 的 Metamask 地址。
useEffect(async () => {
const message = await loadCurrentMessage();
setMessage(message);
addSmartContractListener();
const {address, status} = await getCurrentWalletConnected();
setWallet(address);
setStatus(status);
}, []);
请注意,我们调用getCurrentWalletConnected 来更新我们的 walletAddress 和 status 状态变量。
现在您已经添加了此代码,让我们尝试刷新我们的浏览器窗口。
11、实现addWalletListener
最后一步是实现钱包监听器,以便我们的 UI 在钱包状态发生变化时能立即更新,例如当用户断开连接或切换帐户时。
在 useEffect 函数中更新:
useEffect(async () => {
const message = await loadCurrentMessage();
setMessage(message);
addSmartContractListener();
const {address, status} = await getCurrentWalletConnected();
setWallet(address)
setStatus(status);
addWalletListener();
}, []);
到这里,我们基本完善了钱包。接下去就是调用合约。
12、实现 合约中的 updateMessage 函数
看到这里,我们已经经到了最后一步!updateMessage (实现调用合约中的方法)
我们将执行以下操作:
interact.js file
1) 先做一个错误校验,确保我们的钱包链接是有效的
updateMessage:
export const updateMessage = async (address, message) => {
if (!window.ethereum || address === null) {
return {
status:
"💡 Connect your Metamask wallet to update the message on the blockchain.",
};
}
if (message.trim() === "") {
return {
status: "❌ Your message cannot be an empty string.",
};
}
};
2)签署交易
将以下内容添加到updateMessage:
//set up transaction parameters
const transactionParameters = {
to: contractAddress, // Required except during contract publications.
from: address, // must match user's active address.
data: helloWorldContract.methods.update(message).encodeABI(),
};
//sign the transaction
try {
const txHash = await window.ethereum.request({
method: "eth_sendTransaction",
params: [transactionParameters],
});
return {
status: (
✅{" "}
View the status of your transaction on Etherscan!
ℹ️ Once the transaction is verified by the network, the message will
be updated automatically.
),
};
} catch (error) {
return {
status: "😥 " + error.message,
};
}
transcation 里面有三个参数
to 指定收件人地址(我们的智能合约地址)
from 指定交易的签名者(就是我们操作的钱包子账户的address)
data 包含对我们的 Hello World 智能合约update 方法的调用,接收我们的``message字符串变量作为输入
完成的 updateMessage 函数应该是这样的:
export const updateMessage = async (address, message) => {
//input error handling
if (!window.ethereum || address === null) {
return {
status:
"💡 Connect your Metamask wallet to update the message on the blockchain.",
};
}
if (message.trim() === "") {
return {
status: "❌ Your message cannot be an empty string.",
};
}
//set up transaction parameters
const transactionParameters = {
to: contractAddress, // Required except during contract publications.
from: address, // must match user's active address.
data: helloWorldContract.methods.update(message).encodeABI(),
};
//sign the transaction
try {
const txHash = await window.ethereum.request({
method: "eth_sendTransaction",
params: [transactionParameters],
});
return {
status: (
✅{" "}
View the status of your transaction on Etherscan!
ℹ️ Once the transaction is verified by the network, the message will
be updated automatically.
),
};
} catch (error) {
return {
status: "😥 " + error.message,
};
}
};
最后,我们需要将我们的功能 updateMessage 到我们的组件 HelloWorld.js
13、最后一步,updateMessage 连接到 HelloWorld.js 前端组件。
我们通过 onUpdatePressed 以反映我们的执行事务是成功还是失败:
const onUpdatePressed = async () => {
const { status } = await updateMessage(walletAddress, newMessage);
setStatus(status);
};
这就完成了
很干净、很简单。
让我们最后测试下!效果如下
这就完成了整个完整的的dApp 🚀🚀🚀🚀🚀🚀🚀🚀
Web3的大门,从此打开了!