在上一篇文章 Web與智能合約交互實戰 中咱們已經掌握如何使用 Web3 控制智能合約,而且實現了一個簡單的Dapp,能夠經過瀏覽器簡單地實現數據的設置和讀取。css
在本文中,咱們將介紹智能合約中的 event,以及如何捕捉智能合約的 event。html
在實戰環節中,咱們將使用 Javascript API 捕捉一個 ERC20 合約中的 Transfer() 事件,並在網頁中顯示出合約中每一個帳戶的餘額。前端
event,顧名思義就是智能合約在執行過程當中所發生的一系列事件,被記錄在 EVM 的日誌中,使得程序員能夠在 Dapp 的前端頁面上調用 Javascript 的回調函數。 event 由智能合約的編寫者在代碼中使用 event 關鍵詞進行聲明,示例以下:node
event Deposit( address indexed _from, bytes32 indexed _id, uint _value );
當 event 被調用時,其中的參數會被存儲在交易的日誌當中,示例以下:jquery
function deposit(bytes32 _id) public payable { // 這裏是函數實際執行的代碼 // 激活Deposit事件,記錄在日誌中,保存在區塊鏈上 emit Deposit(msg.sender, _id, msg.value); }
使用 Javascript API 能夠** 捕捉** event,注意這裏說 捕捉 是由於咱們既能夠實時 監控 事件,也能夠從歷史區塊中 檢索 event。git
首先將 event 實例化:程序員
var event = myContractInstance.MyEvent({valueA: 23} [, additionalFilterObject])
在建立 event 實例的時候可使用下面的參數:github
Object:使用 filter 以後獲得的索引返回值,例如,filter 能夠是 {'valueA': 1, 'valueB': [myFirstAddress, mySecondAddress]}。 默認狀況下,全部 filter 的值是 null,這意味着這些值會匹配當前合約發送的任意類型的 event。web
Object:額外的過濾條件,例如可使用 fromBlock 和 toBlock 限定所要查詢的區塊的範圍,詳情能夠參考 filter。瀏覽器
Function:若是將一個回調函數做爲最後一個參數的話,就會馬上開始檢索 event,因此沒有必要再調用 myEvent.watch(function(){})。
完整的示例以下:
var MyContract = web3.eth.contract(abi); var myContractInstance = MyContract.at('0x78e97bcc5b5dd9ed228fed7a4887c0d7287344a9'); // 使用一些參數捕捉 event var myEvent = myContractInstance.MyEvent({some: 'args'}, {fromBlock: 0, toBlock: 'latest'}); myEvent.watch(function(error, result){ ... }); // 中止捕捉 myEvent.stopWatching();
在實戰環節,咱們將實現如何得到一個ERC20合約中,有多少地址擁有代幣,以及每一個地址有多少代幣。
在 ERC20 標準合約 中,有一個 event 定義以下:
event Transfer(address indexed from, address indexed to, uint tokens);
合約中每次執行 transfer() 函數的時候都會調用 Transfer() 事件,所以只須要使用 Javascript 捕捉 Transfer() 事件,就能獲取到全部執行過交易的帳戶地址。 以後使用 Web3 調用合約的 balanceOf() 方法就能夠獲取每一個帳戶地址的 token 餘額。
實現步驟以下:
啓動 Ganache 測試環境;
在 Remix 中建立 ERC20 智能合約;
編寫前端代碼,與合約交互。
前兩步關於如何使用 Ganache 搭建本地測試環境以及如何在 Remix 中建立合約的部分能夠參見個人上一篇文章 Web與智能合約交互實戰。 這裏將重點放在第三步。
建立UI
在 index.html 中,咱們將建立基礎的 UI,功能包括接收 token 的地址輸入框,發送 token 數量的輸入框(因爲這裏老是從默認帳戶發送 token,所以沒有設置發送者的地址輸入框),以及一個發送按鈕,這些將經過 jQuery 實現:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <img id="loader" src="https://loading.io/spinners/double-ring/lg.double-ring-spinner.gif"> <title>ERC20 Token Sample</title> <link rel="stylesheet" type="text/css" href="main.css"> <script src="./node_modules/web3/dist/web3.min.js"></script> </head> <body> <div class="container"> <h1>ERC20 Token</h1> <!-- <label for="name" class="col-lg-2 control-label">From</label> <input id="from" type="text"> --> <label for="name" class="col-lg-2 control-label">To</label> <input id="to" type="text"> <label for="name" class="col-lg-2 control-label">Tokens</label> <input id="tokens" type="text"> <button id="button">Send</button> <ol id="instructor"></ol> </div> <script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script> <script> // 與智能合約的交互代碼 ...... </script> </body> </html>
以及 main.css 文件設定基本的樣式:
body { background-color:#F0F0F0; padding: 2em; font-family: 'Raleway','Source Sans Pro', 'Arial'; } .container { width: 50%; margin: 0 auto; } label { display:block; margin-bottom:10px; } input { padding:10px; width: 50%; margin-bottom: 1em; } button { margin: 2em 0; padding: 1em 4em; display:block; } #instructor { padding:1em; background-color:#fff; margin: 1em 0; } .box div { padding:1em; background-color:#fff; margin: 1em 0; }
捕捉 event 事件
這部分的主要代碼以下:
<script> if (typeof web3 !== 'undefined') { web3 = new Web3(web3.currentProvider); } else { // set the provider you want from Web3.providers web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:7545")); } // 將Ganache建立的第一個帳號看成默認帳號 web3.eth.defaultAccount = web3.eth.accounts[0]; // 添加合約的ABI var tokenerc20Contract = web3.eth.contract([{"constant":true,"inputs":.....}]); // 添加合約地址 var tokenerc20 = tokenerc20Contract.at('0xdca44800d01ab7768902a11757b5ca1801a59efe'); // 聲明transfer事件,所要查找的區塊數從0-latest var transferEvent = tokenerc20.Transfer({}, {fromBlock: 0, toBlock: 'latest'}); // 經過前端進行token交易 $("#button").click(function() { $("#loader").show(); tokenerc20.transfer($("#to").val(), $("#tokens").val()); }); var holdersArray = new Array(); // 查找全部Transfer事件 transferEvent.watch(function(error, result){ if (!error) { $("#loader").hide(); // 判斷From或者To的地址是否在holdersArray中出現 var isFromExist = false; var isToExist = false; for (var i = 0; i < holdersArray.length; i++){ if (result.args.from == holdersArray[i]) { isFromExist = true; } if (result.args.to == holdersArray[i]) { isToExist = true; } } // 找的新的帳戶地址,輸出餘額信息 if (isFromExist == false) { var holder = result.args.from; holdersArray.push(holder); var balanceOfHolder = tokenerc20.balanceOf(holder) $("#instructor").append("<div>" + holder + ' has balance of ' + balanceOfHolder + "</div>"); } if (isToExist == false) { var holder = result.args.to; holdersArray.push(holder); var balanceOfHolder = tokenerc20.balanceOf(holder) $("#instructor").append("<div>" + holder + ' has balance of ' + balanceOfHolder + "</div>"); } } else { $("#loader").hide(); console.log(error); } }); </script>
首先使用 Web3 關聯 ERC20 合約,將合約的 ABI 以及合約地址都複製到代碼中。
當用戶點擊發送按鈕時,會調用合約中的 transfer() 函數(測試時可使用 Ganache 建立的10個帳戶進行交易)。
接下來咱們實例化 Transfer() 事件,將過濾條件設置爲 {fromBlock: 0, toBlock: 'latest'},即從全部區塊中篩選交易。
在回調函數中,咱們將收集全部交易中涉及到的交易雙方的地址,並將每一個新地址的餘額顯示在頁面上。
最終效果顯示以下:
文中涉及的全部代碼能夠在 https://github.com/gitferry/mastering-ethereum/tree/master/Solidity-event 獲取。
http://solidity.readthedocs.io/en/v0.4.21/contracts.html#events
https://github.com/ethereum/wiki/wiki/JavaScript-API#contract-events
http://me.tryblockchain.org/blockchain-solidity-event.html
本文內容做者:HiBlock區塊鏈社區小夥伴——蓋蓋
原文首發於公衆號: ChainLab
如下是咱們的社區介紹,歡迎各類合做、交流、學習:)