Smart Contracts與JavaScript

JavaScript作為智能合約的前端,正是當今區塊鏈應用程式(dApps)最主流的開發模式。它在瀏覽器中運行,是唯一能讓使用者直接與區塊鏈互動的程式語言。它的核心任務是:

  1. 與區塊鏈互動:使用 Ethers.js 或 Web3.js 這類函式庫,將使用者在網頁上的操作(如點擊按鈕)轉換成區塊鏈能理解的交易指令。
  2. 顯示區塊鏈數據:從區塊鏈讀取數據(如代幣餘額、合約狀態),並將其呈現在網頁介面上。

協同工作方式

JavaScript 程式碼需要三個核心資訊才能與智能合約溝通:

1. 合約位址 (Contract Address)

就像網站需要一個 IP 位址或網址一樣,智能合約也需要一個獨一無二的「門牌號碼」。這個位址是用來在區塊鏈上精準定位你的合約。

2. ABI (應用程式二進位介面)

ABI 就像是合約的「說明書」。它用 JSON 格式列出了合約所有公開的函式,以及這些函式的名稱、參數、資料型態和回傳值。沒有 ABI,JavaScript 就不會知道如何正確地呼叫合約函式,或如何解析回傳的資料。

3. Provider (節點連線)

這是一個軟體層級的介面,它將你程式的請求,透過 RPC (Remote Procedure Call) 協定,傳送到一個或多個區塊鏈節點。當 JavaScript 程式需要讀取鏈上資料或發送交易時,會透過 Provider 把請求傳送到RPC 區塊鏈節點。MetaMask 本身不直接提供節點,而是將一個 Provider 注入到網頁,這個 Provider 通常連到 Infura(MetaMask 預設),或是使用者在設定中選擇的 RPC區塊鏈 節點。智能合約運行在鏈上,而 JavaScript 只是呼叫合約、傳送交易與接收回應的工具。

互動流程

當你用 JavaScript 程式碼呼叫一個智能合約函式時,背後會經歷以下流程:

  1. JavaScript 程式碼:你使用像 Ethers.js 這樣的函式庫,將合約位址、ABI 和 Provider 組合成一個合約物件。
  2. 呼叫函式:你對這個合約物件呼叫一個函式,例如 contract.updateMessage("Hello")
  3. 封裝成交易:Ethers.js 根據 ABI 的定義,將你的函式呼叫、參數和目標合約位址,打包成一個標準的區塊鏈交易格式。
  4. 發送交易:你的瀏覽器錢包(如 MetaMask)會簽署這個交易,然後透過 Provider 將其發送到區塊鏈網路。
  5. 區塊鏈執行:區塊鏈上的礦工或驗證者將交易打包進區塊中,並在智能合約中執行 updateMessage 這個函式。

簡而言之,智能合約是邏輯,而 JavaScript 則是將你的意圖傳達給這個邏輯的工具。它們是區塊鏈應用程式前端和後端不可或缺的組合,範例如下。


智能合約和JS互動範例

以下是己經上鏈的智能合約

https://sepolia.etherscan.io/address/0xa01ab66df445a718943faf29d14589335fe32916#code

去掉註解後的內容如下

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract HelloWorld {
    string public message;
    constructor() {
        message = "Hello World";
    }
    function updateMessage(string memory _newMessage) public {
        message = _newMessage;
    }
}

要透過JS去和智能合約互動,可參考如下


載入Dapp的JS庫

常見的區塊鏈 JavaScript 函式庫有以下

  • Ethers.js 由於其現代化的設計、更好的安全性、更小的體積以及對 async/await 的原生支援,已成為當前開發者的主流選擇。如果你是新開發者,或者正在開始一個新專案,強烈建議學習和使用 Ethers.js。
  • Web3.js 雖然較為老舊,但由於其歷史悠久,擁有龐大的用戶社群和豐富的教學資源。如果你的專案需要維護舊有程式碼,或需要一些 Ethers.js 未涵蓋的特定功能,Web3.js 仍然是一個可行的選擇。
<script src="https://cdnjs.cloudflare.com/ajax/libs/ethers/6.15.0/ethers.umd.min.js" type="application/javascript"></script>

合約地址

在 JavaScript 中定義一個常數 (constant),用來儲存智能合約在區塊鏈上的位址

const CONTRACT_ADDRESS = "0xa01aB66df445A718943Faf29D14589335FE32916";

