如果我們想要開發一個真正的 DApp(例如讓使用者把代幣轉給別人,或是參與我們前幾天寫的拍賣合約),我們就必須發起一筆「會改變區塊鏈狀態的交易」。發起交易需要使用者的「私鑰」來進行數位簽章。
在 Web3 的資安鐵則中,DApp 網頁絕對不可以要求使用者輸入私鑰。我們必須透過一個安全的第三方代理人來協助簽署交易,而這個代理人就是目前全世界最多人使用的瀏覽器擴充錢包——MetaMask。今天,我們就來實作前端網頁與 MetaMask 的串接!
瀏覽器裡的橋樑:window.ethereum
當使用者在瀏覽器(如 Chrome)安裝了 MetaMask 擴充功能後,MetaMask 會在所有的網頁環境中偷偷注入一個全域變數:window.ethereum。
這個變數就是我們前端網頁與錢包溝通的唯一橋樑。我們可以透過它來詢問使用者:「嘿,可以讓我連線你的錢包嗎?」或是「請幫我簽署這筆轉帳交易」。
實戰步驟一:連線 MetaMask 並取得 Signer
在昨天我們使用的是唯讀的 JsonRpcProvider,今天我們要改用 Ethers.js v6 專門用來對接瀏覽器錢包的 BrowserProvider。
讓我們寫一段 JavaScript 函數來觸發錢包連線:
'''
const { ethers } = require("ethers");
async function connectWallet() {
// 1. 檢查使用者的瀏覽器有沒有安裝 MetaMask (即是否存在 window.ethereum)
if (typeof window.ethereum === 'undefined') {
alert('請先安裝 MetaMask 錢包!');
return null;
}
try {
// 2. 建立 BrowserProvider,將 window.ethereum 包裝成 Ethers 支援的格式
const provider = new ethers.BrowserProvider(window.ethereum);
// 3. 向 MetaMask 發出連線請求,這會跳出 MetaMask 的授權視窗
// 使用者點擊「連線」後,我們才能拿到他的地址
const accounts = await provider.send("eth_requestAccounts", []);
console.log("成功連線帳戶:", accounts[0]);
// 4. 取得 Signer (簽署者物件)
// Signer 代表了這個連線的帳戶,我們後續發送交易都要靠它
const signer = await provider.getSigner();
return signer;
} catch (error) {
console.error("使用者拒絕連線或發生錯誤:", error);
}
}
'''
實戰步驟二:發送交易與狀態管理 (Transaction Lifecycle)
拿到 signer 後,我們就可以來呼叫合約裡需要花費 Gas 的函數了(例如 transfer)。
在傳統 Web2 裡,API 打過去只要等幾百毫秒就會收到成功或失敗的回覆。但在 Web3 裡,一筆交易被發送到網路上,到真正被礦工打包進區塊(Mined),通常需要十幾秒甚至幾分鐘的時間。
如果這段時間網頁畫面沒有任何回饋,使用者一定會以為當機了,然後瘋狂連點按鈕,最後導致發送了重複的交易並浪費大量 Gas 費。因此,嚴謹的「交易狀態管理」是 DApp 開發的重中之重。
'''
// 假設我們已經準備好合約地址與 ABI
const contractAddress = "0xYourTokenContractAddressHere";
const tokenABI = [
"function transfer(address to, uint256 amount) returns (bool)"
];
async function executeTransfer(signer, toAddress, amount) {
// 建立可以發送交易的 Contract 物件 (這次傳入的是 signer 而不是唯讀的 provider)
const tokenContract = new ethers.Contract(contractAddress, tokenABI, signer);
try {
console.log("請在 MetaMask 中確認交易...");
// 1. 轉換代幣單位 (假設是 18 位小數)
const parsedAmount = ethers.parseUnits(amount.toString(), 18);
// 2. 發送交易!這時 MetaMask 會彈出視窗要求使用者點擊「確認」
const tx = await tokenContract.transfer(toAddress, parsedAmount);
// --- 此時交易已發送至節點的交易池 (Pending 狀態) ---
console.log(`交易已送出!交易雜湊值 (Tx Hash): ${tx.hash}`);
console.log("正在等待礦工打包,請稍候...");
// 實務上這裡會在網頁 UI 顯示一個 Loading 動畫
// 3. 等待交易被寫入區塊鏈 (Mined)
const receipt = await tx.wait();
// --- 此時交易已確認 ---
console.log("交易成功寫入區塊鏈!", receipt);
alert('轉帳成功!');
} catch (error) {
// 處理使用者在 MetaMask 按下「拒絕」,或是合約執行失敗 (Revert) 的狀況
if (error.code === 'ACTION_REJECTED') {
console.log("使用者取消了交易");
} else {
console.error("交易失敗:", error);
}
}
}
'''
這兩天的教學中,我們利用 Ethers.js 補齊了 DApp 的最後一塊拼圖:讀取鏈上狀態與發送簽署交易。
身為未來的資安從業人員與開發者,我們現在不僅懂得如何從零刻出區塊鏈底層、如何寫出防禦重入攻擊的智慧合約,更能打造出與之互動的 Web3 前端介面。