+ {JSON.stringify(attrs.object, null, 2)}
+```
+
+大部分的欄位都是使用 [`JSON.stringify`](https://www.w3schools.com/js/js_json_stringify.asp) 來顯示的。
+
+```tsx
+
+ { funs.length > 0 &&
+ <>
+ 函式:
+ {message}
+ +{status}
+ + ++ {" "} + 🦊 + 您必須在瀏覽器中安裝 MetaMask,這是一款虛擬以太坊錢包。 + +
+ + ), + } + } +} +``` + +那麼這段龐大的程式碼究竟做了什麼? + +首先,它會檢查您的瀏覽器中是否啟用了 `window.ethereum`。 + +`window.ethereum` 是由 MetaMask 和其他錢包提供者注入的全域 API,允許網站請求使用者的以太坊帳戶。 如果獲得批准,它可以從使用者連線的區塊鏈讀取資料,並建議使用者簽署訊息和交易。 查看 [MetaMask 文件](https://docs.metamask.io/guide/ethereum-provider.html#table-of-contents)以獲得更多資訊! + +如果 `window.ethereum` _不存在_,那表示 MetaMask 沒有安裝。 這會回傳一個 JSON 物件,其中回傳的 `address` 是一個空字串,而 `status` JSX 物件則傳達使用者必須安裝 MetaMask 的訊息。 + +現在如果 `window.ethereum` _存在_,事情就變得有趣了。 + +使用 try/catch 迴圈,我們將透過呼叫 [`window.ethereum.request({ method: "eth_requestAccounts" });`](https://docs.metamask.io/guide/rpc-api.html#eth-requestaccounts) 來嘗試連接 MetaMask。 呼叫這個函式會在瀏覽器中開啟 MetaMask,提示使用者將他們的錢包連接到你的去中心化應用程式。 + +- 如果使用者選擇連線,`method: "eth_requestAccounts"` 將回傳一個陣列,其中包含所有連線至去中心化應用程式的使用者帳戶地址。 總而言之,我們的 `connectWallet` 函式會回傳一個 JSON 物件,其中包含此陣列中的_第一個_ `address` (見第 9 行) 和一則 `status` 訊息,提示使用者向智能合約寫入一則訊息。 +- 如果使用者拒絕連接,那麼 JSON 物件將包含一個空字串作為回傳的 `address`,以及一則反映使用者拒絕連接的 `status` 訊息。 + +既然我們已編寫此 `connectWallet` 函式,下一步就是將它呼叫至我們的 `HelloWorld.js` 元件。 + +#### 將 `connectWallet` 函式新增至您的 `HelloWorld.js` UI 元件 {#add-the-connectWallet-function-to-your-HelloWorld-js-ui-component} + +導覽至 `HelloWorld.js` 中的 `connectWalletPressed` 函式,並將其更新為以下內容: + +```javascript +// HelloWorld.js + +const connectWalletPressed = async () => { + const walletResponse = await connectWallet() + setStatus(walletResponse.status) + setWallet(walletResponse.address) +} +``` + +注意到我們的大部分功能是如何從 `interact.js` 檔案中抽象出來,遠離我們的 `HelloWorld.js` 元件嗎? 這是我們跟M-V-C規範相容的做法! + +在 `connectWalletPressed` 中,我們只需對匯入的 `connectWallet` 函式進行一個 await 呼叫,並利用其回應,透過它們的 state hooks 更新我們的 `status` 和 `walletAddress` 變數。 + +現在,讓我們儲存這兩個檔案 (`HelloWorld.js` 和 `interact.js`),並測試一下我們目前的 UI。 + +在 [http://localhost:3000/](http://localhost:3000/) 頁面上開啟您的瀏覽器,並按下頁面右上角的「連線錢包」按鈕。 + +如果你已安裝 MetaMask,系統應該會提示你將錢包連接到你的去中心化應用程式。 請接受進行連結的邀請。 + +您應該會看到錢包按鈕現在反映您的地址已連線! 太棒了 🔥 + +接下來,試著重新整理頁面... 這很奇怪。 我們的錢包按鈕會鼓勵我們對MetaMask進行連結,就算它已經被連結好了。。。。。。 + +不過,別擔心! 我們可以輕鬆地處理這個問題 (懂嗎?) 透過實作 `getCurrentWalletConnected`,它將檢查是否有地址已連線至我們的去中心化應用程式,並相應地更新我們的 UI! + +#### `getCurrentWalletConnected` 函式 {#the-getcurrentwalletconnected-function} + +將您 `interact.js` 檔案中的 `getCurrentWalletConnected` 函式更新為以下內容: + +```javascript +// 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: "👆🏽 在上方的文字欄位中寫一則訊息。", + } + } else { + return { + address: "", + status: "🦊 使用右上角按鈕連線至 MetaMask。", + } + } + } catch (err) { + return { + address: "", + status: "😥 " + err.message, + } + } + } else { + return { + address: "", + status: ( + ++ {" "} + 🦊 + 您必須在瀏覽器中安裝 MetaMask,這是一款虛擬以太坊錢包。 + +
+ + ), + } + } +} +``` + +這段程式碼與我們剛在上一步中編寫的 `connectWallet` 函式_非常_相似。 + +主要的區別在於,我們不是呼叫 `eth_requestAccounts` 方法 (這會開啟 MetaMask 讓使用者連接他們的錢包),而是在這裡呼叫 `eth_accounts` 方法,它只會回傳一個包含當前連接到我們去中心化應用程式的 MetaMask 位址的陣列。 + +若要查看此函式的實際運作情況,讓我們在我們的 `HelloWorld.js` 元件的 `useEffect` 函式中呼叫它: + +```javascript +// HelloWorld.js + +useEffect(async () => { + const message = await loadCurrentMessage() + setMessage(message) + addSmartContractListener() + + const { address, status } = await getCurrentWalletConnected() + setWallet(address) + setStatus(status) +}, []) +``` + +注意,我們使用對 `getCurrentWalletConnected` 呼叫的回應來更新我們的 `walletAddress` 和 `status` 狀態變數。 + +既然您已新增此程式碼,讓我們試著重新整理我們的瀏覽器視窗。 + +太棒了! 這個按鈕應該會跟你說:「你已經連結好了。」,然後會顯出一個你錢包被連結好的地址的預視 - 就算在你刷新之後也會這樣! + +#### 實作 `addWalletListener` {#implement-addwalletlistener} + +我們去中心化應用程式錢包設定的最後一個步驟是實作錢包監聽器,這樣當我們錢包的狀態改變時 (例如使用者中斷連線或切換帳戶),我們的 UI 就會更新。 + +在您的 `HelloWorld.js` 檔案中,將您的 `addWalletListener` 函式修改為以下內容: + +```javascript +// HelloWorld.js + +function addWalletListener() { + if (window.ethereum) { + window.ethereum.on("accountsChanged", (accounts) => { + if (accounts.length > 0) { + setWallet(accounts[0]) + setStatus("👆🏽 在上方的文字欄位中寫一則訊息。") + } else { + setWallet("") + setStatus("🦊 使用右上角按鈕連線至 MetaMask。") + } + }) + } else { + setStatus( ++ {" "} + 🦊 + 您必須在瀏覽器中安裝 MetaMask,這是一款虛擬以太坊錢包。 + +
+ ) + } +} +``` + +我敢打賭,此時您甚至不需要我們的幫助就能了解這裡發生了什麼事,但為了周全起見,讓我們快速分解一下: + +- 首先,我們的函式會檢查 `window.ethereum` 是否已啟用 (即 MetaMask 已安裝)。 + - 如果沒有啟用,我們只需將 `status` 狀態變數設定為一個 JSX 字串,提示使用者安裝 MetaMask。 + - 如果已啟用,我們在第 3 行設定監聽器 `window.ethereum.on("accountsChanged")`,它會監聽 MetaMask 錢包的狀態變化,包括使用者將額外帳戶連接到去中心化應用程式、切換帳戶或中斷帳戶連線時。 如果至少有一個帳戶已連接,`walletAddress` 狀態變數會更新為監聽器回傳的 `accounts` 陣列中的第一個帳戶。 否則,`walletAddress` 會被設定為空字串。 + +最後但同樣重要的是,我們必須在我們的 `useEffect` 函式中呼叫它: + +```javascript +// HelloWorld.js + +useEffect(async () => { + const message = await loadCurrentMessage() + setMessage(message) + addSmartContractListener() + + const { address, status } = await getCurrentWalletConnected() + setWallet(address) + setStatus(status) + + addWalletListener() +}, []) +``` + +就是這樣! 我們已成功完成所有錢包功能的編寫! 現在進入我們最後的任務:更新儲存在我們智慧型合約中的訊息! + +### 步驟 6:實作 `updateMessage` 函式 {#step-6-implement-the-updateMessage-function} + +好了,各位,我們已進入最後階段! 在您 `interact.js` 檔案的 `updateMessage` 中,我們將執行以下操作: + +1. 確保我們希望在我們的智慧合約中發布的訊息是有效的 +2. 使用 MetaMask 簽署我們的交易 +3. 從我們的 `HelloWorld.js` 前端元件呼叫此函式 + +這不會花很長時間;讓我們完成這個去中心化應用程式! + +#### 輸入錯誤處理 {#input-error-handling} + +自然地,在函式開始時進行某種輸入錯誤處理是合理的。 + +如果沒有安裝 MetaMask 擴充功能、沒有連線錢包 (即傳入的 `address` 是空字串),或者 `message` 是空字串,我們希望我們的函式能提早回傳。 讓我們將以下錯誤處理新增至 `updateMessage`: + +```javascript +// interact.js + +export const updateMessage = async (address, message) => { + if (!window.ethereum || address === null) { + return { + status: + "💡 連線您的 MetaMask 錢包以更新區塊鏈上的訊息。", + } + } + + if (message.trim() === "") { + return { + status: "❌ 您的訊息不能是空字串。", + } + } +} +``` + +既然它有了適當的輸入錯誤處理,是時候透過 MetaMask 簽署交易了! + +#### 簽署我們的交易 {#signing-our-transaction} + +如果您已經熟悉傳統的 web3 以太坊交易,我們接下來編寫的程式碼將會非常熟悉。 在您的輸入錯誤處理程式碼下方,將以下內容新增至 `updateMessage`: + +```javascript +// interact.js + +//設定交易參數 +const transactionParameters = { + to: contractAddress, // 合約發布期間除外為必要 + from: address, // 必須與使用者目前的地址相符 + data: helloWorldContract.methods.update(message).encodeABI(), +} + +//簽署交易 +try { + const txHash = await window.ethereum.request({ + method: "eth_sendTransaction", + params: [transactionParameters], + }) + return { + status: ( + + ✅{" "} + + 在 Etherscan 上查看您交易的狀態! + +