智能合約教學(2) 和Hello World合約互動的Web3網頁
這次要來寫一個網頁程式來和前一篇發布的合約進行互動,這裡假設看的人已經對HTML和JavaScript有一定的了解,所以只會談論連結MetaMask帳戶、和合約互動的程式碼的部份
首先先實際操作一下完成的網頁:https://vcckvv.github.io/SmartContract/HelloWorld/
沒有MetaMask錢包的人要先安裝MetaMask錢包瀏覽器外掛,因為這個網頁是預設和MetaMask進行連結
第一次連進去應該會要求帳戶的連線,由於這個程式沒有寫切換到goerli測試鏈的功能,所以記得要手動切換到goerli測試鏈上,另外如果沒goerli的幣的話,可以先去水龍頭網站領取:https://goerlifaucet.com/
按下getValue的按鈕會執行合約中的get()函數,也就是取得合約中value的資料,然後顯示在左邊的輸入框上
按下setValue的按鈕則會執行合約中的set()函數,會把左邊的輸入框中的文字當作輸入參數來執行,按下後會跳出MetaMask的交易確認,接著網頁會等到交易上鏈後再自動執行get()函數,由此顯示出我們的set()函數操作有正確改變合約中value的資料
值得注意的是,如果有多個人在同時間測試的話,value會被設定成最後一個人修改的資料,這個時候就要注意一下合約的交易情況:
https://goerli.etherscan.io/address/0x8abb77ff22f8966729603bd49c5c049c4294efd5
接下來看一下完整網頁程式:https://github.com/vcckvv/SmartContract/blob/gh-pages/HelloWorld/index.html
----------------------------------------------------
<html>
<head>
<title>Hello World</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<input id="showValue" readonly></input><button onclick="onGetValueBtn()">getValue</button><br>
<input id="newValue" ></input><button onclick="onSetValueBtn()">setValue</button>
</body>
<script src="https://cdn.ethers.io/lib/ethers-5.1.umd.min.js"></script>
<script>
// The Contract interface
const abi = [{"constant": false,"inputs": [{"name": "v","type": "string"}],"name": "set","outputs": [],"payable": false,"stateMutability": "nonpayable","type": "function"},{"constant": true,"inputs": [],"name": "get","outputs": [{"name": "","type": "string"}],"payable": false,"stateMutability": "view","type": "function"},{"inputs": [],"payable": false,"stateMutability": "nonpayable","type": "constructor"}];
const contractAddress = "0x8abb77ff22f8966729603bd49c5c049c4294efd5";
let address = null;
let provider = null;
let contract = null;
let contractWithSigner = null;
async function init(){
try{
let response = await window.ethereum.request({method: 'eth_requestAccounts'});
address = response[0];
provider = new ethers.providers.Web3Provider(window.ethereum);
contract = new ethers.Contract(contractAddress, abi, provider);
contractWithSigner = contract.connect(provider.getSigner() );
}catch(err){
alert(err.message);
}
}
init();
async function onGetValueBtn(){
try{
let currentValue = await contract.get();
document.querySelector('#showValue').value=currentValue;
}catch(err){
alert(err.message);
}
}
async function onSetValueBtn(){
try{
let newValue=document.querySelector('#newValue').value;
let tx = await contractWithSigner.set(newValue);
alert(tx.hash);
document.querySelector('#showValue').value="";
await tx.wait();
onGetValueBtn();
}catch(err){
if(err.code==4001){//reject
alert("User rejected tx.");
}
else{
alert(err.message);
}
}
}
</script>
</html>
--------------------------------
由於MetaMask沒辦法在打開本機上的HTML檔時開啟功能,所以在測試的時候要放到伺服器上來進行測試,我一般都是在自己電腦上建立一台網頁伺服器,然後放在上面進行測試比較方便,使用的軟體是Http File Server:https://www.rejetto.com/hfs/
----------------------------------------------------
這次使用的函數庫是ethers.js,個人的使用經驗上感覺非常便利,這裡只需要簡單地引用這個js檔就能使用:
<script src="https://cdn.ethers.io/lib/ethers-5.1.umd.min.js"></script>
----------------------------------------------------
const abi = [{"constant": false,"inputs": [{"name": "v","type": "string"}],"name": "set","outputs": [],"payable": false,"stateMutability": "nonpayable","type": "function"},{"constant": true,"inputs": [],"name": "get","outputs": [{"name": "","type": "string"}],"payable": false,"stateMutability": "view","type": "function"},{"inputs": [],"payable": false,"stateMutability": "nonpayable","type": "constructor"}];
這串abi代表合約的函數介面資料,之前在Remix進行編譯時(Compile HelloWorld.sol),編譯完介面底下會出現一個ABI的按鈕,按下去就會把產生的ABI資料複製起來,然後就可以貼到程式碼這邊
要注意之後在處理多個contract的情況時,要在上面的CONTRACT下拉表中選對發布的contract,才能得到正確的ABI
----------------------------------------------------
let response = await window.ethereum.request({method: 'eth_requestAccounts'});
address = response[0];
provider = new ethers.providers.Web3Provider(window.ethereum);
contract = new ethers.Contract(contractAddress, abi, provider);
contractWithSigner = contract.connect(provider.getSigner() );
這一段就是進行MetaMask的帳戶連結,然後產生provider物件,provider物件基本功能應該是和RPC Server相連線,由此和合約進行互動
RPC Server的網址就是MetaMask設定的區塊鏈的資料中的RPC URL,如果不是商業用的RPC Server也許會有些流量限制,不過測試的時候應該不用考慮那麼多,直接用預設的免費RPC Server就好
產生provider物件之後再產生contract和contractWithSigner物件,這兩個後面和合約互動時會用到
----------------------------------------------------
let currentValue = await contract.get();
document.querySelector('#showValue').value=currentValue;
要執行合約中讀取性質的函數的方法很簡單,像是我們要執行合約中的get(),就只需要寫成contract.get()就可以了,要注意這個動作會動用到網路,所以是非同步函數,因此要用await去等待值回傳回來
而在JavaScript使用到await的話,函數就要加上async,代表這個函數裡面有非同步的指令,而我們這裡的函數都有用到,所以都有加上async
----------------------------------------------------
let newValue=document.querySelector('#newValue').value;
let tx = await contractWithSigner.set(newValue);
alert(tx.hash);
document.querySelector('#showValue').value="";
await tx.wait();
onGetValueBtn();
而要執行合約中寫入性質的函數的方法也很簡單,像是我們要執行合約中的set(),就只需要寫成contractWithSigner.set()
然後再填入輸入的參數就可以了
執行這個函數後回傳的是交易資料tx,我們可以用await tx.wait()
來等待交易上鏈完成,然後繼續其他的動作,這裡是執行onGetValueBtn()
用來確定我們的值有真的在鏈上被修改掉
值得一提的是,如果在MetaMask中把pending的交易啟用加速功能(需要多給費用),那麼即使交易上鏈完成,這裡的tx.wait()也沒辦法回傳繼續行動,這個也不能說算是MetaMask的bug,因為本質上算是換成不同的交易
----------------------------------------------------
和合約互動的基本功能這樣就完成了,不過還是有些東西沒有提到,像是切換區塊鏈、偵測帳號切換、取得帳號餘額,這些之後會再加進來講解,這裡只寫一些最基本的功能
下一篇要來解說怎麼製作自己的ERC20代幣,之後還會弄個水龍頭網站,敬請期待~