1) Full code
2) Count component's role
3) How to interact with contract?
4) Interact with contract: getCount method
5) Interact with contract: setPlus method
6) Transaction life cycle
1) Full code
import React, { Component } from'react'import cx from'classnames'import { cav } from'klaytn/caver'import'./Count.scss'classCountextendsComponent {constructor() {super()// ** 1. Create contract instance **// ex:) new cav.klay.Contract(DEPLOYED_ABI, DEPLOYED_ADDRESS)// You can call contract method through this instance.// Now you can access the instance by `this.countContract` variable.this.countContract =DEPLOYED_ABI&&DEPLOYED_ADDRESS&&newcav.klay.Contract(DEPLOYED_ABI,DEPLOYED_ADDRESS)this.state = { count:'', lastParticipant:'', isSetting:false, } } intervalId =nullgetCount=async () => {// ** 2. Call contract method (CALL) **// ex:) this.countContract.methods.methodName(arguments).call()// You can call contract method (CALL) like above.// For example, your contract has a method called `count`.// You can call it like below:// ex:) this.countContract.methods.count().call()// It returns promise, so you can access it by .then() or, use async-await.constcount=awaitthis.countContract.methods.count().call()constlastParticipant=awaitthis.countContract.methods.lastParticipant().call()this.setState({ count, lastParticipant, }) }setPlus= () => {constwalletInstance=cav.klay.accounts.wallet &&cav.klay.accounts.wallet[0]// Need to integrate wallet for calling contract method.if (!walletInstance) returnthis.setState({ settingDirection:'plus' })// 3. ** Call contract method (SEND) **// ex:) this.countContract.methods.methodName(arguments).send(txObject)// You can call contract method (SEND) like above.// For example, your contract has a method called `plus`.// You can call it like below:// ex:) this.countContract.methods.plus().send({// from: '0x952A8dD075fdc0876d48fC26a389b53331C34585', // PUT YOUR ADDRESS// gas: '200000',// })try{this.countContract.send({ from:walletInstance.address, gas:'200000', },'plus').then((receipt) => {console.log(` Received receipt! It means your transaction(calling plus function) is in klaytn block(#${receipt.blockNumber}) `, receipt)this.setState({ settingDirection:null, txHash:receipt.transactionHash, }) }) } catch (error) {alert(err.message)this.setState({ settingDirection:null }) } }setMinus= () => {constwalletInstance=cav.klay.accounts.wallet &&cav.klay.accounts.wallet[0]// Need to integrate wallet for calling contract method.if (!walletInstance) returnthis.setState({ settingDirection:'minus' })// 3. ** Call contract method (SEND) **// ex:) this.countContract.methods.methodName(arguments).send(txObject)// You can call contract method (SEND) like above.// For example, your contract has a method called `minus`.// You can call it like below:// ex:) this.countContract.methods.minus().send({// from: '0x952A8dD075fdc0876d48fC26a389b53331C34585', // PUT YOUR ADDRESS// gas: '200000',// })// It returns event emitter, so after sending, you can listen on event.// Use .on('transactionHash') event,// : if you want to handle logic after sending transaction.// Use .once('receipt') event,// : if you want to handle logic after your transaction is put into block.// ex:) .once('receipt', (data) => {// console.log(data)// })try{this.countContract.send({ from:walletInstance.address, gas:'200000', },'minus').then((receipt) => {console.log(` Received receipt! It means your transaction(calling minus function) is in klaytn block(#${receipt.blockNumber}) `, receipt)this.setState({ settingDirection:null, txHash:receipt.transactionHash, }) }) } catch (error) {alert(err.message)this.setState({ settingDirection:null }) } }componentDidMount() {this.intervalId =setInterval(this.getCount,1000) }componentWillUnmount() {clearInterval(this.intervalId) }render() {const { lastParticipant,count,settingDirection,txHash } =this.statereturn ( <divclassName="Count"> {lastParticipant && ( <divclassName="Count__lastParticipant"> last participant: {lastParticipant} </div> )} <divclassName="Count__count">COUNT: {count}</div> <buttononClick={this.setPlus}className={cx('Count__button', {'Count__button--setting': settingDirection ==='plus', })} > + </button> <buttononClick={this.setMinus}className={cx('Count__button', {'Count__button--setting': settingDirection ==='minus', })} > - </button> {txHash && ( <divclassName="Count__lastTransaction"> <pclassName="Count__lastTransactionMessage"> You can check your last transaction in klaytn scope: </p> <atarget="_blank"href={`https://baobab.klaytnfinder.io/tx/${txHash}`}className="Count__lastTransactionLink" > {txHash} </a> </div> )} </div> ) }}exportdefault Count
2) Count component's role
'Count' component's role is interacting with Count contract deployed on the Klaytn blockchain.
In Count.sol, we declared several variables and functions like below:
1.count
2.lastParticipant
3.plus - increase count storage variable by 1. (count = count + 1)
4.minus - decrease count storage variable by 1. (count = count - 1)
In Count.js component, we have methods to interact with the functions and variables of the Count contract.
3) How to interact with contract?
To interact with the contract, we need a contract instance of the deployed contract.
The contract instance can be made through caver.klay.Contract(ABI, contractAddress) API of caver-js. For more details, see caver.klay.Contract.
With Contract ABI(Application Binary Interface), caver can call the contract method as if it is a local function,
for example)
contractInstance.methods.count().call()contractInstance.methods.plus().send({ ... })contractInstance.methods.minus().send({ ... })
Contract address can be found in the build/contracts/Count.json file after compiling and deploying the contract. For your testing convenience, we deployed the contract to the Klaytn testnet, and included the deployedABI and deployedAddress files in the directory. Those files contain the ABI of the Count contract and the deployed contract address.
Thanks to the webpack configuration, we can access them via variables. (DEPLOYED_ADDRESS, DEPLOYED_ABI)
For example)
DEPLOYED_ADDRESS returns the deployed contact ddress.
DEPLOYED_ABI returns the Count contract ABI.
constructor() {super()// ** 1. Create contract instance **// ex:) new cav.klay.Contract(DEPLOYED_ABI, DEPLOYED_ADDRESS)// You can call contract method through this instance.// Now you can access the instance by `this.countContract` variable.this.countContract =DEPLOYED_ABI&&DEPLOYED_ADDRESS&&newcav.klay.Contract(DEPLOYED_ABI,DEPLOYED_ADDRESS)...}
this.countContract = new cav.klay.Contract(DEPLOYED_ABI, DEPLOYED_ADDRESS) creates a contract instance to interact with the deployed Count contract, by passing DEPLOYED_ABI and DEPLOYED_ADDRESS to the cav.klay.Contract API. And this contract instance is stored to this.countContract.
4) Interact with contract: getCount method
getCount=async () => {// ** 2. Call contract method (CALL) **// ex:) this.countContract.methods.methodName(arguments).call()// You can call contract method (CALL) like above.// For example, your contract has a method called `count`.// You can call it like below:// ex:) this.countContract.methods.count().call()// It returns promise, so you can access it by .then() or, use async-await.constcount=awaitthis.countContract.methods.count().call()constlastParticipant=awaitthis.countContract.methods.lastParticipant().call()this.setState({ count, lastParticipant, })}
Since we have a contract instance, we can call contract methods. Contract instance has a property, methods.
It contains the functions of the contract, for example, count, lastParticipant, plus, and minus.
In the above code, getCount function is declared as async, since contract function call returns a promise object. We can fetch the count by calling this.countContract.methods.count().call().
We can fetch the lastParticipant address by calling this.countContract.methods.lastParticipant().call().
After fetching those variables, we set the state properties, count and lastParticipant with the received values.
For further information about calling contract methods, see caver.klay.Contract
We want to fetch the count variable per 1 second, it can be achieved by setInterval.
It is the same as we did in the getBlockNumber in BlockNumber.js which calls caver.klay.getBlockNumber() intervally.
5) Interact with contract: setPlus method
setPlus= () => {constwalletInstance=cav.klay.accounts.wallet &&cav.klay.accounts.wallet[0]// Need to integrate wallet for calling contract method.if (!walletInstance) returnthis.setState({ settingDirection:'plus' })// 3. ** Call contract method (SEND) **// ex:) this.countContract.methods.methodName(arguments).send(txObject)// You can call contract method (SEND) like above.// For example, your contract has a method called `plus`.// You can call it like below:// ex:) this.countContract.methods.plus().send({// from: '0x952A8dD075fdc0876d48fC26a389b53331C34585', // PUT YOUR ADDRESS// gas: '200000',// })try{this.countContract.send({ from:walletInstance.address, gas:'200000', },'plus').then((receipt) => {console.log(` Received receipt! It means your transaction(calling plus function) is in klaytn block(#${receipt.blockNumber}) `, receipt)this.setState({ settingDirection:null, txHash:receipt.transactionHash, }) }) } catch (error) {alert(err.message)this.setState({ settingDirection:null }) }}
setPlus function is the most important part in Count component. It interacts with the contract by calling contract function plus. Since this function is also a contract method, it is contained in the this.counterContract.methods.
However, unlike count and lastParticipant that just reads data, plus function writes data to the Klaytn blockchain.
Reading data is free, however writing data incurs cost for the use of computation and storage. The cost is measured by the amount of gas used.
By this reason, sending a transaction needs from: property to inform the Klaytn node who is responsible for the transaction fee. gas: property defines the maximum amount of gas the transaction sender is willing to pay for the transaction.
To send a transaction, use .send() instead of .call().
.send({ from:..., gas:...})
6) Transaction life cycle
try{this.countContract.send({ from:walletInstance.address, gas:'200000', },'plus').then((receipt) => {console.log(` Received receipt! It means your transaction(calling plus function) is in klaytn block(#${receipt.blockNumber}) `, receipt)this.setState({ settingDirection:null, txHash:receipt.transactionHash, }) })} catch (error) {alert(err.message)this.setState({ settingDirection:null })}
After sending a transaction, you can get the transaction status along the life cycle.
transactionHash event is fired when you get the transaction hash. It is available before sending the transaction over the network.
receipt is fired,when you can get the transaction receipt. It means you transaction got into the block. You can get the block number that contains your transaction by receipt.blockNumber.
error is fired when an error occurred while sending a transaction.
cf) settingDirection is used to display a loading indicator(gif). Once the transaction is included in a block, remove the loading indicator by assigning null to settingDirection.
You will send a transaction which calls the contract method, plus.
Just after sending a transaction, you will receive the transaction hash.
3-a. After your transaction has been processed and included in a block, you will receive the transaction receipt.
3-b. If there were errors while sending a transaction, you will receive an error. And receipt block will never be called.
Full code for invoking plus method is as below:
try{this.countContract.send({ from:walletInstance.address, gas:'200000', },'plus').then((receipt) => {console.log(` Received receipt! It means your transaction(calling plus function) is in klaytn block(#${receipt.blockNumber}) `, receipt)this.setState({ settingDirection:null, txHash:receipt.transactionHash, }) })} catch (error) {alert(err.message)this.setState({ settingDirection:null })}
How can I check my transaction in the blockchain?
After sending a transaction, you can check your transaction detail using Klaytnscope.
Check it in https://baobab.scope.klaytn.com/tx/${txHash}.