Migrating Ethereum App to Klaytn

Table of Contents

1. Introduction

This tutorial is intended to give a guide to migrate an Ethereum App to Klaytn. No previous Klaytn experience is needed. A simple blockchain app will be used as a sample to show how to migrate an Ethereum App to Klaytn.
We will focus only on the code modifications required to migrate an Ethereum App to Klaytn. If you need details on creating a Klaytn BApp, Please refer to CountBApp Tutorial.
Source Code Complete source code can be found on GitHub at https://github.com/klaytn/countbapp

Intended Audience

    We assume that you have basic knowledge on React. Sample code is made with React.
    Basic knowledge and experience in Blockchain app is required, but no previous Klaytn experience is needed.

Testing Environment

CountBApp is tested in the following environment.
    MacOS Mojave 10.14.5
    Node 10.16.0 (LTS)
    npm 6.9.0
    Python 2.7.10

2. Klaytn has Ethereum compatibility

Klaytn runtime environment is compatible with Ethereum Virtual Machine and executes smart contracts written in Solidity. Klaytn's RPC APIs and other client libraries maintain almost identical API specifications with Ethereum's whenever available. Therefore, it is fairly straightforward to migrate Ethereum Apps to Klaytn. This helps developers easily move to a new blockchain platform.

3. Change node connection from Ethereum to Klaytn

First, you need to change the library that makes a connection to the node. Then you will specify the node URL in 'rpcURL'.
    In Ethereum BApp example
      web3 library connects to and communicates with Ethereum node.
      Ropsten testnet URL is assigned to 'rpcURL' .
    In Klaytn BApp example
      caver-js library is used to connect to and communicate with Klaytn node.
      Baobab testnet URL is assigned to 'rpcURL'.
src/klaytn/caver.js
1
// import Web3 from 'web3'
2
import Caver from 'caver-js'
3
4
// const ROPSTEN_TESTNET_RPC_URL = 'https://ropsten.infura.io/'
5
const BAOBAB_TESTNET_RPC_URL = 'https://your.en.url:8651/'
6
7
// const rpcURL = ROPSTEN_TESTNET_RPC_URL
8
const rpcURL = BAOBAB_TESTNET_RPC_URL
9
10
// const web3 = new Web3(rpcURL)
11
const caver = new Caver(rpcURL)
12
13
// export default web3
14
export default caver
Copied!

4. Interact with Klaytn node: BlockNumber component

blocknumber component
BlockNumber component gets the current block number every 1 second (1000ms).
By simply replacing the web3 library with caver-js, you can sync Klaytn's BlockNumber in real-time instead of Ethereum's BlockNumber.
1
// import web3 from 'ethereum/web3'
2
import caver from 'klaytn/caver'
3
4
class BlockNumber extends Component {
5
state = { currentBlockNumber: '...loading' }
6
7
getBlockNumber = async () => {
8
// const blockNumber = await web3.eth.getBlockNumber()
9
const blockNumber = await caver.klay.getBlockNumber()
10
11
this.setState({ currentBlockNumber: blockNumber })
12
}
13
// ...
14
}
15
16
export default BlockNumber
Copied!
For more detail about BlockNumber component, see CountBApp tutorial - Blocknumber Component.

5. Interact with the contract: Count component

count component
To interact with the contract, we need to create an instance of the deployed contract. With the instance, we can read and write the contract's data.
Let's learn step by step how to migrate CountBApp from Ethereum to Klaytn!
    5-1. Deploy Count contract on Klaytn
    5-2. Create a contract instance
    5-3. Interact with contract

5-1. Deploy Count contract on Klaytn

The first step is deploying Count contract on Klaytn and get the contract address. Most of the cases, you can use Etherem contracts on Klaytn without modification. See Porting Etherem Contract. In this guide, we will use Truffle to deploy the contract.
    1.
    Change network properties in truffle-config.js to deploy the contract on Klaytn.
    2.
    Top up your account using KLAY faucet.
    3.
    Type $ truffle deploy --network baobab --reset
    4.
    Count contract will be deployed on Baobab testnet, Klaytn.