const:一個 JavaScript 關鍵字,用來宣告一個常數。這代表 CONTRACT_ADDRESS 這個變數一旦被賦值之後,就不能再被修改,確保了程式碼的穩定性。


ABI

在JavaScript中指定合約ABI,它是 JavaScript 程式碼與智能合約溝通的「翻譯本」或「介面說明書」。

const CONTRACT_ABI = [
{ "inputs": [], "name": "message", "outputs": [{ "internalType": "string", "name": "", "type": "string" }], "stateMutability": "view", "type": "function" },
{ "inputs": [{ "internalType": "string", "name": "_newMessage", "type": "string" }], "name": "updateMessage", "outputs": [], "stateMutability": "nonpayable", "type": "function" }];

PS:ABI可在此查到 https://sepolia.etherscan.io/address/0xa01ab66df445a718943faf29d14589335fe32916#code


連接錢包

連接你的瀏覽器錢包(如 MetaMask),並建立一個可以與智能合約互動的物件。簡單來說,就是完成了授權使用者、連上區塊鏈、取得簽名權限、並初始化合約互動介面的所有工作,為接下來的合約操作做好準備。

主要做四件事如下:

1.請求使用者授權連接錢包:

以下程式碼會觸發一個 MetaMask 彈窗,要求使用者授權網頁連接他們的錢包。eth_requestAccounts 是一個標準的以太坊 API 方法,它會回傳使用者授權的錢包帳號列表。如果使用者拒絕,程式會拋出錯誤。

await window.ethereum.request({ method: "eth_requestAccounts" });

2.建立 Provider:

Provider 是 Ethers.js 中用來讀取區塊鏈資料的物件。window.ethereum 是由 MetaMask 注入到瀏覽器的一個全域物件,它提供了與以太坊網路溝通的介面。以下程式碼就是告訴 Ethers.js:「嘿,請使用 MetaMask 提供的介面來與區塊鏈連線。」

provider = new ethers.BrowserProvider(window.ethereum);

3.取得 Signer:

Signer 是用來發送交易和簽署訊息的物件。它代表了一個特定的帳號。signer 會使用你的錢包私鑰來簽署交易,但這個過程完全在 MetaMask 內部完成,程式碼永遠無法直接存取你的私鑰,這確保了安全性。當你想要呼叫一個需要支付 Gas 費用的合約函式時,就必須使用 signer

signer = await provider.getSigner();

4.建立合約實例:

以下程式碼將前面所有東西整合在一起,建立一個可用的合約物件。這個物件知道你的合約在哪裡(CONTRACT_ADDRESS)、它有哪些功能(CONTRACT_ABI),並分成以下2種:

  • 可讀可寫的實例:這個實例傳入了 signer,所以它同時具有讀取和寫入的能力。
    • 功能: 用於呼叫會改變區塊鏈狀態的函式,例如:updateMessage()transfer()
    • 特性: 這種操作需要發送交易,因此必須透過 signer 來向你的錢包請求簽名並支付 Gas 費用。
  • 可讀的實例:這個實例只傳入了 provider,所以它只具有讀取 (Read) 區塊鏈資料的能力。
    • 功能: 用於呼叫不需要改變區塊鏈狀態的函式,例如:message()balanceOf() 或任何帶有 viewpure 關鍵字的函式。
    • 特性: 這種操作不會發送交易,因此不需要 Gas 費用,也不需要你的錢包進行簽名。
contract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, sign);
readOnlyContract = new ethers.Contract(CONTRACT_ADDRESS, CONTRACT_ABI, provider);

讀取合約訊息

透過可讀的實例readOnlyContract,呼叫了合約中的 message() 函式

const message = await readOnlyContract.message();

更新合約訊息

透過可讀可寫的實例contract,呼叫了合約中的 updateMessage() 函式。因為這個函式會修改合約的狀態變數 message,所以它需要發送一筆交易。這時,你的 MetaMask 錢包會彈出一個視窗,要求你確認並支付 Gas 費用。

接著第二行程式碼會等待交易被區塊鏈確認。

const tx = await contract.updateMessage(newMessage);
await tx.wait();

tx.wait() 是一個 Ethers.js 的方法,它會監聽區塊鏈網路,直到你的交易被礦工打包進一個區塊。

await 則是告訴程式:「請在這裡停下來,不要繼續執行後面的程式碼,直到 tx.wait() 回傳一個結果。」


範例JS