目錄
- 智能合約 Solidity 教學 (上) - Solidity 基礎語法教學
- 智能合約 Solidity 教學 (中) - MetaMask 與以太坊測試網
- 智能合約 Solidity 教學 (下) - Web3.js 實際開發一個 DApp 去中心化應用前端
開發 Web3 應用的前端
零、前言
在過去兩章節中,我們已經完成了智能合約,並且將其部署到測試鏈 Sepolia 上
Sepolia 是全球公開的以太坊測試網絡,已經算是個半公開環境了
在實際項目開發中,我們會使用 Ganache 之類的程序,來架設自己的私人區塊鏈,並且在上頭測試合約,以獲得更高的調測性與開發效率。
在本教學中則因為時間問題,省略了這一部分。
一、開發環境準備
- 安裝 Node.js 及 npm
- 如果你的電腦還沒有安裝 Node.js,請至 Node.js 官方網站下載並安裝最新版本的 Node.js,即可同時安裝 npm(Node Package Manager)。
- 安裝 Vite此時 Vite 會自動幫你建立一個基本的前端專案結構。
- 透過 npm 方式安裝 Vite(其實也可以直接使用
npm create vite@latest
來初始化專案)。 - 這裡建議使用簡單的安裝方式快速開始:npm create vite@latest
- 在互動式的 CLI 中選擇:
- 專案名稱 (如
my-web3-project
) - 框架選擇:Vanilla
- 選擇 JavaScript(非 TypeScript)
- 專案名稱 (如
- 透過 npm 方式安裝 Vite(其實也可以直接使用
- 進入專案並安裝依賴
- 切換到專案資料夾:
cd my-web3-project
- 安裝 web3.js:
npm install web3
- 切換到專案資料夾:
二、專案結構簡介
執行完 npm create vite@latest
之後,你的專案結構大致會長這樣:
my-web3-project ├─ index.html ├─ package.json ├─ vite.config.js └─ src ├─ main.js └─ style.css
為了方便教學,所有的程式碼都會放在 index.html
中來展示結果。
三、拿到合約的 ABI 與合約地址
在上次的教學範例中,HelloWorld 合約已經部署在 Sepolia 網路上。
我們仍然需要兩項關鍵資訊才能在前端與之互動

首先是合約 ABI (Application Binary Interface)
可以在 Remix 部署成功後,點開「Solidity Compiler」下方的「ABI」標籤,取得 ABI 資訊(是一段 JSON 格式)。

再來就是合約地址
你可以在我們剛剛 Deploy 好的合約中,點擊複製按鈕

四、撰寫前端程式碼
打開 `index.html`
替換成最基本的 HTML,我們一步一步來建構 Web3 應用
1<!DOCTYPE html>
2<html lang="en">
3<head>
4 <meta charset="UTF-8">
5 <meta name="viewport" content="width=device-width, initial-scale=1.0">
6 <title>Document</title>
7</head>
8<body>
9
10</body>
11</html>
為了方便教學,我會將 JavaScript 直接寫在 HTML 中
在正式專案開發時,這樣做程式碼會長到很難閱讀,要特別注意
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 // ... 之後的程式碼就放這吧
10 </script>
11</head>
12
13<body>
14
15</body>
16
17</html>
還記得我們剛剛安裝的 web3.js 嗎?
這是別人已經設計、封裝好的程式碼,我們可以簡單的使用 import 來把它加入我們的程式碼
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 </script>
11</head>
12
13<body>
14
15</body>
16
17</html>
將剛剛獲得的合約地址與合約 ABI 貼上去
這個程式碼會瞬間變超級長
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 const contractAddress = "0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749";
11
12 const contractABI = [
13 {
14 inputs: [
15 ....
16 ],
17 stateMutability: "nonpayable",
18 type: "constructor",
19 },
20 ...
21 ];
22 </script>
23</head>
24
25<body>
26
27</body>
28
29</html>
contractABI 真的太長了!!
讓我們把它獨立成一個檔案吧
在 src 資料夾中創建新的檔案 `abi.js`
並貼上剛剛的 合約 ABI
記得在最前面加上 export
這表示允許其他程式檔案使用這個變數
1// abi.js
2export const contractABI = [
3 {
4 inputs: [
5 ....
6 ],
7 stateMutability: "nonpayable",
8 type: "constructor",
9 },
10 ...
11];

回到 index.html
透過 import 將剛剛宣告的變數導進來
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 import { contractABI } from "./src/abi.js";
11
12 const contractAddress = "0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749";
13 </script>
14</head>
15
16<body>
17
18</body>
19
20</html>
設計一個按鈕來連結用戶錢包
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 import { contractABI } from "./src/abi.js";
11
12 const contractAddress = "0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749";
13
14 </script>
15</head>
16
17<body>
18 <div>
19 <button id="connectButton">連線錢包</button>
20 </div>
21</body>
22
23</html>
在 JS 中設定該按鈕點擊後要做什麼
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 import { contractABI } from "./src/abi.js";
11
12 const contractAddress = "0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749";
13
14 // 找到 網頁 中的按鈕
15 const connectButton = document.getElementById("connectButton");
16 connectButton.addEventListener("click", async () => {
17 // ...點擊後要做的事情
18 });
19 </script>
20</head>
21
22<body>
23 <div>
24 <button id="connectButton">連線錢包</button>
25 </div>
26</body>
27
28</html>
點擊按鈕後,要先檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)
(如果沒裝任何錢包 window.ethereum 不會有東西
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 import { contractABI } from "./src/abi.js";
11
12 const contractAddress = "0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749";
13
14 const connectButton = document.getElementById("connectButton");
15 connectButton.addEventListener("click", async () => {
16 // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)
17 if (typeof window.ethereum !== 'undefined') {
18 // ... 設定錢包
19 } else {
20 alert('請先安裝 MetaMask 或其他以太坊錢包外掛!');
21 }
22 });
23 </script>
24</head>
25
26<body>
27 <div>
28 <button id="connectButton">連線錢包</button>
29 </div>
30</body>
31
32</html>
請求使用者透過 MetaMask 授權並連接錢包,讓 DApp 可以存取使用者的 Ethereum 帳戶地址。
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 import { contractABI } from "./src/abi.js";
11
12 const contractAddress = "0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749";
13
14 const connectButton = document.getElementById("connectButton");
15 connectButton.addEventListener("click", async () => {
16 // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)
17 if (typeof window.ethereum !== 'undefined') {
18 try {
19 // 請求錢包授權
20 await window.ethereum.request({ method: 'eth_requestAccounts' });
21 } catch (error) {
22 console.error(error);
23 alert("連線錢包失敗,請查看控制台訊息或再次嘗試。");
24 }
25 } else {
26 alert('請先安裝 MetaMask 或其他以太坊錢包外掛!');
27 }
28 });
29 </script>
30</head>
31
32<body>
33 <div>
34 <button id="connectButton">連線錢包</button>
35 </div>
36</body>
37
38</html>
將 MetaMask 作為 Web3 的提供者 (Provider),讓 DApp 透過 MetaMask 與區塊鏈互動。
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 import { contractABI } from "./src/abi.js";
11
12 const contractAddress = "0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749";
13
14 let web3; // 用來存放 Web3 物件實例
15 const connectButton = document.getElementById("connectButton");
16 connectButton.addEventListener("click", async () => {
17 // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)
18 if (typeof window.ethereum !== 'undefined') {
19 try {
20 // 請求錢包授權
21 await window.ethereum.request({ method: 'eth_requestAccounts' });
22
23 // 建立 web3 物件(連到當前 MetaMask 所指向的網路)
24 web3 = new Web3(window.ethereum);
25 } catch (error) {
26 console.error(error);
27 alert("連線錢包失敗,請查看控制台訊息或再次嘗試。");
28 }
29 } else {
30 alert('請先安裝 MetaMask 或其他以太坊錢包外掛!');
31 }
32 });
33 </script>
34</head>
35
36<body>
37 <div>
38 <button id="connectButton">連線錢包</button>
39 </div>
40</body>
41
42</html>
可以印出來看看有沒有成功連結帳號
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 import { contractABI } from "./src/abi.js";
11
12 const contractAddress = "0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749";
13
14 let web3; // 用來存放 Web3 物件實例
15 const connectButton = document.getElementById("connectButton");
16 connectButton.addEventListener("click", async () => {
17 // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)
18 if (typeof window.ethereum !== 'undefined') {
19 try {
20 // 請求錢包授權
21 await window.ethereum.request({ method: 'eth_requestAccounts' });
22
23 // 建立 web3 物件(連到當前 MetaMask 所指向的網路)
24 web3 = new Web3(window.ethereum);
25
26 // 印出當前使用的帳戶
27 const accounts = await web3.eth.getAccounts();
28 console.log('已連線帳戶:', accounts[0]);
29 } catch (error) {
30 console.error(error);
31 alert("連線錢包失敗,請查看控制台訊息或再次嘗試。");
32 }
33 } else {
34 alert('請先安裝 MetaMask 或其他以太坊錢包外掛!');
35 }
36 });
37 </script>
38</head>
39
40<body>
41 <div>
42 <button id="connectButton">連線錢包</button>
43 </div>
44</body>
45
46</html>
因為你可能沒學過物件導向,所以讓我用更簡單的方式來解釋這段程式碼。
你可以想像 Web3.js 會幫我們創建一個「合約操作員」,這個操作員負責幫我們與區塊鏈上的智能合約互動。
這個 合約操作員 具備三個關鍵能力:
- 擁有你的錢包地址的操控權(當然,它不會擅自動用,你需要授權)。
- 知道你要操作哪個智能合約(透過 合約地址)。
- 知道該怎麼操作這份合約(透過 ABI(操作指南))。
當我們想與合約互動時,只需要告訴這個「操作員」我們要做什麼,這位 操作員 會幫你處理所有的技術細節,確保你的指令能順利送進區塊鏈。
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 import { contractABI } from "./src/abi.js";
11
12 const contractAddress = "0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749";
13
14 let web3; // 用來存放 Web3 物件實例
15 let contractInstance; // 用來存放合約實例
16 const connectButton = document.getElementById("connectButton");
17 connectButton.addEventListener("click", async () => {
18 // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)
19 if (typeof window.ethereum !== 'undefined') {
20 try {
21 // 請求錢包授權
22 await window.ethereum.request({ method: 'eth_requestAccounts' });
23
24 // 建立 web3 物件(連到當前 MetaMask 所指向的網路)
25 web3 = new Web3(window.ethereum);
26
27 // 印出當前使用的帳戶
28 const accounts = await web3.eth.getAccounts();
29 console.log('已連線帳戶:', accounts[0]);
30
31 // 建立合約實例
32 contractInstance = new web3.eth.Contract(contractABI, contractAddress);
33
34 alert("錢包連線成功!");
35 } catch (error) {
36 console.error(error);
37 alert("連線錢包失敗,請查看控制台訊息或再次嘗試。");
38 }
39 } else {
40 alert('請先安裝 MetaMask 或其他以太坊錢包外掛!');
41 }
42 });
43 </script>
44</head>
45
46<body>
47 <div>
48 <button id="connectButton">連線錢包</button>
49 </div>
50</body>
51
52</html>
在 HTML 中顯示合約中的 message 變數
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 import { contractABI } from "./src/abi.js";
11
12 const contractAddress = "0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749";
13
14 let web3; // 用來存放 Web3 物件實例
15 let contractInstance; // 用來存放合約實例
16 const connectButton = document.getElementById("connectButton");
17 connectButton.addEventListener("click", async () => {
18 // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)
19 if (typeof window.ethereum !== 'undefined') {
20 try {
21 // 請求錢包授權
22 await window.ethereum.request({ method: 'eth_requestAccounts' });
23
24 // 建立 web3 物件(連到當前 MetaMask 所指向的網路)
25 web3 = new Web3(window.ethereum);
26
27 // 印出當前使用的帳戶
28 const accounts = await web3.eth.getAccounts();
29 console.log('已連線帳戶:', accounts[0]);
30
31 // 建立合約實例
32 contractInstance = new web3.eth.Contract(contractABI, contractAddress);
33
34 alert("錢包連線成功!");
35 } catch (error) {
36 console.error(error);
37 alert("連線錢包失敗,請查看控制台訊息或再次嘗試。");
38 }
39 } else {
40 alert('請先安裝 MetaMask 或其他以太坊錢包外掛!');
41 }
42 });
43 </script>
44</head>
45
46<body>
47 <div>
48 <button id="connectButton">連線錢包</button>
49 </div>
50
51 <div>
52 <p>目前合約訊息:<span id="currentMessage">---</span></p>
53 <button id="readMessageButton">讀取合約訊息</button>
54 </div>
55
56</body>
57
58</html>
在 JS 中設定該按鈕點擊後要做什麼
讀取區塊鏈的資料錢,要先連結錢包在進行操作
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 import { contractABI } from "./src/abi.js";
11
12 const contractAddress = "0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749";
13
14 let web3; // 用來存放 Web3 物件實例
15 let contractInstance; // 用來存放合約實例
16 const connectButton = document.getElementById("connectButton");
17 connectButton.addEventListener("click", async () => {
18 // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)
19 if (typeof window.ethereum !== 'undefined') {
20 try {
21 // 請求錢包授權
22 await window.ethereum.request({ method: 'eth_requestAccounts' });
23
24 // 建立 web3 物件(連到當前 MetaMask 所指向的網路)
25 web3 = new Web3(window.ethereum);
26
27 // 印出當前使用的帳戶
28 const accounts = await web3.eth.getAccounts();
29 console.log('已連線帳戶:', accounts[0]);
30
31 // 建立合約實例
32 contractInstance = new web3.eth.Contract(contractABI, contractAddress);
33
34 alert("錢包連線成功!");
35 } catch (error) {
36 console.error(error);
37 alert("連線錢包失敗,請查看控制台訊息或再次嘗試。");
38 }
39 } else {
40 alert('請先安裝 MetaMask 或其他以太坊錢包外掛!');
41 }
42 });
43
44 const readMessageButton = document.getElementById("readMessageButton");
45
46 readMessageButton.addEventListener("click", async () => {
47 if (!contractInstance) {
48 alert("請先按 [連線錢包] 按鈕,再進行操作。");
49 return;
50 }
51 });
52 </script>
53</head>
54
55<body>
56 <div>
57 <button id="connectButton">連線錢包</button>
58 </div>
59
60 <div>
61 <p>目前合約訊息:<span id="currentMessage">---</span></p>
62 <button id="readMessageButton">讀取合約訊息</button>
63 </div>
64
65</body>
66
67</html>
從智能合約中讀取 message 變數的值,而且不會發送交易,只會查詢區塊鏈上的數據。
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 import { contractABI } from "./src/abi.js";
11
12 const contractAddress = "0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749";
13
14 let web3; // 用來存放 Web3 物件實例
15 let contractInstance; // 用來存放合約實例
16 const connectButton = document.getElementById("connectButton");
17 connectButton.addEventListener("click", async () => {
18 // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)
19 if (typeof window.ethereum !== 'undefined') {
20 try {
21 // 請求錢包授權
22 await window.ethereum.request({ method: 'eth_requestAccounts' });
23
24 // 建立 web3 物件(連到當前 MetaMask 所指向的網路)
25 web3 = new Web3(window.ethereum);
26
27 // 印出當前使用的帳戶
28 const accounts = await web3.eth.getAccounts();
29 console.log('已連線帳戶:', accounts[0]);
30
31 // 建立合約實例
32 contractInstance = new web3.eth.Contract(contractABI, contractAddress);
33
34 alert("錢包連線成功!");
35 } catch (error) {
36 console.error(error);
37 alert("連線錢包失敗,請查看控制台訊息或再次嘗試。");
38 }
39 } else {
40 alert('請先安裝 MetaMask 或其他以太坊錢包外掛!');
41 }
42 });
43
44 const readMessageButton = document.getElementById("readMessageButton");
45
46 readMessageButton.addEventListener("click", async () => {
47 if (!contractInstance) {
48 alert("請先按 [連線錢包] 按鈕,再進行操作。");
49 return;
50 }
51 try {
52 const message = await contractInstance.methods.message().call();
53 console.log("合約訊息為:", message);
54 } catch (error) {
55 console.error(error);
56 alert("讀取合約訊息失敗!");
57 }
58 });
59 </script>
60</head>
61
62<body>
63 <div>
64 <button id="connectButton">連線錢包</button>
65 </div>
66
67 <div>
68 <p>目前合約訊息:<span id="currentMessage">---</span></p>
69 <button id="readMessageButton">讀取合約訊息</button>
70 </div>
71
72</body>
73
74</html>
將從區塊鏈上取得的資料寫入網頁
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 import { contractABI } from "./src/abi.js";
11
12 const contractAddress = "0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749";
13
14 let web3; // 用來存放 Web3 物件實例
15 let contractInstance; // 用來存放合約實例
16 const connectButton = document.getElementById("connectButton");
17 connectButton.addEventListener("click", async () => {
18 // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)
19 if (typeof window.ethereum !== 'undefined') {
20 try {
21 // 請求錢包授權
22 await window.ethereum.request({ method: 'eth_requestAccounts' });
23
24 // 建立 web3 物件(連到當前 MetaMask 所指向的網路)
25 web3 = new Web3(window.ethereum);
26
27 // 印出當前使用的帳戶
28 const accounts = await web3.eth.getAccounts();
29 console.log('已連線帳戶:', accounts[0]);
30
31 // 建立合約實例
32 contractInstance = new web3.eth.Contract(contractABI, contractAddress);
33
34 alert("錢包連線成功!");
35 } catch (error) {
36 console.error(error);
37 alert("連線錢包失敗,請查看控制台訊息或再次嘗試。");
38 }
39 } else {
40 alert('請先安裝 MetaMask 或其他以太坊錢包外掛!');
41 }
42 });
43
44 const readMessageButton = document.getElementById("readMessageButton");
45 const currentMessageSpan = document.getElementById("currentMessage");
46
47 readMessageButton.addEventListener("click", async () => {
48 if (!contractInstance) {
49 alert("請先按 [連線錢包] 按鈕,再進行操作。");
50 return;
51 }
52 try {
53 const message = await contractInstance.methods.message().call();
54 console.log("合約訊息為:", message);
55 currentMessageSpan.textContent = message;
56 } catch (error) {
57 console.error(error);
58 alert("讀取合約訊息失敗!");
59 }
60 });
61 </script>
62</head>
63
64<body>
65 <div>
66 <button id="connectButton">連線錢包</button>
67 </div>
68
69 <div>
70 <p>目前合約訊息:<span id="currentMessage">---</span></p>
71 <button id="readMessageButton">讀取合約訊息</button>
72 </div>
73
74</body>
75
76</html>
再來就是要來實踐如何觸發 setMessage 的 function 啦
先從 HTML 介面開始
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 import { contractABI } from "./src/abi.js";
11
12 const contractAddress = "0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749";
13
14 let web3; // 用來存放 Web3 物件實例
15 let contractInstance; // 用來存放合約實例
16 const connectButton = document.getElementById("connectButton");
17 connectButton.addEventListener("click", async () => {
18 // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)
19 if (typeof window.ethereum !== 'undefined') {
20 try {
21 // 請求錢包授權
22 await window.ethereum.request({ method: 'eth_requestAccounts' });
23
24 // 建立 web3 物件(連到當前 MetaMask 所指向的網路)
25 web3 = new Web3(window.ethereum);
26
27 // 印出當前使用的帳戶
28 const accounts = await web3.eth.getAccounts();
29 console.log('已連線帳戶:', accounts[0]);
30
31 // 建立合約實例
32 contractInstance = new web3.eth.Contract(contractABI, contractAddress);
33
34 alert("錢包連線成功!");
35 } catch (error) {
36 console.error(error);
37 alert("連線錢包失敗,請查看控制台訊息或再次嘗試。");
38 }
39 } else {
40 alert('請先安裝 MetaMask 或其他以太坊錢包外掛!');
41 }
42 });
43
44 const readMessageButton = document.getElementById("readMessageButton");
45 const currentMessageSpan = document.getElementById("currentMessage");
46
47 readMessageButton.addEventListener("click", async () => {
48 if (!contractInstance) {
49 alert("請先按 [連線錢包] 按鈕,再進行操作。");
50 return;
51 }
52 try {
53 const message = await contractInstance.methods.message().call();
54 console.log("合約訊息為:", message);
55 currentMessageSpan.textContent = message;
56 } catch (error) {
57 console.error(error);
58 alert("讀取合約訊息失敗!");
59 }
60 });
61 </script>
62</head>
63
64<body>
65 <div>
66 <button id="connectButton">連線錢包</button>
67 </div>
68
69 <div>
70 <p>目前合約訊息:<span id="currentMessage">---</span></p>
71 <button id="readMessageButton">讀取合約訊息</button>
72 </div>
73
74 <div>
75 <input type="text" id="newMessageInput" placeholder="輸入新的訊息" />
76 <button id="setMessageButton">寫入合約訊息</button>
77 </div>
78</body>
79
80</html>
處理 JS 的事件綁定與錢包檢查
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 import { contractABI } from "./src/abi.js";
11
12 const contractAddress = "0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749";
13
14 let web3; // 用來存放 Web3 物件實例
15 let contractInstance; // 用來存放合約實例
16 const connectButton = document.getElementById("connectButton");
17 connectButton.addEventListener("click", async () => {
18 // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)
19 if (typeof window.ethereum !== 'undefined') {
20 try {
21 // 請求錢包授權
22 await window.ethereum.request({ method: 'eth_requestAccounts' });
23
24 // 建立 web3 物件(連到當前 MetaMask 所指向的網路)
25 web3 = new Web3(window.ethereum);
26
27 // 印出當前使用的帳戶
28 const accounts = await web3.eth.getAccounts();
29 console.log('已連線帳戶:', accounts[0]);
30
31 // 建立合約實例
32 contractInstance = new web3.eth.Contract(contractABI, contractAddress);
33
34 alert("錢包連線成功!");
35 } catch (error) {
36 console.error(error);
37 alert("連線錢包失敗,請查看控制台訊息或再次嘗試。");
38 }
39 } else {
40 alert('請先安裝 MetaMask 或其他以太坊錢包外掛!');
41 }
42 });
43
44 const readMessageButton = document.getElementById("readMessageButton");
45 const currentMessageSpan = document.getElementById("currentMessage");
46
47 readMessageButton.addEventListener("click", async () => {
48 if (!contractInstance) {
49 alert("請先按 [連線錢包] 按鈕,再進行操作。");
50 return;
51 }
52 try {
53 const message = await contractInstance.methods.message().call();
54 console.log("合約訊息為:", message);
55 currentMessageSpan.textContent = message;
56 } catch (error) {
57 console.error(error);
58 alert("讀取合約訊息失敗!");
59 }
60 });
61
62 // 5. 寫入合約訊息
63 const setMessageButton = document.getElementById("setMessageButton");
64 const newMessageInput = document.getElementById("newMessageInput");
65
66 setMessageButton.addEventListener("click", async () => {
67 if (!contractInstance) {
68 alert("請先按 [連線錢包] 按鈕,再進行操作。");
69 return;
70 }
71 // ... 點擊按鈕並確認連線錢包後要做的事情
72 });
73 </script>
74</head>
75
76<body>
77 <div>
78 <button id="connectButton">連線錢包</button>
79 </div>
80
81 <div>
82 <p>目前合約訊息:<span id="currentMessage">---</span></p>
83 <button id="readMessageButton">讀取合約訊息</button>
84 </div>
85
86 <div>
87 <input type="text" id="newMessageInput" placeholder="輸入新的訊息" />
88 <button id="setMessageButton">寫入合約訊息</button>
89 </div>
90</body>
91
92</html>
從輸入框取得當前輸入的數值,檢查一下有沒有輸入
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 import { contractABI } from "./src/abi.js";
11
12 const contractAddress = "0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749";
13
14 let web3; // 用來存放 Web3 物件實例
15 let contractInstance; // 用來存放合約實例
16 const connectButton = document.getElementById("connectButton");
17 connectButton.addEventListener("click", async () => {
18 // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)
19 if (typeof window.ethereum !== 'undefined') {
20 try {
21 // 請求錢包授權
22 await window.ethereum.request({ method: 'eth_requestAccounts' });
23
24 // 建立 web3 物件(連到當前 MetaMask 所指向的網路)
25 web3 = new Web3(window.ethereum);
26
27 // 印出當前使用的帳戶
28 const accounts = await web3.eth.getAccounts();
29 console.log('已連線帳戶:', accounts[0]);
30
31 // 建立合約實例
32 contractInstance = new web3.eth.Contract(contractABI, contractAddress);
33
34 alert("錢包連線成功!");
35 } catch (error) {
36 console.error(error);
37 alert("連線錢包失敗,請查看控制台訊息或再次嘗試。");
38 }
39 } else {
40 alert('請先安裝 MetaMask 或其他以太坊錢包外掛!');
41 }
42 });
43
44 const readMessageButton = document.getElementById("readMessageButton");
45 const currentMessageSpan = document.getElementById("currentMessage");
46
47 readMessageButton.addEventListener("click", async () => {
48 if (!contractInstance) {
49 alert("請先按 [連線錢包] 按鈕,再進行操作。");
50 return;
51 }
52 try {
53 const message = await contractInstance.methods.message().call();
54 console.log("合約訊息為:", message);
55 currentMessageSpan.textContent = message;
56 } catch (error) {
57 console.error(error);
58 alert("讀取合約訊息失敗!");
59 }
60 });
61
62 // 5. 寫入合約訊息
63 const setMessageButton = document.getElementById("setMessageButton");
64 const newMessageInput = document.getElementById("newMessageInput");
65
66 setMessageButton.addEventListener("click", async () => {
67 if (!contractInstance) {
68 alert("請先按 [連線錢包] 按鈕,再進行操作。");
69 return;
70 }
71 const newMessage = newMessageInput.value;
72 if (!newMessage) {
73 alert("請輸入新的訊息再嘗試。");
74 return;
75 }
76 });
77 </script>
78</head>
79
80<body>
81 <div>
82 <button id="connectButton">連線錢包</button>
83 </div>
84
85 <div>
86 <p>目前合約訊息:<span id="currentMessage">---</span></p>
87 <button id="readMessageButton">讀取合約訊息</button>
88 </div>
89
90 <div>
91 <input type="text" id="newMessageInput" placeholder="輸入新的訊息" />
92 <button id="setMessageButton">寫入合約訊息</button>
93 </div>
94</body>
95
96</html>
取得剛剛綁定好的錢包,由於用戶可能有多個錢包
使用首個錢包
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 import { contractABI } from "./src/abi.js";
11
12 const contractAddress = "0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749";
13
14 let web3; // 用來存放 Web3 物件實例
15 let contractInstance; // 用來存放合約實例
16 const connectButton = document.getElementById("connectButton");
17 connectButton.addEventListener("click", async () => {
18 // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)
19 if (typeof window.ethereum !== 'undefined') {
20 try {
21 // 請求錢包授權
22 await window.ethereum.request({ method: 'eth_requestAccounts' });
23
24 // 建立 web3 物件(連到當前 MetaMask 所指向的網路)
25 web3 = new Web3(window.ethereum);
26
27 // 印出當前使用的帳戶
28 const accounts = await web3.eth.getAccounts();
29 console.log('已連線帳戶:', accounts[0]);
30
31 // 建立合約實例
32 contractInstance = new web3.eth.Contract(contractABI, contractAddress);
33
34 alert("錢包連線成功!");
35 } catch (error) {
36 console.error(error);
37 alert("連線錢包失敗,請查看控制台訊息或再次嘗試。");
38 }
39 } else {
40 alert('請先安裝 MetaMask 或其他以太坊錢包外掛!');
41 }
42 });
43
44 const readMessageButton = document.getElementById("readMessageButton");
45 const currentMessageSpan = document.getElementById("currentMessage");
46
47 readMessageButton.addEventListener("click", async () => {
48 if (!contractInstance) {
49 alert("請先按 [連線錢包] 按鈕,再進行操作。");
50 return;
51 }
52 try {
53 const message = await contractInstance.methods.message().call();
54 console.log("合約訊息為:", message);
55 currentMessageSpan.textContent = message;
56 } catch (error) {
57 console.error(error);
58 alert("讀取合約訊息失敗!");
59 }
60 });
61
62 // 5. 寫入合約訊息
63 const setMessageButton = document.getElementById("setMessageButton");
64 const newMessageInput = document.getElementById("newMessageInput");
65
66 setMessageButton.addEventListener("click", async () => {
67 if (!contractInstance) {
68 alert("請先按 [連線錢包] 按鈕,再進行操作。");
69 return;
70 }
71 const newMessage = newMessageInput.value;
72 if (!newMessage) {
73 alert("請輸入新的訊息再嘗試。");
74 return;
75 }
76 try {
77 // 發送交易需要指定哪個帳戶做為 from
78 const accounts = await web3.eth.getAccounts();
79 const usedAccount = accounts[0]
80
81 } catch (error) {
82 console.error(error);
83 alert("寫入合約訊息失敗,請查看控制台訊息。");
84 }
85 });
86 </script>
87</head>
88
89<body>
90 <div>
91 <button id="connectButton">連線錢包</button>
92 </div>
93
94 <div>
95 <p>目前合約訊息:<span id="currentMessage">---</span></p>
96 <button id="readMessageButton">讀取合約訊息</button>
97 </div>
98
99 <div>
100 <input type="text" id="newMessageInput" placeholder="輸入新的訊息" />
101 <button id="setMessageButton">寫入合約訊息</button>
102 </div>
103</body>
104
105</html>
正式發起 setMessage 的合約請求,並且以我剛剛選定好的錢包來當作付款對象。
1<!DOCTYPE html>
2<html lang="en">
3
4<head>
5 <meta charset="UTF-8">
6 <meta name="viewport" content="width=device-width, initial-scale=1.0">
7 <title>Document</title>
8 <script type="module">
9 import Web3 from "web3";
10 import { contractABI } from "./src/abi.js";
11
12 const contractAddress = "0xFEA4f2C9B99Df0B2b7da67e60371BE4275C3d749";
13
14 let web3; // 用來存放 Web3 物件實例
15 let contractInstance; // 用來存放合約實例
16 const connectButton = document.getElementById("connectButton");
17 connectButton.addEventListener("click", async () => {
18 // 檢查瀏覽器是否安裝了 MetaMask(或其他注入 web3 的錢包)
19 if (typeof window.ethereum !== 'undefined') {
20 try {
21 // 請求錢包授權
22 await window.ethereum.request({ method: 'eth_requestAccounts' });
23
24 // 建立 web3 物件(連到當前 MetaMask 所指向的網路)
25 web3 = new Web3(window.ethereum);
26
27 // 印出當前使用的帳戶
28 const accounts = await web3.eth.getAccounts();
29 console.log('已連線帳戶:', accounts[0]);
30
31 // 建立合約實例
32 contractInstance = new web3.eth.Contract(contractABI, contractAddress);
33
34 alert("錢包連線成功!");
35 } catch (error) {
36 console.error(error);
37 alert("連線錢包失敗,請查看控制台訊息或再次嘗試。");
38 }
39 } else {
40 alert('請先安裝 MetaMask 或其他以太坊錢包外掛!');
41 }
42 });
43
44 const readMessageButton = document.getElementById("readMessageButton");
45 const currentMessageSpan = document.getElementById("currentMessage");
46
47 readMessageButton.addEventListener("click", async () => {
48 if (!contractInstance) {
49 alert("請先按 [連線錢包] 按鈕,再進行操作。");
50 return;
51 }
52 try {
53 const message = await contractInstance.methods.message().call();
54 console.log("合約訊息為:", message);
55 currentMessageSpan.textContent = message;
56 } catch (error) {
57 console.error(error);
58 alert("讀取合約訊息失敗!");
59 }
60 });
61
62 // 5. 寫入合約訊息
63 const setMessageButton = document.getElementById("setMessageButton");
64 const newMessageInput = document.getElementById("newMessageInput");
65
66 setMessageButton.addEventListener("click", async () => {
67 if (!contractInstance) {
68 alert("請先按 [連線錢包] 按鈕,再進行操作。");
69 return;
70 }
71 const newMessage = newMessageInput.value;
72 if (!newMessage) {
73 alert("請輸入新的訊息再嘗試。");
74 return;
75 }
76 try {
77 // 發送交易需要指定哪個帳戶做為 from
78 const accounts = await web3.eth.getAccounts();
79 const usedAccount = accounts[0]
80 const receipt = await contractInstance.methods
81 .setMessage(newMessage)
82 .send({ from: usedAccount });
83
84 console.log("交易回執:", receipt);
85 alert("寫入訊息成功,請再度點擊 [讀取合約訊息] 查看更新。");
86 } catch (error) {
87 console.error(error);
88 alert("寫入合約訊息失敗,請查看控制台訊息。");
89 }
90 });
91 </script>
92</head>
93
94<body>
95 <div>
96 <button id="connectButton">連線錢包</button>
97 </div>
98
99 <div>
100 <p>目前合約訊息:<span id="currentMessage">---</span></p>
101 <button id="readMessageButton">讀取合約訊息</button>
102 </div>
103
104 <div>
105 <input type="text" id="newMessageInput" placeholder="輸入新的訊息" />
106 <button id="setMessageButton">寫入合約訊息</button>
107 </div>
108</body>
109
110</html>
最後打開測試網站 http://localhost:5173/
跑跑看吧

到 Remix 看看,你會發現數值也變了
