Thông thường khi chúng ta xây dựng một Dapp blockhain thì việc đồng bộ giữa giao diện người dùng và giữ liệu trên blokchain là điều rất quan trọng. Và người dùng sẽ luôn muốn thấy các thông tin được cập nhật ngay lập tức sau khi các transaction của họ được thực thi xong. Vậy thì làm sao để giao diện có thể cập nhật những thay đổi khi có vô vàn các transaction đang đượ ghi đi trên mạng. Lúc đó chúng ta sẽ sử dụng đến events trong solidity và hôm nay mình sẽ giới thiệu cho các bạn một số cách để có thể listen các events này ngay khi transaction thành công.
Giới thiệu về events
Event thì là một thành phần cũng có thể được kế thừa như Function. Nó thì thường được sử dụng để broadcast ra cho phía client biết răng hàm nào đó đã được gọi và thực thi. Nó hay được để ở cuối của Function và cũng có các tham số để phía client có thể hiểu được. Ví dụ như một token ERC20 thường emit ra một event Transfer với các thông số người gửi, người nhận và value bao nhiêu. Để các clients subscribe có thể biết được là vừa có một giao dịch chuyển token.
...// Định nghĩa event Transfer
event Transfer(address indexed from, address indexed to, uint256 value);...// Hàm transfer tokenfunctiontransfer(address to, uint256 value)publicreturns(bool){require(value <= _balances[msg.sender]);require(to !=address(0));
_balances[msg.sender]= _balances[msg.sender].sub(value);
_balances[to]= _balances[to].add(value);
emit Transfer(msg.sender, to, value);returntrue;}
Kết quả trả về sẽ có dạng như sau
Transfer(index_topic_1 address from, index_topic_2 address to, uint256 value)
address from
0x8f7f6c18039776c8b7f952185c3e75649a25b9cb
address to
0x611abc072ee91c0cc19ffef97ac7e69a1a7a17ec
uint256 value
6538744793000000000000[topic0]0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef[topic1]0x0000000000000000000000008f7f6c18039776c8b7f952185c3e75649a25b9cb[topic2]0x000000000000000000000000611abc072ee91c0cc19ffef97ac7e69a1a7a17ec
Chuẩn bị
Việc đầu tiên cần chuẩn bị test event là chúng ta phải có một provider web3 và ABI của contract muốn subcribe
const Web3 =require('web3');constABI=require('./ABI-WETH.json');const web3 =newWeb3('wss://mainnet.infura.io/ws/v3/<PROJECT_ID_INFURA>');constCONTRACT_ADDRESS='0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2';const myContract =newweb3.eth.Contract(ABI,CONTRACT_ADDRESS);
Trong bài này mình sử dụng RPC của infura thì mọi người có thể tham khảo cách get một RPC Infura tại đây: https://blog.infura.io/getting-started-with-infura-28e41844cc89/
Còn ABI ngoài việc phải compiler để có được thì đối với một số contract public và đã verified chúng ta có thể lên etherscan.io hoặc bscscan.com để lấy ví dụ như ở đây là https://etherscan.io/address/0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2#code và đến phần Contract ABI để lấy ABI của chúng.
1. Getting past events
Thực ra cách này thì giống với query data blockchain hơn nhưng mình vẫn muốn giới thiệu đến với mọi người. Do các events được lưu trữ trên blockchain nên ta có thể query nó bất cứ khi nào. Chúng ta sẽ sử dụng method getPastEvents có sẵn trong instance mà phía trên ta đã tạo để lấy về các events trong quá khứ. Ví dụ như ở đây chúng ta sẽ get về tất các event Transfer của contract ERC20 WETH từ block 12856158
đến block mới nhất
asyncfunctionexample1(){let options ={
filter:{
value:['1000','1337']//Only get events where transfer value was 1000 or 1337},
fromBlock:12856158,//Number || "earliest" || "pending" || "latest"
toBlock:'latest'};
myContract.getPastEvents('Transfer', options).then(result=> console.log('result', result)).catch(err=> console.log('error', err.message, err.stack));}
Kết quả:
...{
address:'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
blockHash:'0x5d6592c6f8be4443dd79b27fafa35bb58fd8b71161a5d58715a8888a0b186601',
blockNumber:12856161,
logIndex:287,
removed:false,
transactionHash:'0x7c9d4b3655b89179b09bc025ecc12530eb38111c7cc79312c85786bd7d8a398a',
transactionIndex:180,
id:'log_980923fb',
returnValues: Result {'0':'0x05f21E62952566CeFb77F5153Ec6B83C14FB6b1D','1':'0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D','2':'20568368904018071',
src:'0x05f21E62952566CeFb77F5153Ec6B83C14FB6b1D',
dst:'0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',
wad:'20568368904018071'},
event:'Transfer',
signature:'0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
raw:{
data:'0x000000000000000000000000000000000000000000000000004912d292221897',
topics:[Array]}},...404 more items
]
Như ở trên bạn cũng có thể thấy là còn thể filter với các điều kiện như fromBlock từ block thứ bao nhiêu , form address nào, to block nào.
2. Contract instance event method
Cách thứ 2 là sử dụng trực tiếp từ methods event trong instance contract mà ta đã tạo. Mọi người hãy để ý khi ta tạo instance trong phần events sẽ có tất cả các events mà ta đã định nghĩa trong contact
...
events:{
Approval:[Function: bound ],'0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925':[Function: bound ],'Approval(address,address,uint256)':[Function: bound ],
Transfer:[Function: bound ],'0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef':[Function: bound ],'Transfer(address,address,uint256)':[Function: bound ],
Deposit:[Function: bound ],'0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c':[Function: bound ],'Deposit(address,uint256)':[Function: bound ],
Withdrawal:[Function: bound ],'0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65':[Function: bound ],'Withdrawal(address,uint256)':[Function: bound ],
allEvents:[Function: bound ]},
_address:'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
_jsonInterface:[...
Ta cũng có thể sử dụng trực tiếp các methods này để lắng nghe sự kiện
functionexample2(){let options ={
filter:{
value:[],},
fromBlock:12856518};
myContract.events.Transfer(options).on('data',event=> console.log(event)).on('changed',changed=> console.log(changed)).on('error',err=> console.log('error', err.message, err.stack)).on('connected',str=> console.log(str))}
Mỗi method events sẽ trả về một EventEmitter. Event này sẽ lắng nghe các sự kiện như dưới đây.
- data – Nó sẽ được kích hoạt mỗi khi có một event Transfer gọi đến contract được emited
- changed – Nó sẽ được kích hoạt mỗi khi event bị xóa khỏi blockchain
- error – Sẽ được kích hoạt khi có lỗi trong quá trình subscription events
- connected – Sẽ được kích hoạt khi thiết lập subscription đã thành công. Nó sẽ trả về một subscription id và điều này chỉ xảy ra một lần duy nhất.
Kết quả như sau
...{
address:'0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
blockHash:'0xee2da8ff4b39294545ed2af49ee40c17ddad9c12d21d0733554a2579b2ca006b',
blockNumber:12856552,
logIndex:252,
removed:false,
transactionHash:'0x1871d9dbe683796d4219ed34403495dd5bf8784e9f73e92602adcac9ebd887fe',
transactionIndex:147,
id:'log_190e2a37',
returnValues: Result {'0':'0x11b815efB8f581194ae79006d24E0d814B7697F6','1':'0xE592427A0AEce92De3Edee1F18E0157C05861564','2':'1667655933682553825',
src:'0x11b815efB8f581194ae79006d24E0d814B7697F6',
dst:'0xE592427A0AEce92De3Edee1F18E0157C05861564',
wad:'1667655933682553825'},
event:'Transfer',
signature:'0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
raw:{
data:'0x0000000000000000000000000000000000000000000000001724b43c6eb893e1',
topics:['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef','0x00000000000000000000000011b815efb8f581194ae79006d24e0d814b7697f6','0x000000000000000000000000e592427a0aece92de3edee1f18e0157c05861564']}}...
3. The eth subscribe method
Kiểu này là một cách tổng quát nhất để có thể lắng tất cả các logs events đã được emited. Và nếu muốn lọc events thì bạn phải thiết lập trong phần options
functionexample3(){let options ={
fromBlock:12856551,
address:['address-1','address-2'],//Only get events from specific addresses
topics:[]//What topics to subscribe to};let subscription = web3.eth.subscribe('logs', options,(err,event)=>{if(!err)
console.log(event)});
subscription.on('data',event=> console.log(event))
subscription.on('changed',changed=> console.log(changed))
subscription.on('error',err=> console.log('error', err.message, err.stack))
subscription.on('connected',nr=> console.log(nr))}
Kiểu subscribe này sẽ trả lại một Subscription instance nó cũng là một EventEmitter. Vì vậy các events cũng giống với cách số 2, ngoài ra nó cũng có thêm một vài properties khác như:
- id – Đây là id của subscription
- unsubscribe(callback) – Method này được sử dụng để unsubscribe
- subscribe(callback) – Method này được sử dụng để re-subscribe khởi tạo lại subscription với những parameters đã có trước đó
- arguments – Subscription arguments như ở trên thì được nó được sử dụng để re-subscribe.
4. Listen pending transactions
Thực ra cách này thì cũng không được coi là cách để listen events đúng lắm. Nhưng cách này là cách chúng ta có thể lắng nghe sự kiện kể từ khi transaction còn đang ở status pending và chưa được confirm. Nó phù hợp để sử dụng cho các trường hợp cần tạo transaction với tốc độc nhanh giống như front-running
functionexample4(){let subscription = web3.eth.subscribe('pendingTransactions',function(error, result){if(!error)
console.log(result)});
subscription.on('data',event=> console.log(event))
subscription.on('changed',changed=> console.log(changed))
subscription.on('error',err=> console.log('error', err.message, err.stack))
subscription.on('connected',nr=> console.log(nr))// unsubscribes the subscription
subscription.unsubscribe(function(error, success){if(success)
console.log('Successfully unsubscribed!');});}
Kết quả ta sẽ thấy rất nhiều các hash transaction pending mới được tạo và đang ở trong mempool. Ở đây ta có thể get detail từng transaction sau đó dùng một package decode value trong params của transacion là ta có thể thấy được các tông tin cần thiết. Để có thể ngay lập tạo một transaction khác front-running.
$ node index
0x0a3b8d59019e69890acbf83ac37941654b1cc38aa403dec48ff8c06655b94d600xb987c738efeaffa5264b3f742473217b02d4f46b957b10bec8a17e166d78e5ae0xf709f281a8bd7b8f3e9a1d179795a897184ae325ba16a9d4659e6f70149d1e300xf8fee92a9d0acb71c98924790fff4adb4783b67af166ba8b21ac6c60d1a8f1fc0xc1a7c3f539e97f3c3f5f92ee1baae8e19e218d04768fd902aa3b4d4f00c3fe2a0x060b5f0bc264a49946b33368c25eac9e1dc811ae05b0a4a67d296685aa508fc00xdc6ca9e3f06553f7d085938a650dd0f54158789734362ec6ff524f0affeb84220x0bc0b31ee1031f8756209d1ce36aabed5b16199b4647bab5a95b94ae9c6b2d3e0xc060e657437b13cc2034b0183d551cb46710e18958dd680ace3c30fc9d95a1810x218ee31c6081b23d94bc325b2f95cc2a02ed3aa4225b806de01294119d9c1be30xbb39ce1d5d1c301dbb82c46e769b964ffb476d612fce913b39e15ef0f4f5ba510xa4fc54a17bb947492cef5c2ee511819b263715afd659eef12b10cda63edbb2470xae10c65552d34c56005b4762fe8eb3e7d98d552ee43e38b97b56e5ac48187b390x362d3d74d230befac200df6d79c4189b894d04bfc0ea7b4a85956d7923efcb9c0xf4a68c61014ed3ba4a5555ce7b2905a3ddb6e518b1652fcaec76d293ed21adc90x8274ff71b177bd735f574f46ee105b886530f507779d28cc07a3df58f23acc1e0xf55c854f80ec3a46c5ba379ccbe4f4a4e4c61b97d38403db77a83caacdfd03980x6810428528670494b12206e94973f1223a8fde8f13d6e5a14432265944474f340xc1a158f038054f0aaca34baa4aaffba69410795e361657d47967861d187180500xcdf04bdb4987737a28cbe759ca50cfc45af9e31da485cec89cb19b7929aaf0f50x0070afc5b8849edf32614969b8e11564801ab1ae8cdfedb128a9c03c285582b20xe9da592af30ccd51c10e83a3ec15ae7366253ac9dd41100d0cb5c05eaf029827
Package mình sử dụng để decode value params transacion là cái này: https://www.npmjs.com/package/ethereum-input-data-decoder
Tổng kết
Bài viết này mình muốn giới thiệu đến mọi người các cách để có thể lắng nghe được các events solidity trong blockchain với việc sử dụng web3js. Mong là sẽ giúp mọi người có thể chọn được cách phù hợp nhất để lắng nghe events cho Dapp của mình.
Nguồn: viblo.asia