智能合約教學(2) 和Hello World合約互動的Web3網頁

0 17
Avatar for vcckvv
Written by
2 years ago

這次要來寫一個網頁程式來和前一篇發布的合約進行互動,這裡假設看的人已經對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代幣,之後還會弄個水龍頭網站,敬請期待~

1
$ 0.00
Avatar for vcckvv
Written by
2 years ago

Comments