truffle-config.js
1
// const HDWalletProvider = require("truffle-hdwallet-provider")
2
const HDWalletProvider = require("truffle-hdwallet-provider-klaytn")
3
4
// const NETWORK_ID = '3' // Ethereum, Ropsten testnet's network id
5
const NETWORK_ID = '1001' // Klaytn, Baobab testnet's network id
6
7
// const RPC_URL = 'https://ropsten.infura.io/'
8
const RPC_URL = 'https://your.en.url:8651'
9
10
// Change it to your own private key that has enough KLAY to deploy contract
11
const PRIVATE_KEY = '0x3de0c90ce7e440f19eff6439390c29389f611725422b79c95f9f48c856b58277'
12
13
14
module.exports = {
15
networks: {
16
/* ropsten: {
17
provider: () => new HDWalletProvider(PRIVATE_KEY, RPC_URL),
18
network_id: NETWORK_ID,
19
gas: '8500000',
20
gasPrice: null,
21
}, */
22
23
baobab: {
24
provider: () => new HDWalletProvider(PRIVATE_KEY, RPC_URL),
25
network_id: NETWORK_ID,
26
gas: '8500000',
27
gasPrice: null,
28
},
29
},
30
compilers: {
31
solc: {
32
version: '0.5.6',
33
},
34
},
35
}
Copied!
For more details about deploying contracts, See CountBapp tutorial - Deploy Contract.

5-2. Create a contract instance

You can create a contract instance with the caver-js API. The contract instance creates a connection to Count contract. You can invoke contract methods through this instance.
src/components/Count.js
1
// import web3 from 'ethereum/web3'
2
import caver from 'klaytn/caver'
3
4
class Count extends Component {
5
constructor() {
6
/* const CountContract = DEPLOYED_ABI
7
&& DEPLOYED_ADDRESS
8
&& new web3.eth.Contract(DEPLOYED_ABI, DEPLOYED_ADDRESS) */
9
10
this.countContract = DEPLOYED_ABI
11
&& DEPLOYED_ADDRESS
12
&& new cav.klay.Contract(DEPLOYED_ABI, DEPLOYED_ADDRESS)
13
}
14
15
// ...
16
}
17
export default Count
Copied!

5-3. Interact with contract

The ABI (Application Binary Interface) used to create the Count contract instance allows the caver-js to invoke contract's methods as below. You can interact with Count contract as if it were a JavaScript object.
    Read data (call)
    CountContract.methods.count().call()
    Write data (send)
    CountContract.methods.plus().send({ ... })
    CountContract.methods.minus().send({ ... })
Once you created a contract instance as in the previous step, you don't need to modify any code in using the contract methods afterward. BApp migration has been completed!

Full code: Count component

