一篇傳送門:Web3教程:編寫您的第一個“Hello, World”智能合約(3)
1. 什么是智能合約
2. 部署您的第一個智能合約
3. 什么是氣體
4. 與您的智能合約交互
5. 智能合約的結構
6. 將您的合同提交給 Etherscan
7. 智能合約安全挑戰
8. 將合約集成到前端
9. 其他資源
您終于到了要完成了本教程系列的最后一部分:通過將您的 Hello World 智能合約連接到前端項目并與之交互來創建一個全棧去中心化應用程序 (dApp)。
在本教程結束時,您將知道如何:
? 將 Metamask 錢包連接到您的 dApp 項目
? 使用Alchemy Web3[1] API從您的智能合約中讀取數據
? 使用 Metamask 簽署以太坊交易
對于這個 dApp,我們將使用React[2]作為我們的前端框架;但是,重要的是要注意,我們不會花太多時間分解它的基礎,因為我們將主要專注于將 Web3 功能引入我們的項目。
作為先決條件,你應該對 React 有初級的了解——知道組件、props、useState/useEffect 和基本的函數調用是如何工作的。如果你以前從未聽說過這些術語,我們建議你查看這個React 簡介教程[3]。對于更多的視覺學習者,我們強烈推薦這個很棒的 Net Ninja Full Modern React 教程[4]視頻系列。
我們來這里是為了什么?讓我們開始吧!
首先,轉到hello-world-part-four github 存儲庫[5]以獲取此項目的啟動文件。將此存儲庫克隆到您的本地環境中。
不知道如何克隆存儲庫?從 Github查看本指南。[6]
當您打開這個克隆的hello-world-part-four存儲庫時,您會注意到它包含兩個文件夾:starter-files和completed。
starter files包含該項目的啟動文件(本質上是 React UI)。在本教程中,我們將在此目錄中工作,因為您將學習如何通過將其連接到您的以太坊錢包和您在 Etherscan 上發布的 Hello World 智能合約來使此 UI 栩栩如生第 3 部分。
completed包含完整的完整教程,如果您遇到困難,可以作為參考。
接下來,用您最喜歡的代碼編輯器打開starter-files的拷貝(在 Alchemy,我們是VScode),然后導航到您的src文件夾:
我們將編寫的所有代碼都將存在于該src文件夾下。我們將編輯HelloWorld.js組件和util/interact.js文件來添加我們的項目 Web3 功能。
在我們開始編碼之前,弄清楚啟動文件中已經為我們提供了什么是非常重要的。
讓我們從在瀏覽器中運行 React 項目開始。React 的美妙之處在于,一旦我們的項目在瀏覽器中運行,我們保存的任何更改都會在瀏覽器中實時更新。
要讓項目運行,請導航到文件夾的根目錄starter files,然后npm install在終端中運行以安裝項目的依賴項:
cd starter-files
npm install
完成安裝后,在終端中運行npm start:
npm start
這樣會在您的瀏覽器中打開http://localhost:3000/,您將在其中看到我們項目的前端。它應該由一個字段(一個更新存儲在智能合約中的消息的地方)、一個“連接錢包”按鈕和一個“更新”按鈕組成。
如果您嘗試單擊“連接錢包”或“更新”按鈕,您會發現它們不起作用——那是因為我們仍然需要對它們的功能進行編程!:)
注意:確保您在 starter-files 文件夾中,而不是在completed的文件夾中!
讓我們回到編輯器中的src文件夾并打開HelloWorld.js文件。理解這個文件中的所有內容非常重要,因為它是我們將要處理的主要 React 組件。
在這個文件的頂部,你會注意到我們有幾個 import 語句是讓我們的項目運行所必需的,包括 React 庫、useEffect 和 useState 鉤子,一些其他項./util/interact.js(我們很快會更詳細地描述它們! ),還有Alchemy的logo。
import React from "react";
import { useEffect, useState } from "react";
import {
helloWorldContract,
connectWallet,
updateMessage,
loadCurrentMessage,
getCurrentWalletConnected,
} from "./util/interact.js";
import alchemylogo from "./alchemylogo.svg";
接下來,我們將在特定事件后更新狀態變量。
//State variables
const [walletAddress, setWallet] = useState("");
const [status, setStatus] = useState("");
const [message, setMessage] = useState("No connection to the network.");
const [newMessage, setNewMessage] = useState("");
從未聽說過 React 狀態變量或狀態鉤子?查看這些[7]文檔。
walletAddress- 存儲用戶錢包地址的字符串
status- 存儲有用消息的字符串,指導用戶如何與 dApp 交互
message- 在智能合約中存儲當前消息的字符串
new message- 存儲將寫入智能合約的新消息的字符串
在狀態變量之后,您將看到五個未實現的函數:useEffect、addSmartContractListener、connectWalletPressed和onUpdatePressed。我們將在下面解釋它們的作用:
//called only once
useEffect(async () => { //TODO: implement
}, []);
function addSmartContractListener() { //TODO: implement
}
function addWalletListener() { //TODO: implement
}
const connectWalletPressed = async () => { //TODO: implement
};
const onUpdatePressed = async () => { //TODO: implement
};
useEffect- 這是一個 React 鉤子,在渲染組件后調用。因為它有一個空數組 [] 傳遞給它(見第 4 行),它只會在組件的第一次渲染時被調用。在這里,我們將加載存儲在我們的智能合約中的當前消息,調用我們的智能合約和錢包監聽器,并更新我們的 UI 以反映錢包是否已經連接。
addSmartContractListener- 這個函數設置了一個監聽器,它將監聽我們的 HelloWorld 合約的UpdatedMessages事件,并在我們的智能合約中的消息發生更改時更新我們的 UI。
addWalletListener- 此函數設置一個監聽器,檢測用戶 Metamask 錢包狀態的變化,例如用戶斷開錢包或切換地址時。
connectWalletPressed- 將調用此函數將用戶的 Metamask 錢包連接到我們的 dApp。
onUpdatePressed- 當用戶想要更新存儲在智能合約中的消息時,將調用此函數。
在這個文件的末尾,我們有我們組件的 UI。
如果您仔細瀏覽此代碼,您會注意到我們在 UI 中使用各種狀態變量的位置
在第6-12行,如果用戶的錢包被連接(即walletAddress.length>0),我們在ID為 "walletButton "的按鈕中顯示用戶walletAddress的截斷版本;否則,它只是說 "Connect Wallet."。
在第 17 行,我們顯示存儲在智能合約中的當前消息,該消息被存儲在 message字符串中。
在第 23-26 行,我們使用當文本字段中的輸入發生變化時,控制組件更新我們的newMessage狀態變量。
除了我們的狀態變量,你還會看到,當ID為publishButton和walletButton的按鈕被點擊時,connectWalletPressed和onUpdatePressed函數被調用。
最后讓我們解決這個HelloWorld.js組件在哪里添加。
如果你去看App.js,它是React的主要組件,作為所有其他組件的容器,你會看到我們的HelloWorld.js組件在第7行被注入。
最后但并非最不重要的是,讓我們看看為你提供的另一個文件,interactive.js文件。
因為我們要遵守M-V-C范式,所以我們希望有一個單獨的文件,包含我們所有的函數來管理我們的DApp的邏輯、數據和規則,然后能夠將這些函數輸出到我們的前端(我們的HelloWorld.js組件)。
這就是我們的interactive.js文件的確切目的!
導航到src目錄下的util文件夾,你會注意到我們已經包含了一個名為interactive.js的文件,它將包含我們所有的智能合約交互和錢包函數和變量。
//export const helloWorldContract;
export const loadCurrentMessage = async () => {
};
export const connectWallet = async () => {
};
const getCurrentWalletConnected = async () => {
};
export const updateMessage = async (message) => {
};
你會注意到在文件的頂部,我們已經注釋了helloWorldContract對象。在本教程的后面,我們將取消對這個對象的注釋,并在這個變量中實例化我們的智能合約,然后我們將把它導出到我們的HelloWorld.js組件。
在我們的helloWorldContract對象之后的四個未實現的函數,做了以下事情:
loadCurrentMessage--該函數處理加載存儲在智能合約中的當前消息的邏輯。它將使用Alchemy Web3 API對Hello World智能合約進行讀取調用。
connectWallet- 此函數將連接用戶的Metamask到我們的DApp。
getCurrentWalletConnected- 此函數將在頁面加載時檢查Ethereum賬戶是否已經連接到我們的dApp,并相應地更新我們的UI。
updateMessage 這個函數將更新存儲在智能合約中的消息。它將對Hello World智能合約進行寫入調用,因此用戶的Metamask錢包將不得不簽署一個以太坊交易來更新消息。
現在,我們明白了我們的工作內容,讓我們來弄清楚如何從我們的智能合約中讀取信息吧
要從你的智能合約中讀取,你需要成功設置。
這聽起來可能有很多步驟,但不要擔心!我們將指導你如何完成這些步驟。我們會一步一步地指導你如何做每一個步驟! :)
還記得在本教程的第二部分,我們是如何使用Alchemy Web3密鑰來讀取我們的智能合約[8]的嗎?你也需要一個Alchemy Web3密鑰在你的DApp中從鏈上讀取數據。
如果您還沒有安裝Alchemy Web3,請首先安裝它。方法是瀏覽您的啟動文件的根目錄,并在您的終端上運行以下內容啟動文件并在您的終端上運行以下內容
npm install @alch/alchemy-web3
Alchemy Web3[9]是一個圍繞Web3.js[10]的封裝器,提供了增強的API方法和其他重要的好處,使你作為一個Web3開發者的生活更容易。它的設計需要最小的配置,所以你可以立即開始在你的應用程序中使用它
然后,在你的項目目錄中安裝dotenv[11]包,這樣我們就有一個安全的地方來存儲我們的API密鑰,在我們獲取它之后。
npm install dotenv --save
對于我們的DApp,我們將使用我們的Websockets API密鑰,而不是我們的HTTP API密鑰,因為它將允許我們設置一個監聽器,以檢測存儲在智能合約中的消息何時發生變化。
一旦你有了你的API密鑰,在你的根目錄下創建一個.env文件,并將你的Alchemy Websockets網址添加到其中。之后,你的.env文件應該看起來像這樣。
REACT_APP_ALCHEMY_KEY = wss://eth-goerli.ws.alchemyapi.io/v2/
現在,我們已經準備好在我們的dApp中設置我們的Alchemy Web3端點了! 讓我們回到嵌套在util文件夾中的interactive.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;
上面,我們首先從我們的.env文件中導入了Alchemy ket,然后將我們的alchemyKey傳遞給createAlchemyWeb3以建立我們的Alchemy Web3端點。
準備好這個端點后,就可以加載我們的智能合約了。
要加載您的Hello World智能合約,您需要它的合約地址和ABI,如果您完成了本教程的第三部分,就可以在Etherscan上找到這兩個信息。
一旦你有了你的API密鑰,在你的根目錄下創建一個.env文件,并將你的Alchemy Websockets網址添加到其中。之后,你的.env文件應該看起來像這樣。
REACT_APP_ALCHEMY_KEY = wss://eth-goerli.ws.alchemyapi.io/v2/
現在,我們已經準備好在我們的dApp中設置我們的Alchemy Web3端點了! 讓我們回到嵌套在util文件夾中的interactive.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;
上面,我們首先從我們的.env文件中導入了Alchemy ket,然后將我們的alchemyKey傳遞給createAlchemyWeb3以建立我們的Alchemy Web3端點。
準備好這個端點后,就可以加載我們的智能合約了。
要加載您的Hello World智能合約,您需要它的合約地址和ABI,如果您完成了本教程的第三部分,就可以在Etherscan上找到這兩個信息。
如果你跳過了本教程的第三部分,你可以使用地址為0x6f3f635A9762B47954229Ea479b4541eAF402A6A[12]的HelloWorld合約。它的ABI可以在這里找到[13]。
有了我們的合約地址、ABI和Alchemy Web3端點,我們可以使用合約方法來加載我們的智能合約實例。將你的合約ABI導入interactive.js文件,并添加你的合約地址。
const contractABI = require("../contract-abi.json");
const contractAddress = "0x6f3f635A9762B47954229Ea479b4541eAF402A6A";
現在我們終于可以取消對helloWorldContract變量的注釋,并使用我們的AlchemyWeb3端點加載智能合約。
export const helloWorldContract = new web3.eth.Contract(
contractABI,
contractAddress
);
回顧一下,你的interactive.js的前12行現在應該是這樣的。
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 = "0x6f3f635A9762B47954229Ea479b4541eAF402A6A";
export const helloWorldContract = new web3.eth.Contract(
contractABI,
contractAddress
);
現在,我們已經加載了我們的合同,我們可以實現我們的loadCurrentMessage函數了。
這個函數超級簡單。就像我們在本教程系列的第二部分所做的那樣,在這里我們要做一個簡單的異步web3調用來讀取我們的合同。我們的函數將返回存儲在智能合約中的消息。
將你的interactive.js文件中的loadCurrentMessage更新為以下內容。
export const loadCurrentMessage = async () => {
const message = await helloWorldContract.methods.message().call();
return message;
};
由于我們想在我們的用戶界面中顯示這個智能合約,讓我們把HelloWorld.js組件中的useEffect函數更新為以下內容。
//called only once
useEffect(async () => {
const message = await loadCurrentMessage();
setMessage(message);
}, []);
注意,我們希望我們的loadCurrentMessage在組件的第一次渲染時被調用一次。我們很快就會實現addSmartContractListener,以便在智能合約中的消息改變后自動更新用戶界面。
在我們深入研究我們的監聽器之前,讓我們看看我們目前所擁有的東西吧 保存你的HelloWorld.js和interactive.js文件,然后轉到http://localhost:3000/
你會注意到,當前的消息不再說 "沒有連接到網絡"。相反,它反映了存儲在智能合約中的信息。有病啊!
現在說說那個監聽器...
如果你回想一下我們在本教程系列第一部分中寫的HelloWorld.sol文件,你會記得有一個名為UpdateMessages的智能合約事件,在我們的智能合約的更新函數被調用后被發射出來(見第9和27行)。
// Specifies the version of Solidity, using semantic versioning.
// Learn more: https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma
pragma solidity ^0.7.3;
// Defines a contract named `HelloWorld`.
// A contract is a collection of functions and data (its state). Once deployed, a contract resides at a specific address on the Ethereum blockchain. Learn more: https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html
contract HelloWorld {
//Emitted when update function is called
//Smart contract events are a way for your contract to communicate that something happened on the blockchain to your app front-end, which can be 'listening' for certain events and take action when they happen.
event UpdatedMessages(string oldStr, string newStr);
// Declares a state variable `message` of type `string`.
// State variables are variables whose values are permanently stored in contract storage. The keyword `public` makes variables accessible from outside a contract and creates a function that other contracts or clients can call to access the value.
string public message;
// Similar to many class-based object-oriented languages, a constructor is a special function that is only executed upon contract creation.
// Constructors are used to initialize the contract's data. Learn more:https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors
constructor(string memory initMessage) {
// Accepts a string argument `initMessage` and sets the value into the contract's `message` storage variable).
message = initMessage;
}
// A public function that accepts a string argument and updates the `message` storage variable.
function update(string memory newMessage) public {
string memory oldMsg = message;
message = newMessage;
emit UpdatedMessages(oldMsg, newMessage);
}
}
智能合約事件是你的合約向你的前端應用程序傳達區塊鏈上發生的事情(即有一個事件)的一種方式,它可以 "監聽 "特定的事件并在它們發生時采取行動。
addSmartContractListener函數將專門監聽我們的Hello World智能合約的UpdateMessages事件,并更新我們的用戶界面以顯示新消息。
將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!");
}
});
}
如果事件發出時發生了錯誤,它將通過我們的狀態變量反映在用戶界面中。
否則,我們將使用返回的數據對象。data.returnValues是一個索引為0的數組,數組中的第一個元素存儲了之前的消息,第二個元素存儲了更新后的消息。總之,在一個成功的事件中,我們將把我們的消息字符串設置為更新的消息,清除newMessage字符串,并更新我們的狀態變量以反映新的消息已經在我們的智能合約上發布。
最后,讓我們在useEffect函數中調用我們的監聽器,以便它在HelloWorld.js組件第一次渲染時被初始化。總的來說,你的useEffect函數應該是這樣的。
useEffect(async () => {
const message = await loadCurrentMessage();
setMessage(message);
addSmartContractListener();
}, []);
現在我們已經能夠從我們的智能合約中讀取信息了,如果能弄清楚如何向它寫入信息,那就更好了!然而,要向我們的DApp寫入信息,我們必須首先有一個連接到Ethereum錢包。然而,要寫到我們的DApp,我們必須首先有一個以太坊錢包連接到它。
因此,接下來我們將解決設置我們的Ethereum錢包(Metamask),然后將其連接到我們的DApp上!
要向以太坊鏈寫入任何東西,用戶必須使用他們的虛擬錢包的私鑰簽署交易。在本教程中,我們將使用Metamask[14],一個用于管理你的以太坊賬戶地址的瀏覽器中的虛擬錢包,因為它使這種交易簽署對終端用戶來說超級簡單。
如果你想了解更多關于以太坊交易的運作方式,請查看以太坊基金會[15]的這個頁面。
你可以在這里免費下載并創建一個Metamask賬戶[16]。當你在創建賬戶時,或者如果你已經有一個賬戶,確保切換到右上方的 "Ropsten測試網絡"(這樣我們就不會和真實的錢打交道)。
為了在以太坊區塊鏈上簽署交易,我們需要一些假的Eth。要獲得Eth,你可以去Ropsten faucet[17],輸入你的Ropsten賬戶地址,然后點擊 "發送Ropsten Eth"。你應該很快在你的Metamask賬戶中看到Eth!
為了仔細檢查我們的余額,讓我們使用Alchemy’s composer tool[18]做一個https://docs.alchemyapi.io/alchemy/documentation/alchemy-api-reference/json-rpc#eth_getbalance請求。這將返回我們錢包中的Eth數量。在你輸入你的Metamask賬戶地址并點擊 "發送請求 "后,你應該看到這樣的響應。
{"jsonrpc": "2.0", "id": 0, "result": "0xde0b6b3a7640000"}
注意:這個結果是以wei而不是eth為單位的。Wei被用作最小的以太坊面額。從wei到eth的轉換是:1 eth = 101? wei。因此,如果我們將0xde0b6b3a7640000轉換為十進制,我們得到1*101?,等于1 eth。
唷! 我們的假錢都在這里了!
現在我們的Metamask錢包已經設置好了,讓我們把我們的DApp連接到它!
在我們的interactive.js文件中,讓我們實現connectWallet函數,然后我們可以在HelloWorld.js組件中調用它。
讓我們把connectWallet修改為以下內容。
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: (
<span>
<p>
{" "}
{" "}
<a target="_blank" href={`https://metamask.io/download.html`}>
You must install Metamask, a virtual Ethereum wallet, in your
browser.
</a>
</p>
</span>
),
};
}
};
那么,這個巨大的代碼塊到底是做什么的?
嗯,首先,它檢查window.ethereum是否在你的瀏覽器中啟用。
window.ethereum是由Metamask和其他錢包提供商注入的全球API,允許網站請求用戶的以太坊賬戶。如果被批準,它可以從用戶連接的區塊鏈中讀取數據,并建議用戶簽署信息和交易. 請查看Metamask文檔[19]以了解更多信息!
如果window.ethereum不存在,那么就意味著Metamask沒有安裝。這將導致一個JSON對象被返回,其中返回的地址是一個空字符串,而狀態JWX對象轉達了用戶必須安裝Metamask。
現在,如果window.ethereum存在,那么事情就變得有趣了。
使用try/catch循環,我們將嘗試通過調用window.ethereum.request({ method: "eth_requestAccounts" })連接到Metamask;調用此函數將在瀏覽器中打開Metamask,用戶將被提示將他們的錢包連接到你的dApp。
如果用戶選擇了連接,method: "eth_requestAccounts"將返回一個數組,其中包含所有連接到dApp的用戶的賬戶地址。總的來說,我們的connectWallet函數將返回一個JSON對象,其中包含這個數組中的第一個地址(見第9行)和一個統計信息,提示用戶寫一個信息給智能合約。
如果用戶拒絕連接,那么JSON對象將包含一個返回地址的空字符串和一個反映用戶拒絕連接的狀態消息。
現在我們已經寫好了這個connectWallet函數,下一步是將其調用到我們的HelloWorld.js組件。
導航到HelloWorld.js中的connectWalletPressed函數,并將其更新為以下內容。
const connectWalletPressed = async () => {
const walletResponse = await connectWallet();
setStatus(walletResponse.status);
setWallet(walletResponse.address);
};
注意到我們的大部分功能是如何從interactive.js文件中抽象出來給我們的HelloWorld.js組件的嗎?這是為了讓我們符合M-V-C的范式!
在connectWalletPressed中,我們簡單地對導入的connectWallet函數進行等待調用,并使用其響應,通過其狀態鉤子更新我們的狀態和walletAddress變量。
現在,讓我們保存這兩個文件(HelloWorld.js和interactive.js)并測試一下我們到目前為止的用戶界面。
在http://localhost:3000/ 頁面上打開你的瀏覽器,然后按下頁面右上方的 "連接錢包 "按鈕。
如果你安裝了Metamask,你應該被提示將你的錢包連接到你的dApp。接受連接的邀請。
您應該看到錢包按鈕現在反映出您的地址已被連接! Yasssss
接下來,嘗試刷新頁面...這很奇怪。我們的錢包按鈕正在提示我們連接Metamask,盡管它已經連接了...
重新加載時的問題頁面
然而,不用擔心! 我們可以通過實現getCurrentWalletConnected來解決這個問題(明白嗎? ),它將檢查一個地址是否已經連接到我們的DApp,并相應地更新我們的用戶界面
將 interact.js 文件中的 getCurrentWalletConnected 函數更新為以下內容。
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: (
<span>
<p>
{" "}
{" "}
<a target="_blank" href={`https://metamask.io/download.html`}>
You must install Metamask, a virtual Ethereum wallet, in your
browser.
</a>
</p>
</span>
),
};
}
};
這段代碼與我們在上一步剛剛寫好的connectWallet函數非常相似。
主要的區別是,我們沒有調用方法eth_requestAccounts(打開Metamask讓用戶連接他們的錢包),而是在這里調用方法eth_accounts,它只是返回一個包含當前連接到我們dApp的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和狀態變量。
現在你已經添加了這些代碼,讓我們試著刷新我們的瀏覽器窗口。
很好! 這個按鈕應該說你已經連接上了,并顯示你所連接的錢包的地址預覽--即使在你刷新之后也是如此
dApp錢包設置的最后一步是實現錢包監聽器,這樣當我們的錢包狀態改變時,我們的用戶界面就會更新,例如當用戶斷開連接或切換賬戶時。
在你的HelloWorld.js文件中,修改你的addWalletListener函數如下。
我打賭你甚至不需要我們的幫助來理解這里發生了什么,但為了徹底了解,讓我們快速分解它。
首先,我們的函數檢查window.ethereum是否被啟用(即Metamask被安裝)。
如果沒有,我們只需將我們的狀態變量設置為一個JSX字符串,提示用戶安裝Metamask。
如果它被啟用,我們在第3行設置監聽器window.ethereum.on("accountsChanged"),監聽Metamask錢包的狀態變化,其中包括當用戶連接一個額外的帳戶到DApp,切換帳戶,或斷開一個帳戶。如果至少有一個賬戶被連接,walletAddress狀態變量將被更新為監聽器返回的賬戶陣列中的第一個賬戶。否則,walletAddress被設置為一個空字符串。
最后但同樣重要的是,我們必須在我們的useEffect函數中調用它。
useEffect(async () => {
const message = await loadCurrentMessage();
setMessage(message);
addSmartContractListener();
const {address, status} = await getCurrentWalletConnected();
setWallet(address)
setStatus(status);
addWalletListener();
}, []);
就這樣了! 我們已經成功地完成了所有的錢包功能的編程! 現在進入我們的最后一項任務:更新存儲在我們的智能合約中的消息
好了,家人,我們已經到了最后的關頭了 在你的interactive.js文件的updateMessage中,我們要做以下工作。
確保我們希望在我們的智能聯系人中發布的信息是有效的
使用Metamask簽署我們的交易
從我們的HelloWorld.js前臺組件中調用這個函數
自然地,在函數的開始有某種輸入錯誤處理是有意義的。
如果沒有安裝Metamask擴展,沒有連接錢包(即傳入的地址是一個空字符串),或者消息是一個空字符串,我們希望我們的函數提前返回。讓我們為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.",
};
}
};
現在,它有適當的輸入錯誤處理,是時候通過Metamask簽署交易了
如果你對傳統的web3以太坊交易已經很熟悉了,那么我們接下來寫的代碼將非常熟悉。在你的輸入錯誤處理代碼下面,給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,
};
}
讓我們來分析一下發生了什么。首先,我們設置了我們的交易參數,其中。
to 指定收件人地址(我們的智能合約)。
from 指定交易的簽署者,即我們傳入函數的地址變量
data包含對我們的Hello World智能合約的更新方法的調用,接收我們的messagestring變量作為輸入。
然后,我們進行一個等待調用,window.ethereum.request,在這里我們要求Metamask簽署交易。注意,在第11行和第12行,我們指定了我們的eth方法,eth_sendTransaction,并傳入我們的transactionParameters。
在這一點上,Metamask將在瀏覽器中打開,并提示用戶簽署或拒絕交易。
總的來說,我們的 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組件。
我們的onUpdatePressed函數應該對導入的updateMessage函數進行等待調用,并修改狀態變量以反映我們的交易是成功還是失敗。
const onUpdatePressed = async () => {
const { status } = await updateMessage(walletAddress, newMessage);
setStatus(status);
};
這是超級干凈和簡單的。 你猜怎么著......你的DAPP已經完成了!!。
讓我們測試一下 "更新 "按鈕!
Wooooo,你已經走到了本教程的最后! 回顧一下,你學會了如何。
現在,您已經完全有能力應用本教程中的技能來構建您自己的自定義dApp項目了 如果您有任何問題,請不要猶豫,在Alchemy Discord中向我們尋求幫助。♂?
您覺得我們的智能合約介紹如何?
還有很多東西要學。特別是,扎實掌握 Solidity 的基礎知識并使用以太坊中一些最常見的框架進行智能合約開發會非常有用。
我們建議檢查以下工具:
譯者注: 文章代碼較多,如果疑問,請查看github源碼:https://github.com/alchemyplatform/hello-world-part-four-tutorial。 文章原文鏈接:https://www.web3.university/tracks/create-a-smart-contract/integrate-your-smart-contract-with-a-frontend
[1] 使用Alchemy Web3: https://docs.alchemy.com/alchemy/documentation/alchemy-web3
[2] React: https://reactjs.org/
[3] React 簡介教程: https://reactjs.org/tutorial/tutorial.html
[4] Full Modern React 教程: https://www.youtube.com/playlist?list=PL4cUxeGkcC9gZD-Tvwfod2gaISzfRiP9d
[5] hello-world-part-four github 存儲庫: https://github.com/alchemyplatform/hello-world-part-four-tutorial
[6] 本指南。: https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository
[7] 這些: https://reactjs.org/docs/hooks-state.html
[8] 使用Alchemy Web3密鑰來讀取我們的智能合約: https://docs.alchemy.com/alchemy/tutorials/hello-world-smart-contract/interacting-with-a-smart-contract#step-1-install-web3-library
[9] Alchemy Web3: https://github.com/alchemyplatform/alchemy-web3
[10] Web3.js: https://web3js.readthedocs.io/en/v1.2.9/
[11] dotenv: https://www.npmjs.com/package/dotenv
[12] 0x6f3f635A9762B47954229Ea479b4541eAF402A6A: https://ropsten.etherscan.io/address/0x6f3f635a9762b47954229ea479b4541eaf402a6a#code
[13] 它的ABI可以在這里找到: https://ropsten.etherscan.io/address/0x6f3f635a9762b47954229ea479b4541eaf402a6a#code
[14] Metamask: https://metamask.io/
[15] 以太坊基金會: https://ethereum.org/en/developers/docs/transactions/
[16] 免費下載并創建一個Metamask賬戶: https://metamask.io/download.html
[17] Ropsten faucet: https://faucet.ropsten.be/
[18] Alchemy’s composer tool: https://composer.alchemyapi.io/?composer_state={"network"%3A0%2C"methodName"%3A"eth_getBalance"%2C"paramValues"%3A[""%2C"latest"]}
[19] Metamask文檔: https://docs.metamask.io/guide/ethereum-provider.html#table-of-contents
[20] Solidity 示例:: https://solidity-by-example.org/
[21] OpenZeppelin Docs:: https://docs.openzeppelin.com/
[22] Scaffold-Eth:: https://github.com/scaffold-eth/scaffold-eth
象描述
真正意義上的inline-block水平呈現的元素間,換行顯示或空格分隔的情況下會有間距,很簡單的個例子:
兩個相鄰的radio之間總是多那么一點點的間距,不是margin也不是padding,但是設計妹紙不干了!!!
我們使用CSS更改非inline-block水平元素為inline-block水平,也會有該問題:
.space a { display: inline-block; padding: .5em 1em; background-color: #cad5eb; } <div class="space"> <a href="##">惆悵</a> <a href="##">淡定</a> <a href="##">熱血</a> </div>
這種表現是符合規范的應該有的表現
不過,這類間距有時會對我們布局,或是兼容性處理產生影響,需要去掉它,該怎么辦呢?以下展示N種方法(歡迎補充)!
元素間留白間距出現的原因就是標簽段之間的空格,因此,去掉HTML中的空格,自然間距就木有了。考慮到代碼可讀性,顯然連成一行的寫法是不可取的,我們可以:
<div class="space"> <a href="##"> 惆悵</a><a href="##"> 淡定</a><a href="##"> 熱血</a> </div>
或者是:
<div class="space"> <a href="##">惆悵</a ><a href="##">淡定</a ><a href="##">熱血</a> </div>
或者是借助HTML注釋:
<div class="space"> <a href="##">惆悵</a><!-- --><a href="##">淡定</a><!-- --><a href="##">熱血</a> </div>
.space a { display: inline-block; margin-right: -3px; }
margin負值的大小與上下文的字體和文字大小相關,其中,間距對應大小值可以參見我之前“基于display:inline-block的列表布局”一文part 6的統計表格:
例如,對于12像素大小的上下文,Arial字體的margin負值為-3像素,Tahoma和Verdana就是-4像素,而Geneva為-6像素。
由于外部環境的不確定性,以及最后一個元素多出的父margin值等問題,這個方法不適合大規模使用。
如下處理:
<div class="space"> <a href="##">惆悵 <a href="##">淡定 <a href="##">熱血</a> </div>
注意,為了向下兼容IE6/IE7等喝蒙牛長大的瀏覽器,最后一個列表的標簽的結束(閉合)標簽不能丟。
在HTML5中,我們直接:
<div class="space"> <a href="##">惆悵 <a href="##">淡定 <a href="##">熱血 </div>
好吧,雖然感覺上有點怪怪的,但是,這是OK的。
您可以狠狠地點擊這里:無閉合標簽去除inline-block元素間距demo
類似下面的代碼:
.space { font-size: 0; } .space a { font-size: 12px; }
這個方法,基本上可以解決大部分瀏覽器下inline-block元素之間的間距(IE7等瀏覽器有時候會有1像素的間距)。不過有個瀏覽器,就是Chrome, 其默認有最小字體大小限制,因為,考慮到兼容性,我們還需要添加:
類似下面的代碼:
.space { font-size: 0; -webkit-text-size-adjust:none; }
您可以狠狠地點擊這里(去年制作的一個簡單demo):font-size:0清除換行符間隙demo
補充:根據小杜在評論中中的說法,目前Chrome瀏覽器已經取消了最小字體限制。因此,上面的-webkit-text-size-adjust:none;代碼估計時日不多了。
類似下面的代碼:
.space { letter-spacing: -3px; } .space a { letter-spacing: 0; }
根據我去年的測試,該方法可以搞定基本上所有瀏覽器,包括吃“東鞋”、“西毒(膠囊)”、“南地(溝油)”、“北鈣(三鹿)”的IE6/IE7瀏覽器,不過Opera瀏覽器下有蛋疼的問題:最小間距1像素,然后,letter-spacing再小就還原了。
類似下面代碼:
.space { word-spacing: -6px; } .space a { word-spacing: 0; }
一個是字符間距(letter-spacing)一個是單詞間距(word-spacing),大同小異。據我測試,word-spacing的負值只要大到一定程度,其兼容性上的差異就可以被忽略。因為,貌似,word-spacing即使負值很大,也不會發生重疊。
您可以狠狠地點擊這里:word-spacing與元素間距去除demo
與上面demo一樣的效果,這里就不截圖展示了。如果您使用Chrome瀏覽器,可能看到的是間距依舊存在。確實是有該問題,原因我是不清楚,不過我知道,可以添加display: table;或display:inline-table;讓Chrome瀏覽器也變得乖巧。
.space { display: inline-table; word-spacing: -6px; }
下面展示的是YUI 3 CSS Grids 使用letter-spacing和word-spacing去除格柵單元見間隔方法(注意,其針對的是block水平的元素,因此對IE8-瀏覽器做了hack處理):
.yui3-g { letter-spacing: -0.31em; /* webkit */ *letter-spacing: normal; /* IE < 8 重置 */ word-spacing: -0.43em; /* IE < 8 && gecko */ } .yui3-u { display: inline-block; zoom: 1; *display: inline; /* IE < 8: 偽造 inline-block */ letter-spacing: normal; word-spacing: normal; vertical-align: top; }
以下是一個名叫RayM的人提供的方法:
li { display:inline-block; background: orange; padding:10px; word-spacing:0; } ul { width:100%; display:table; /* 調教webkit*/ word-spacing:-1em; } .nav li { *display:inline;}
也就是上面一系列CSS方法的組組合合。
結語
其他去除間距的方法肯定還有,歡迎大家通過評論方式進行補充。上文部分方法可能有測試不周全之處,因此,部分細節上可能會有紕漏,歡迎指正。
最后,推薦幾本書
開始
? 讓我們構建一些很酷的東西。
? 什么是 DAO?
為 DAO 設置客戶端應用程序
? 獲取客戶端代碼 + 獲取設置。
? 將“連接到錢包”添加到您的 DAO 儀表板。
創建會員 NFT
? 部署你的 NFT 包。
? 部署 NFT 元數據。
? 讓用戶鑄造你的 NFT。
創建代幣+治理
? 部署 ERC-20 合約。
? 在 DAO Dashboard 上展示代幣持有者。
? ??? 建庫+治理。
? ? 讓用戶對提案進行投票。
收尾工作
? 刪除您的管理員權限并處理基本錯誤。
? 完成并慶祝。
獲取客戶端代碼 + 獲取設置。
我們將為人們構建一個網絡應用程序:連接他們的錢包 → 鑄造會員 NFT → 接收代幣空投 → 并對提案進行實際投票。Web 應用程序就是我所說的“DAO 儀表板”。這是我們的新成員可以加入的地方,它允許現有成員查看 DAO 正在做什么。
我們將使用這個叫做 Replit[1]的東西!它是一個基于瀏覽器的 IDE,可讓我們輕松構建 Web 應用程序并從瀏覽器中部署它們。這是很合法的。無需設置完整的本地環境并編寫命令進行部署,這一切都交給了我們。
在繼續之前在 Replit 上注冊一個帳戶。
我已經創建了一個基本的反應項目,你可以 在Replit 上創建它。 只要去 這里[2],在右邊你會看到“fork”按鈕。 確保您已登錄,然后單擊此按鈕。
您將在瀏覽器中克隆我的 repo 和完整的 IDE 以使用代碼。一旦它停止加載并顯示一些代碼。單擊頂部的“運行”,一切順利。
這是我在過去的項目中制作的關于 Replit 的視頻:
https://www.loom.com/share/8e8f47eacf6d448eb5d25b6908021035
如果您不想使用 Replit,則不必這樣做。
首先前往此處[3] ,您可以在此處找到起始代碼庫代碼。從這里你要確保你按下頁面右上角的“Fork”按鈕:
很好! 當你分叉這個 repo 時,你實際上是在創建一個與之相同的副本,該副本存在于你的 Github 檔案下。所以現在你有了自己版本的代碼,你可以隨心所欲地編輯。
這里的最后一步是在你的本地機器上實際獲得你新分叉的 repo。點擊 "代碼 "按鈕并復制該鏈接!
到你的終端,然后cd到你的項目所在的任何目錄。例如,我喜歡把我的項目克隆到我的Developer文件夾。這要看你喜歡什么了! 一旦你找到了位置,只需通過運行來克隆 repo。
git clone YOUR_FORKED_LINK
cd buildspace-dao-starter
就這樣了! 從那里開始,繼續運行:
npm install
然后:
npm start
接下來我們需要一個以太坊錢包。有很多這樣的,但是,對于這個項目,我們將使用 Metamask。下載瀏覽器擴展并在此處[4]設置您的錢包 。即使您已經有另一個錢包提供商,現在也只需使用 Metamask。
但是為什么我們需要 Metamask?
出色地。我們需要能夠調用位于區塊鏈上的智能合約上的函數。而且,要做到這一點,我們需要有一個包含我們的以太坊地址和私鑰的錢包。
這幾乎就像身份驗證一樣。我們需要一些東西來“登錄”到區塊鏈,然后使用這些登錄憑據從我們的網站發出 API 請求。
因此,為了讓我們的網站與區塊鏈對話,我們需要以某種方式將我們的錢包連接到它。一旦我們將錢包連接到我們的網站,我們的網站將有權代表我們調用智能合約。 請記住,這就像對網站進行身份驗證一樣。
所以,繼續設置吧!他們的設置流程非常不言自明。
設置好錢包后,請務必將網絡切換到“ Goerli ”,這是我們將使用的測試網絡。
那里有一些測試網,我們將使用一個名為“Goerli”的測試網,由以太坊基金會運營。
為了部署到 Goerli,我們需要假 ETH。為什么?因為如果您要部署到實際的以太坊主網,您將使用真錢!因此,測試網復制了主網的工作方式,唯一的區別是不涉及真金白銀。
為了獲得假 ETH,我們必須向網絡索取一些。這個假的 ETH 只能在這個特定的測試網上工作。你可以通過水龍頭為 Goerli 獲取一些假的以太坊。你只需要找到一個有效的大聲笑。
對于 MyCrypto,您需要連接您的錢包,創建一個帳戶,然后再次單擊同一鏈接以請求資金。對于Goerli官方水龍頭,如果您登錄您的Alchemy帳戶,您應該可以獲得2倍的金額。
您有幾個水龍頭可供選擇:
姓名關聯數量時間mycryptohttps://app.mycrypto.com/faucet[5]0.01沒有goerlihttps://goerlifaucet.com/[6]0.2524小時chainlinkhttps://faucets.chain.link/goerli[7]0.1沒有
您可以在此處[8]找到您的公共地址。
一旦你的交易被挖掘出來,你的錢包里就會有一些假的 ETH。
因此,為了讓我們的網站與區塊鏈交互,我們需要以某種方式將我們的錢包連接到它。一旦我們將錢包連接到我們的網站,我們的網站將有權代表我們調用智能合約。 請記住,這就像對網站進行身份驗證一樣。
您過去可能已經構建了“連接到錢包”按鈕!這一次,我們將使用thirdweb 的前端SDK,這讓它變得非常簡單。
轉到您的 React 應用程序的src/index.js并添加以下代碼:
import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';
// Import thirdweb provider and Goerli ChainId
import { ThirdwebProvider } from '@thirdweb-dev/react';
import { ChainId } from '@thirdweb-dev/sdk';
// This is the chainId your dApp will work on.
const activeChainId = ChainId.Goerli;
// Wrap your app with the thirdweb provider
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
<React.StrictMode>
<ThirdwebProvider desiredChainId={activeChainId}>
<App />
</ThirdwebProvider>
</React.StrictMode>,
);
很簡單。我們正在導入thirdweb,然后指定我們正在處理的鏈的chainId,即Goerli!。
然后我們用 <ThirdwebProvider>包裝所有內容,該provider保存用戶經過身份驗證的錢包數據(如果他們之前已連接到我們的網站)并將其傳遞給App.
注意:如果你以前在 dapps 上工作過,或者如果你以前連接過它,請確保你在 Metamask 上斷開你的錢包與https://localhost:3000[9]的連接。
如果您前往您的網絡應用程序,您會看到一個空白的紫色頁面。讓我們添加一些基本的副本 + 一個按鈕,讓用戶連接他們的錢包。
前往App.jsx。添加以下代碼。
import { useAddress, ConnectWallet } from '@thirdweb-dev/react';
const App = () => {
// Use the hooks thirdweb give us.
const address = useAddress();
console.log(" Address:", address);
// This is the case where the user hasn't connected their wallet
// to your web app. Let them call connectWallet.
if (!address) {
return (
<div className="landing">
<h1>Welcome to NarutoDAO</h1>
<div className="btn-hero">
<ConnectWallet />
</div>
</div>
);
}
// This is the case where we have the user's address
// which means they've connected their wallet to our site!
return (
<div className="landing">
<h1> wallet connected, now what!</h1>
</div>);
}
export default App;
相當簡單!順便說一句 -如果您在本地運行,請確保您的 Web 應用程序正在使用npm start運行。
現在,當您轉到您的網絡應用程序并單擊連接到錢包時,您會看到它彈出 Metamask!授權您的錢包后,您將看到以下屏幕:
很好。如果你去你的控制臺,你會看到它打印出了你的公共地址。如果您在此處刷新頁面,您會看到我們的錢包連接仍然還存在。
如果您過去已經建立了與錢包的連接,您會注意到使用 thirdweb 的客戶端 SDK 變得更加容易,因為它可以為您處理常見的邊緣情況(例如,在變量中維護用戶錢包的狀態)。
順便說一句 - 我在這里<h1>Welcome to NarutoDAO</h1>,請把它變成你自己的。不要模仿我!這是你自己的 DAO!
注意:如果您想測試用戶沒有連接錢包的情況,也可以隨時斷開您的網站與 Metamask 的連接。[10]
Github源碼:https://github.com/buildspace/buildspace-projects/tree/main/JS_DAO/
原文鏈接:https://buildspace.so/p/build-dao-with-javascript
[1] Replit: https://replit.com/~?utm_source=buildspace.so&utm_medium=buildspace_project
[2] 這里: https://replit.com/@thirdweb/buildspace-dao-starter-v3?utm_source=buildspace.so&utm_medium=buildspace_project
[3] 此處: https://github.com/buildspace/buildspace-dao-starter?utm_source=buildspace.so&utm_medium=buildspace_project
[4] 下載瀏覽器擴展并在此處: https://metamask.io/download.html?utm_source=buildspace.so&utm_medium=buildspace_project
[5] https://app.mycrypto.com/faucet: https://app.mycrypto.com/faucet?utm_source=buildspace.so&utm_medium=buildspace_project
[6] https://goerlifaucet.com/: https://goerlifaucet.com/?utm_source=buildspace.so&utm_medium=buildspace_project
[7] https://faucets.chain.link/goerli: https://faucets.chain.link/goerli?utm_source=buildspace.so&utm_medium=buildspace_project
[8] 您可以在此處: https://metamask.zendesk.com/hc/en-us/articles/360015289512-How-to-copy-your-MetaMask-account-public-address-?utm_source=buildspace.so&utm_medium=buildspace_project
[9] https://localhost:3000: https://localhost:3000/?utm_source=buildspace.so&utm_medium=buildspace_project
[10] 斷開您的網站與 Metamask 的連接。: https://metamask.zendesk.com/hc/en-us/articles/360059535551-Disconnect-wallet-from-Dapp?utm_source=buildspace.so&utm_medium=buildspace_project
*請認真填寫需求信息,我們會在24小時內與您取得聯系。