智能合約教學(4) ERC20代幣水龍頭網站
依照預告,這次要實作的ERC20代幣的水龍頭網站
首先實際操作一遍水龍頭網站看看:
https://vcckvv.github.io/SmartContract/TestToken/
和之前一樣,要安裝MetaMask的瀏覽器外掛才能運作,沒有goerli的代幣可以到那個goerli水龍頭網站連結領取
這次有加入切換成goerli鏈的功能,所以不用再先自行切換,MetaMask會自動跳出一個視窗要你切換到goerli鏈
連結帳戶、切換goerli鏈完成後,接下來就按下「新增TestToken代幣資料到MetaMask」的按鈕,MetaMask會自動跳出一個視窗要你新增一種新的ERC20代幣TestToken
按下那個Add Token按鈕就可以看到Assets多了新的代幣TEST
接下來按下「獲得TestToken代幣」的按鈕,就會跳出一個視窗要求簽署交易,簽署完等待交易上鏈,就可以看到帳戶中代幣TEST多了8.00元
接下來讓我們來看一下網頁的完整程式碼:
https://github.com/vcckvv/SmartContract/blob/gh-pages/TestToken/index.html
---------------------------------------
<html>
<head>
<title>TestToken水龍頭</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
</head>
<body>
<input id="showAddress" readonly size="50" /><input id="showBalance" readonly /><br>
<button onclick="onAddTokenBtn()">新增TestToken代幣資料到MetaMask</button><br>
<button onclick="onGetTokenBtn()">獲得TestToken代幣</button><br>
<a href="https://goerlifaucet.com/" target="_blank">goerli水龍頭</a>
</body>
<script src="https://cdn.ethers.io/lib/ethers-5.1.umd.min.js"></script>
<script>
// The Contract interface
const abi = [{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"value","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"mint","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"INITIAL_SUPPLY","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"}];
const contractAddress = "0x63E1ddeB828eD1634B3165f56e2F31950b4d0B93";
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];
document.querySelector('#showAddress').value=address;
//切換到goerli測試鏈
await window.ethereum.request({method: 'wallet_switchEthereumChain', params: [{ chainId: '0x5' }],});
provider = new ethers.providers.Web3Provider(window.ethereum);
let balanceNum=await provider.getBalance(address);
document.querySelector('#showBalance').value=ethers.utils.formatEther(balanceNum);
contract = new ethers.Contract(contractAddress, abi, provider);
contractWithSigner = contract.connect(provider.getSigner() );
}catch(err){
alert(err.message);
}
}
init();
window.ethereum.on('accountsChanged', function (accounts) {
init();
});
window.ethereum.on('chainChanged', async function(networkId){
if(networkId!=5){
alert("this only works on goerli");
await window.ethereum.request({method: 'wallet_switchEthereumChain', params: [{ chainId: '0x5' }],});
}
});
async function onAddTokenBtn(){
try {
const isAdded = await window.ethereum.request({
method: 'wallet_watchAsset',
params: {
type: 'ERC20',
options: {
address: contractAddress,
symbol: "TEST",
decimals: 2,
image: "https://vcckvv.github.io/SmartContract/TestToken/icon.png",
},
},
});
}catch(error){
alert(err.message);
}
}
async function onGetTokenBtn(){
try{
let tx = await contractWithSigner.mint();
alert("https://goerli.etherscan.io/tx/" + tx.hash);
}catch(err){
if(err.code==4001){//reject
alert("User rejected tx.");
}
else{
alert(err.message);
}
}
}
</script>
</html>
---------------------------------------
解說:
let response = await window.ethereum.request({method: 'eth_requestAccounts'});
address = response[0];
document.querySelector('#showAddress').value=address;
上次沒特別提到,進行MetaMask帳戶連結後得到的response[0]就是這個帳戶的位址,這次用一個輸入框顯示出來
---------------------------------------
await window.ethereum.request({method: 'wallet_switchEthereumChain', params: [{ chainId: '0x5' }],});
這次新增了切換到goerli測試鏈的功能,那個0x5就是goerli鏈的ID,如果是smartBCH鏈的話就要換成0x2710
---------------------------------------
let balanceNum=await provider.getBalance(address);
document.querySelector('#showBalance').value=ethers.utils.formatEther(balanceNum);
這次也額外做了顯示帳戶擁有的原生代幣餘額,要注意provider.getBalance()
取得的是沒有小數點的大數,所以還要使用ethers.utils.formatEther()
轉換成有小數點的字串
---------------------------------------
window.ethereum.on('accountsChanged', function (accounts) {
init();
});
這個程式碼用來設定在使用者轉換帳號時要執行的函數,這裡只需要重新呼叫init()就好了
---------------------------------------
window.ethereum.on('chainChanged', async function(networkId){
if(networkId!=5){
alert("this only works on goerli");
await window.ethereum.request({method: 'wallet_switchEthereumChain', params: [{ chainId: '0x5' }],});
}
});
這個程式碼用來設定在使用者轉換區塊鏈時要執行的函數,如果不是goerli鏈就會要求切換回去
---------------------------------------
const isAdded = await window.ethereum.request({
method: 'wallet_watchAsset',
params: {
type: 'ERC20',
options: {
address: contractAddress,
symbol: "TEST",
decimals: 2,
image: "https://vcckvv.github.io/SmartContract/TestToken/icon.png",
},
},
});
這一段程式碼就是在帳戶中新增ERC20代幣的資料,其中image參數是這個代幣的圖片網址,弄成像素值40X40的圖片大小就好:
值得一提的是,MetaMask目前沒有提供函數來判斷帳戶Assets中有哪些ERC20代幣,而且即便已經新增過代幣,執行新增代幣的程式碼還是會跳出一個視窗,所以只能設成用按按鈕的形式來新增代幣
---------------------------------------
let tx = await contractWithSigner.mint();
這次獲取代幣的方式就只是執行鑄造函數mint(),雖然也可以弄成把合約中的儲備代幣轉移過來,不過那涉及到怎麼把代幣轉移給合約的問題,那個我們預定下一篇再說,這一篇就先解說到這裡