src/components/Count.js
1
import React, { Component } from 'react'
2
import cx from 'classnames'
3
4
import caver from 'klaytn/caver'
5
6
import './Count.scss'
7
8
class Count extends Component {
9
constructor() {
10
super()
11
// ** 1. Create contract instance **
12
// ex:) new caver.klay.Contract(DEPLOYED_ABI, DEPLOYED_ADDRESS)
13
// You can call contract method through this instance.
14
// Now you can access the instance by `this.countContract` variable.
15
this.countContract = DEPLOYED_ABI
16
&& DEPLOYED_ADDRESS
17
&& new caver.klay.Contract(DEPLOYED_ABI, DEPLOYED_ADDRESS)
18
this.state = {
19
count: '',
20
lastParticipant: '',
21
isSetting: false,
22
}
23
}
24
25
intervalId = null
26
27
getCount = async () => {
28
// ** 2. Call contract method (CALL) **
29
// ex:) this.countContract.methods.methodName(arguments).call()
30
// You can call contract method (CALL) like above.
31
// For example, your contract has a method called `count`.
32
// You can call it like below:
33
// ex:) this.countContract.methods.count().call()
34
// It returns promise, so you can access it by .then() or, use async-await.
35
const count = await this.countContract.methods.count().call()
36
const lastParticipant = await this.countContract.methods.lastParticipant().call()
37
this.setState({
38
count,
39
lastParticipant,
40
})
41
}
42
43
setPlus = () => {
44
const walletInstance = caver.klay.accounts.wallet && caver.klay.accounts.wallet[0]
45
46
// Need to integrate wallet for calling contract method.
47
if (!walletInstance) return
48
49
this.setState({ settingDirection: 'plus' })
50
51
// 3. ** Call contract method (SEND) **
52
// ex:) this.countContract.methods.methodName(arguments).send(txObject)
53
// You can call contract method (SEND) like above.
54
// For example, your contract has a method called `plus`.
55
// You can call it like below:
56
// ex:) this.countContract.methods.plus().send({
57
// from: '0x952A8dD075fdc0876d48fC26a389b53331C34585', // PUT YOUR ADDRESS
58
// gas: '200000',
59
// })
60
this.countContract.methods.plus().send({
61
from: walletInstance.address,
62
gas: '200000',
63
})
64
.once('transactionHash', (txHash) => {
65
console.log(`
66
Sending a transaction... (Call contract's function 'plus')
67
txHash: ${txHash}
68
`
69
)
70
})
71
.once('receipt', (receipt) => {
72
console.log(`
73
Received receipt! It means your transaction(calling plus function)
74
is in klaytn block(#${receipt.blockNumber})
75
`, receipt)
76
this.setState({
77
settingDirection: null,
78
txHash: receipt.transactionHash,
79
})
80
})
81
.once('error', (error) => {
82
alert(error.message)
83
this.setState({ settingDirection: null })
84
})
85
}
86
87
setMinus = () => {
88
const walletInstance = caver.klay.accounts.wallet && caver.klay.accounts.wallet[0]
89
90
// Need to integrate wallet for calling contract method.
91
if (!walletInstance) return
92
93
this.setState({ settingDirection: 'minus' })
94
95
// 3. ** Call contract method (SEND) **
96
// ex:) this.countContract.methods.methodName(arguments).send(txObject)
97
// You can call contract method (SEND) like above.
98
// For example, your contract has a method called `minus`.
99
// You can call it like below:
100
// ex:) this.countContract.methods.minus().send({
101
// from: '0x952A8dD075fdc0876d48fC26a389b53331C34585', // PUT YOUR ADDRESS
102
// gas: '200000',
103
// })
104
105
// It returns event emitter, so after sending, you can listen on event.
106
// Use .on('transactionHash') event,
107
// : if you want to handle logic after sending transaction.
108
// Use .once('receipt') event,
109
// : if you want to handle logic after your transaction is put into block.
110
// ex:) .once('receipt', (data) => {
111
// console.log(data)
112
// })
113
this.countContract.methods.minus().send({
114
from: walletInstance.address,
115
gas: '200000',
116
})
117
.once('transactionHash', (txHash) => {
118
console.log(`
119
Sending a transaction... (Call contract's function 'minus')
120
txHash: ${txHash}
121
`
122
)
123
})
124
.once('receipt', (receipt) => {
125
console.log(`
126
Received receipt which means your transaction(calling minus function)
127
is in klaytn block(#${receipt.blockNumber})
128
`, receipt)
129
this.setState({
130
settingDirection: null,
131
txHash: receipt.transactionHash,
132
})
133
})
134
.once('error', (error) => {
135
alert(error.message)
136
this.setState({ settingDirection: null })
137
})
138
}
139
140
componentDidMount() {
141
this.intervalId = setInterval(this.getCount, 1000)
142
}
143
144
componentWillUnmount() {
145
clearInterval(this.intervalId)
146
}
147
148
render() {
149
const { lastParticipant, count, settingDirection, txHash } = this.state
150
return (
151
<div className="Count">
152
{Number(lastParticipant) !== 0 && (
153
<div className="Count__lastParticipant">
154
last participant: {lastParticipant}
155
</div>
156
)}
157
<div className="Count__count">COUNT: {count}</div>
158
<button
159
onClick={this.setPlus}
160
className={cx('Count__button', {
161
'Count__button--setting': settingDirection === 'plus',
162
})}
163
>
164
+
165
</button>
166
<button
167
onClick={this.setMinus}
168
className={cx('Count__button', {
169
'Count__button--setting': settingDirection === 'minus',
170
})}
171
>
172
-
173
</button>
174
{txHash && (
175
<div className="Count__lastTransaction">
176
<p className="Count__lastTransactionMessage">
177
You can check your last transaction in klaytnscope:
178
</p>
179
<a
180
target="_blank"
181
href={`https://scope.klaytn.com/transaction/${txHash}`}
182
className="Count__lastTransactionLink"
183
>
184
{txHash}
185
</a>
186
</div>
187
)}
188
</div>
189
)
190
}
191
}
192
193
export default Count
Copied!
Last modified 6mo ago