The German poet Goethe once wrote: “Enjoy what pain has left behind for you! With hardship past, the same hardships are sweet.” No matter the external circumstance, if we persevere in what we believe to be right, we will look back and smile at what has passed.

Klaytn has also been focusing on strengthening our fundamentals in pursuit of the values we espouse. So we proudly announce the release of v1.8.4, which includes extensive improvements in our ServiceChain, an integral element in Klaytn’s Metaverse Package. FYI, the release only pertains to the ServiceChain, so CNs or PNs don’t need to update.


# Improvements
The name of the VT_RECOVERY option changed to SC_VT_RECOVERY. The option VT_RECOVERY is still usable with the update and is the same option. The option SC_VTRECOVERY_INTERVAL has also been added, which configures the cycle for Value Transfer Recovery. The default value is 5 seconds. (#1271)

Two APIs for the Subbridge namespace subbridge_getChildBridgeContractBalance and subbridge_getParentBridgeContractBalance have been added that return the balance for the respective contracts. (#1254)

We added a logic to check the address type, which is a parameter for the setBridge() function used for setting up a bridge in a token contract. If the address is not of a contract, it cannot be set up as a bridge. (#1299)

The option SC_KAS_ANCHOR_REQUEST_TIMEOUT has been added. Using this option, you can set the timeout for KAS API requests. (#1284)

You can now change the default gas price for ServiceChain. (#1287)

In previous versions, the gas limit for SCN node operators was fixed to 10000000. Transactions that used more gas such as contract deployment would not be processed with the fixed gas limit. So with the update, you can now set your own gas limit for each chain. (SC_PARENT_OPERATOR_GASLIMIT and SC_CHILD_OPERATOR_GASLIMIT). You can also GET and SET the current gas limit for your node using the four new APIs. (subbridge_setChildBridgeOperatorGasLimit subbridge_setParentBridgeOperatorGasLimit, subbridge_getChildBridgeOperatorGasLimit, subbridge_getParentBridgeOperatorGasLimit). (#1353 #1344)
We added the chaindatafetcher option for SEN nodes. This node functions in the same way as the chaindatafetcher option in existing EN nodes. (#1347)

# Fixes
There was a bug in which the URI value when sending ERC-721 tokens were set as an empty string. To solve this, some changes regarding a new event structure as well as a new logic to handle the event have been added. (#1321 #1378)


# Miscellaneous
You can now find out the location of the imported account file when importing accounts using the node command account import. (#1285 #1262)

For more details, please check out this link. Subscribe to our official Medium to not miss anything! Thank you!

We had the pleasure of hosting Colin Kim, Klaytn Core Development Team Leader, for an informative AMA session where he answered the community’s questions about Klaytn implementing support for Ethereum equivalence. Read on for our recap!

Note: Some of the text have been edited for grammar and clarity.

Date & Time: Mar 25th at 4pm SGT / 5pm KST
Platform: Telegram
Speaker: 
Colin Kim from Klaytn Foundation
Host: Sofy, Community Manager at Klaytn Foundation


Introduction

Sofy: A very good afternoon to everyone and Happy Friday! Welcome once again to another AMA session with us. I am Sofy, community manager for Klaytn and I will be your host for today’s session. Thank you so much everyone for taking time to be here.

Today’s session will be divided into 2 parts. We will begin with an introduction from Colin, followed by him answering commonly asked questions.Lastly, we will be opening up the floor to the community. Feel free to drop your questions for the last segment of the AMA.

Please kindly introduce yourself to the community and share with us about your place in the Klaytn network.

Colin: Hi, Sofy. Thanks for having me. I joined Klaytn in 2018. I worked in various layers like platform development, SDK development, API Service development a.k.a Klaytn API Service, and application development such as KrafterSpace. Now I’m leading Klaytn Core Development Team.

Questions

Q1
Sofy: How will Klaytn v1.8.0 supporting Ethereum APIs support the Klaytn 2.0 initiative that the team set out?

Colin: Klaytn 2.0 is basically for metaverse builders. For them to build applications easily on Klaytn, it would be good to utilize massive resources previously built on Ethereum. Since Klaytn is forked from Ethereum, it is possible to support Ethereum APIs. Then many open-source resources can be used on Klaytn. Since Klaytn has many good features for better UI/UX, applications can provide better UI/UX with the abundant resources on Ethereum.

Q2
Sofy: So what are the significant changes? Please summarize that. It’s far too lengthy!

Colin: Okay. Let me summarize:

  1. We will support London EVM.
  2. We will support eth namespace APIs.
  3. We will support new Ethereum transaction types.

Q3
Sofy: When can we use that?

Colin: For Baobab, it will be activated on March 24th. The block number is 86,513,895. The time will be around 12 PM.

For Cypress, it will be activated on March 31th. The block number is 86,816,005. The time will be around 12 PM.

After then, you can use truffle, hardhat, web3.js, ethers.js, etc. as it is.

If you want to experience this before the hardfork, you can use our test network. The URL can be found in this article.

Q4
Sofy: What about the gas price increase?

Colin: Good question. We are also thinking that this can make the community confused. We are trying hard to do our best for the community. Let me make this clear.

For Cypress

  1. The v1.8.0 hardfork will be in effect on March 31th, the block number would be 86,816,005.
  2. The gas price will increase on April 3rd, the block number would be 87,091,200.

Q5
Sofy: Can you share the links to all the related articles?

Colin: Sure. Here are all links related to this topic.

Q6
Sofy: What are some benefits of the new feature that users may look forward to?

Colin: The main benefit is that you can now use Ethereum tools such as Truffle, HardHat, Web3.js, Ethers.js, etc. You can also use the latest Solidity versions.

All features supported by Ethereum’s London EVM are also now supported on Klaytn. Ethereum’s London hard fork is the latest technological update to the Ethereum ecosystem with changes to the EVM.

In other words, every EVM feature will now be available on Klaytn core protocol as well, however it should be noted that there have been some changes made with regard to precompiled contract addresses in the process of supporting London EVM, for which Klaytn developers should take into account. You can read more about this here.

Q7
Sofy: Can you share with us the new transaction types on Klaytn that support Ethereum transaction types?

Colin: Actually, Klaytn has employed transaction types for extensibility. We present the account model and transaction model in ETH Devcon 5, 2019. Ethereum also found that they need to add more fields to the transaction, so they introduced transaction types. Mainly two reasons: access list and dynamic gas price a.k.a. EIP-1559. To support these new Ethereum transaction types, Klaytn also needs to add more transaction types. This is why new transaction types were introduced in Klaytn.

Ethereum’s transaction types AccessList and DynamicFee will be supported. In 2021, Ethereum introduced what is called the transaction types. But varied transaction types are something that Klaytn has had since the launch of its mainnet in 2019, so there was a compatibility issue with Ethereum’s new transaction types. Klaytn v1.8.0 includes fixes to resolve the inconsistencies between transaction types of Klaytn and Ethereum. You can read more about this here.

Q8
Sofy: What are some challenges that users may potentially face with the new features and how may they address or where should they seek help from?

Colin: This is a quite big change towards Ethereum equivalence, but there is not much difference especially for users using Klaytn. Check the above articles. Especially for developers, they need to take care of these three things:

  1. Potential out of gas error: gas cost mechanism is changed slightly. This can cause out of gas error if the fallback function does storage related operation.
  2. Precompiled contract address change: The problematic case is that a newly deployed contract does a delegate call to the old contract. And the old contract needs to perform the execution of the precompiled contracts that are moved to the other address range.
  3. Support new transaction types: Since Ethereum transaction types are newly added to Klaytn, SDKs support the transaction types. Services like KlaytnScope should also process the new transaction types.

Q9
Sofy: How can users streamline when generating and retrieving Ethereum-formatted transactions?

Colin: If they are Ethereum users, and they have their tools to generate Ethereum-formatted transactions, then they just send the transactions to Klaytn via eth_sendRawTransaction().

Q10
Sofy: Klaytn’s existing precompiled contract addresses have been mapped to new addresses in order not to overlap with the existing Ethereum assignments. What is the effect of the precompiled contract address changes?

Colin: We can separate this into two:

  1. Contracts deployed prior to the hard fork
  2. Contracts deployed after the hard fork.

The first ones use the old precompiled contract addresses, so they don’t need to be redeployed. It can be a problem if the first one is called via a delegate call from the second one because this call uses the context of the second one.

The second ones use the new precompiled contract addresses. In this case, it can be a problem if the second one is trying to call old precompiled addresses. In this case the source code needs to be updated to use new precompiled addresses. You can read more about this here.

Q11
Sofy: What are some changes that users have to look out for if they are using `eth` namespace APIs with Geth client and Ethereum development tools?

Colin: Klaytn supports eth namespace APIs, so developers using Ethereum based SDKs or tools now can easily migrate their existing projects to Klaytn. (e.g. You can replace the endpoint URL in the Ethereum tools to point to a Klaytn node.) You can read more about this here.

Q12
Sofy: When should users utilise the call method vs the delegate call method when deploying new contracts?

Colin: Please see the details in this article. To summarize this, please do not use delegatecall to the old contract from the new contract. This is the problematic case.

Q13
Sofy: Are there any tips you may give to our users?

Colin: I think one of the things that was not propagated well was that we also employed gas cost changes (EIP-1844), so SLOAD, SSTORE gas cost is changed. The problematic case here happens when the fallback function uses storage. Please take a look at our release notes to learn more.

Q14
Sofy: What is the progress on Klaytn’s product development? Is Klaytn on track with its roadmap?

Colin: I think yes. We are also planning to show the detailed tech roadmap that we want to achieve. Please stay tuned on Medium.

Q15
Sofy: What are some similarities and difference Klaytn has in comparison to Ethereum?

Colin: It’s a very good question. For similarity, we try to be similar as much as possible so that any applications running on Ethereum can be directly run on Klaytn.

For the differences, we can think of this as a UI/UX enhancement. We achieve 4000 TPS and 1-second block finality. This means you can make an application similar to a mobile application. The transaction is finalized in 2 or 3 seconds including transaction propagation time.

Also we’ve developed the account model and transaction model. With this, we can easily build a fee-delegation mechanism. Also we can improve security by making it possible to map multiple private keys to the account.

Q16
Sofy: Overall, what do you think is the greatest advantage for our community — dev, users etc., with the latest towards Ethereum equivalence?

Colin: As I discussed, we can embrace all Ethereum ecosystem tools and services very easily. We know that not many applications or services are onboard on Klaytn. By ourselves or with the Klaytn community, we can easily migrate or port applications first. Then we can build more on this.

By doing this, I think we can give better user experience even though the source code is exactly the same.

Many thanks to Colin Kim for taking time out of his busy schedule for our AMA! To find out more about Klaytn and join our growing global community, please follow these links below:

Website | Twitter | Discord | Telegram

Transforming Klaytn into a metaverse blockchain for all is a massive undertaking that’s a lot more than just TPS upgrades.

On the technology front, you need speed, scale, and standards for interoperability. Then you’ve got to build an environment that’s conducive for metaverse builders to unleash their creativity. And of course, align with the decentralized nature of our future economy.

That’s why our Klaytn 2.0 development roadmap encompasses far more than just speeds and feeds—and today we’re excited to share our plans with the world. Check it out below!

We’re making great progress towards these goals—in fact, support for Ethereum equivalence is already well under way and almost ready to ship. Watch our blog and community channels to be the first to know every time we hit another milestone!

Join our communities on:

Twitter | Discord | Telegram | Instagram | Facebook | Reddit

One of the special features of Klaytn is that it has decoupled keys from addresses, so that you can update your keys in the account. In this post, we will be explaining how to update account keys to AccountKeyRoleBased using caver-js and caver-java. For more details on the different types of AccountKey, please refer to Klaytn Docs.

This tutorial assumes that you have the necessary environment setup. If you haven’t yet set up the environment, please refer to caver-js — Prerequisites or caver-java — Prerequisites.

In each section, we use little snippets of the whole code; you can find the in the links below:

1. Creating a keyring

First, create a Klaytn account to use for this tutorial. This account will be used for sending a transaction to make the update to the key stored on the network, so it must possess sufficient KLAY.

Caver uses a structure called Keyring to store private key(s) used in Klaytn accounts.

First, you can create a Keyring instance that stores Klaytn account information with caver-js like this:

// caver-js
const senderKeyring = caver.wallet.keyring.create(senderAddress, senderPrivateKey)

If you have a keystore file instead of a private key string, you can create a keyring using caver.wallet.keyring.decrypt.

And you can create a Keyring instance using caver-java as well:

// caver-java
SingleKeyring senderKeyring = caver.wallet.keyring.create(senderAddress, senderPrivateKey);

For caver-java, you can also create a keyring with caver.wallet.keyring.decrypt, if you have a keystore file.

2. Adding a keyring to caver in-memory wallet

In this exercise, we will be using an in-memory wallet. If you add a keyring to caver’s in-memory wallet, the key stored in that keyring will automatically be used to sign transactions even if you don’t designate a specific key.

This is how you add a keyring to in-memory wallet using caver-js:

// caver-js
caver.wallet.add(senderKeyring)

It is the same for caver-java:

// caver-java
caver.wallet.add(senderKeyring);

3. Creating a new private key

In order to update Klaytn account’s AccountKey to AccountKeyRoleBased, you need private keys defined around roles for your Klaytn account. Here we will use private keys that are randomly generated using the generateRoleBasedKeys function. But if there is a specific private key that you want to use, you can use that one. For more details on each role, please refer to Klaytn Docs.

Below is how you can create multiple private key strings defined for each role to be used with your Klaytn account with caver-js. The array that is sent as a parameter defines the number of keys to be used for each role. The below example will generate two private keys as RoleTransactionKeys, one as RoleAccountUpdateKey, and three as RoleFeePayerKeys.

// caver-js
const newRoleBasedKeys = caver.wallet.keyring.generateRoleBasedKeys([2, 1, 3])

caver-java can also create multiple private key strings using generateRoleBasedKeys:

List<String[]> newRoleBasedKeys = caver.wallet.keyring.generateRolBasedKeys(new int[]{2, 1, 3});

4. Creating a new keyring

Now that we have created new private keys, let’s create a Keyring instance to store the new private keys defined per their respective roles. You can use the Keyring instance once the AccountKey is successfully changed.

You can create a Keyring instance where the private keys for each role are defined using caver-js as shown below:

// caver-js
const newKeyring = caver.wallet.keyring.create(senderKeyring.address, newRoleBasedKeys)

From now on, the newKeyring that stores the new private keys will be used to sign the transaction using newRoleBasedKeys.

Using caver-java, creating the Keyring instance looks like this:

RoleBasedKeyring newKeyring = caver.wallet.keyring.create(senderKeyring.getAddress(), newRoleBasedKeys);

5. Creating an Account instance

The Account class provided by Caver contains the information required to update accounts. To update your key to AccountKeyRoleBased, you need the Klaytn account address that you want to update and the role based keys, where the keys are defined for each role. AccountKeyRoleBased stores the keys in the form of public keys. When multiple keys are used for a role, you can also define the threshold as well as the weight of each public key.

You can create an Account instance by calling the toAccount function of the keyring that stores the newly created private keys. Here is what it looks like when you are using caver-js:

// caver-js
const account = newKeyring.toAccount([{ threshold: 2, weights: [1, 1] }, {}, { threshold: 3, weights: [2, 1, 1] }])

The newKeyring to be used after the update has two RoleTransaction keys and three RoleFeePayer keys, so the threshold and weights can be defined for the keys for each role. When calling the toAccount function, pass the object that defines the WeightedMultisigOptions for each role as parameter. The first element of the array is the object that defines the threshold and weights for the two keys to be used for the RoleTransactionKey. And since only one key is used as RoleAccountUpdateKey, pass an empty object for the second element. The last array element is an object that defines the threshold and weights for the three keys used as RoleFeePayerKey.

You can also create an Account instance by calling the toAccount function of the keyring that stores the new private keys defined according to their roles with caver-java. The threshold of AccountKeyRoleBased and the key’s weights are defined using the WeightedMultiSigOptions class.

// caver-java
BigInteger[][] optionWeight = {
{BigInteger.ONE, BigInteger.ONE},
{},
{BigInteger.valueOf(2), BigInteger.ONE, BigInteger.ONE},
};WeightedMultiSigOptions[] options = {
new WeightedMultiSigOptions(BigInteger.valueOf(2), Arrays.asList(optionWeight[0])),
new WeightedMultiSigOptions(),
new WeightedMultiSigOptions(BigInteger.valueOf(3), Arrays.asList(optionWeight[2])),
};Account account = newKeyring.toAccount(Arrays.asList(options));

For caver-java also, you can create an Account instance by passing the array with WeightedMultiSigOptions defined for each role to the toAccount function.

The new Account instance will now store the address of the Klaytn account to be updated and the keys for each role in the form of public keys. The threshold and weights for the keys will also be stored.

6. Creating a transaction

Once you have created an Account instance, you can use it to create an account update transaction.

Here is how you create a transaction using caver-js:

// caver-js
const accountUpdate = caver.transaction.accountUpdate.create({
from: senderKeyring.address,
account: account,
gas: 150000,})

And here is how you do it using caver-java:

// caver-java
AccountUpdate accountUpdate = caver.transaction.accountUpdate.create(
TxPropertyBuilder.accountUpdate()
.setFrom(senderKeyring.getAddress())
.setAccount(account)
.setGas(BigInteger.valueOf(150000))
);

7. Signing the transaction

Once you have created an account update transaction, you need to sign it using the keyring that was added to the in-memory wallet. Caver’s in-memory wallet, caver.wallet, has a sign function.

Here is how you sign a transaction using caver-js:

// caver-js
await caver.wallet.sign(senderKeyring.address, accountUpdate)

And for caver-java:

// caver-java
caver.wallet.sign(senderKeyring.getAddress(), accountUpdate);

If caver.wallet.sign has been successfully executed, you should see that the signature has been assigned to the signatures field of accountUpdate.

Now all that is left for us to do is to send the transaction to the network.

8. Sending the transaction

Now that we have created and signed the transaction, let’s try sending it to the network. Once this transaction goes through on the network, the old key for your Klaytn account will no longer be usable. Since your old keyring will also no longer be usable, you have to use the new one that stores the new private key.

You can send the signed transaction to the network using caver.rpc.klay.sendRawTransaction.

Below is a sample that shows how to send a transaction with caver-js, using an EventEmitter:

// caver-js
caver.rpc.klay.sendRawTransaction(accountUpdate)
.on(‘transactionHash’, hash => {
console.log(hash)
})
.on(‘receipt’, receipt => {
console.log(receipt)
})

When you are sending a transaction using caver-js, you can get a receipt of the transaction result using Promise with the code below:

// caver-js
const receipt = await caver.rpc.klay.sendRawTransaction(accountUpdate)

You can also send a transaction using caver-java like this:

// caver-java
Bytes32 sendResult = caver.rpc.klay.sendRawTransaction(accountUpdate).send();
String txHash = sendResult.getResult();

When the above code is executed, you will get a transaction hash. The result of the transaction can be obtained with the code as shown below:

// caver-java
public String objectToString(Object value) throws JsonProcessingException {
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
return ow.writeValueAsString(value);
}TransactionReceiptProcessor receiptProcessor = new PollingTransactionReceiptProcessor(caver, 1000, 15);TransactionReceipt.TransactionReceiptData receiptData = receiptProcessor.waitForTransactionReceipt(txHash);System.out.println(objectToString(receiptData));

On the receipt, if the status has returned true, it means that the transaction has been successfully processed. Transaction type is TxTypeAccountUpdate and the updated AccountKeyRoleBased is returned in the key field in encoded form.

{
blockHash: ‘0xe1a010ffef58727d47ed34d071a523c21a661f0c50a0074f8ec2bb8389bf7775’,
blockNumber: ‘0x33ca958’,
contractAddress: null,
from: ‘0x344de28e3e3089c3d7b9076f30dbbafcb329176f’,
gas: ‘0x249f0’,
gasPrice: ‘0x5d21dba00’,
gasUsed: ‘0x226c8’,
key: ‘0x05f8e8b84e04f84b01f848e301a102e2b8b818b0668e26651ed7fa199eccdc9b77e40775db80db6391244175ad6e5ee301a1033c79532bdd5b2c7265754df31f3665ed13a8556c24525a69cf0eedbbf5ff7ef1a302a102670a11eba2c17d92c01dfe272263db6c6d3eed5b1119401f9a62b7023cde6a6ab87204f86f01f86ce301a102104b2f85f43abc7b295cfd3ce91f6cb2c68e47c76d4bcc765b28c6ef2e0a86e8e301a1021190e282cf2a5066013784e08e29fb7821b3044a5f500059575bc003e926528be301a10289a57b9501c831e6b41537d3adbcaeca07ad5685d2963601b282def87ee619f2’,
logs: [],
logsBloom: ‘0x00000…’,
nonce: ‘0x0’,
senderTxHash: ‘0x826e5fbe3f33fd19c9a07b0f315eda2066ce2150b17656825e926374d32ef39a’,
signatures: [
{ V: ‘0x7f5’, R: ‘0xa952b…’, S: ‘0x936ad…’ }
],
status: ‘0x1’,
transactionHash: ‘0x826e5fbe3f33fd19c9a07b0f315eda2066ce2150b17656825e926374d32ef39a’,
transactionIndex: ‘0x0’,
type: ‘TxTypeAccountUpdate’,
typeInt: 32
}

9. Confirming the update of the AccountKey

If the transaction was successful, the account key stored on the network will have been updated. You can check the result using caver.rpc.klay.getAccountKey.

Confirm your new accountKey with caver-js like this:

// caver-js
const accountKey = await caver.rpc.klay.getAccountKey(senderKeyring.address)console.log(accountKey)

Or with caver-java:

// caver-java
AccountKey accountKey = caver.rpc.klay.getAccountKey(senderKeyring.getAddress()).send();System.out.println(objectToString(accountKey));

The code above will return the account keys stored on the Klaytn account. Since the key has been updated to AccountKeyRoleBased, the keyType, represented by the key type ID, is 5. For more details on Account Key Type IDs, please refer to Klaytn Docs.

{
keyType: 5,
key: [
{
keyType: 4,
key: {
threshold: 1,
keys: [
{
weight: 1,
key: { x: ‘0xe2b8b…’, y: ‘0xc8004…’ }
},
{
weight: 1,
key: { x: ‘0x3c795…’, y: ‘0x18799…’ }
}
]
}
},
{
keyType: 2,
key: { x: ‘0x670a1…’, y: ‘0x1fe76…’ }
},
{
keyType: 4,
key: {
threshold: 1,
keys: [
{
weight: 1,
key: { x: ‘0x104b2…’, y: ‘0xa388d…’ }
},
{
weight: 1,
key: { x: ‘0x1190e…’, y: ‘0x3c23b…’ }
},
{
weight: 1,
key: { x: ‘0x89a57…’, y: ‘0xfedd0…’ }
}
]
}
}
]
}

10. Updating in-memory wallet’s keyring

If your account key has been updated with the successfully processed transaction, you have to start using the updated key for signing transactions.

For now, the key that is used in the keyring, and stored in the in-memory wallet, is the old one before the update. Sending a transaction in this state will cause an error, as shown below:

Error: Returned error: invalid transaction v, r, s values of the sender

That is why you also have to update the keyring’s keys stored in the in-memory wallet after updating your Klaytn account key.

The private key used in keyring, and stored in the in-memory wallet can be updated using caver.wallet.updateKeyring. The newKeyring that contains the updated Klaytn account address and new private key will be passed as parameter.

Below is how you can update in-memory wallet’s keyring using caver-js:

// caver-js
caver.wallet.updateKeyring(newKeyring)

And here is how you can do this using caver-java:

// caver-java
caver.wallet.updateKeyring(newKeyring);

11. Sending a transaction with the updated account

If your Klaytn account key has been updated, let’s try sending a transaction using the updated account.

Since the account key stored on the network has been updated, you have to sign the transaction using this new key. In section [10. Updating in-memory wallet’s keyring], we also updated the key in the keyring in the in-memory wallet, so a transaction will be sent with the sign given with this updated key.

Here we will just send a simple value transfer transaction to confirm the update.

Below is a sample that shows how to create, sign and send a ValueTransfer transaction using caver-js:

// caver-js
const vt = caver.transaction.valueTransfer.create({
from: senderKeyring.address,
to: recipientAddress,
value: 1,
gas: 100000,
})

await caver.wallet.sign(senderKeyring.address, vt)

const vtReceipt = await caver.rpc.klay.sendRawTransaction(vt)

Create a transaction using caver.transaction.valueTransfer, and sign it using the caver.wallet.sign function as already demonstrated. Since the keyring in caver.wallet has been updated in [10. Updating in-memory wallet’s keyring], it is the new private keys that will sign the transaction. In this example, the sender signs a ValueTransfer transaction, so the private keys defined in the Role Transaction key will be used for signing. After that, the transaction will be sent to the network using caver.rpc.klay.sendRawTransaction. In this tutorial, the transaction result is returned using Promise.

Below is a sample of the same process, but with caver-java:

// caver-javaValueTransfer vt = caver.transaction.valueTransfer.create(
TxPropertyBuilder.valueTransfer()
.setFrom(senderKeyring.getAddress())
.setTo(recipientAddress)
.setValue(BigInteger.valueOf(1))
.setGas(BigInteger.valueOf(100000))
);caver.wallet.sign(senderKeyring.getAddress(), vt);Bytes32 vtResult = caver.rpc.klay.sendRawTransaction(vt).send();TransactionReceipt.TransactionReceiptData vtReceiptData = receiptProcessor.waitForTransactionReceipt(vtResult.getResult());

Create a ValueTransfer transaction and sign it using the caver.wallet.sign function. The transaction will be sent to the network with caver.rpc.klay.sendRawTransaction. You can confirm the transaction status using the returned transaction hash.

In this post we showed you how to update an account key to AccountKeyRoleBased on Klaytn. This was the last of the series on updating Klaytn account keys. We will come back with a new topic for our next post.

If you encounter any problems following this tutorial or have any questions, feel free to visit Klaytn Forum for help.

Thanks for reading and stay tuned!

The complete code used in this tutorial:

One of the special features of Klaytn is that it has decoupled keys from addresses, so that it’s possible to update your keys in the account. In this post, we will be explaining how to update account keys to AccountKeyWeightedMultiSig using caver-js and caver-java. For more details on the different types of AccountKey, please refer to Klaytn Docs.

This tutorial assumes that you have the necessary environment setup. If you haven’t yet set up the environment, please refer to caver-js — Prerequisites or caver-java — Prerequisites.

In each section, we will go through little snippets of the whole code; you can check the complete code in the links below:

1. Creating a keyring

First, create an Klaytn account to use for this tutorial. This account will be used for sending a transaction to make the update to the key stored on the network, so it must possess sufficient KLAY.

Caver uses a structure called Keyring to store private key(s) used in Klaytn addresses and accounts.

First, you can create a Keyring instance that stores Klaytn account information with caver-js like this:

// caver-js 
const senderKeyring = caver.wallet.keyring.create(senderAddress, senderPrivateKey)

If you have a keystore file instead of a private key string, you can create a keyring using caver.wallet.keyring.decrypt.

And you can create Keyring instance using caver-java as well:

// caver-java
SingleKeyring senderKeyring = caver.wallet.keyring.create(senderAddress, senderPrivateKey);

For caver-java, you can also create a keyring with caver.wallet.keyring.decrypt, if you have a keystore file.

2. Adding a keyring to caver in-memory wallet

In this exercise, we will be using an in-memory wallet. If you add a keyring to caver’s in-memory wallet, the key stored in that keyring will automatically be used to sign transactions even if you don’t designate a specific key.

This is how you add a keyring to in-memory wallet using caver-js:

// caver-js
caver.wallet.add(senderKeyring)

It is the same for caver-java:

// caver-java
caver.wallet.add(senderKeyring);

3. Creating new private keys

In order to update Klaytn account’s AccountKey to AccountKeyWeightedMultiSig, you need private keys for your Klaytn account. Here we will use randomly generated private keys via the generateMultipleKeys function. But if there are specific private keys that you want to use, that is fine.

Below is how you create multiple private key strings for your Klaytn account using caver-js:

// caver-js
const newKeys = caver.wallet.keyring.generateMultipleKeys(3)

You can also generate multiple private key strings using generateMultipleKeys with caver-java:

// caver-java
String[] newKeys = caver.wallet.keyring.generateMultipleKeys(3);

4. Creating a new keyring

Now that we have created new private keys, we will go on to create a Keyring instance to store them. You can start using the Keyring instance with the new keys once the AccountKey in the Klaytn account has been successfully updated.

You can create a Keyring instance that stores the new private keys using caver-js as shown below:

// caver-js
const newKeyring = caver.wallet.keyring.create(senderKeyring.address, newKeys)

The newKeyring that stores the new private keys will sign the transaction using newKeys.

Using caver-java, creating a Keyring instance for storing the new private keys looks like this:

// caver-java
MultipleKeyring newKeyring = caver.wallet.keyring.create(senderKeyring.getAddress(), newKeys);

5. Creating an Account instance

The Account class provided by caver contains the information required to update accounts. When making the update to AccountKeyWeightedMultiSig, you need the Klaytn account address that you want to update, as well as a new weighted multisig key. AccountKeyWeightedMultiSig stores the keys in the form of public keys and defines the threshold, as well as the weight of each public key.

You can create an Account instance by calling the toAccount function of the keyring that stores the newly created private keys. Here is what it looks like when you are using caver-js:

The threshold and the key’s weights of AccountKeyWeightedMultiSig are defined as below:

// caver-js
const account = newKeyring.toAccount({ threshold: 3, weights: [2, 1, 1] })

You can also create an Account instance by calling the keyring’s toAccount function with caver-java. The threshold and the key’s weights for AccountKeyWeightedMultiSig are defined using the WeightedMultiSigOptions class.

BigInteger[] weights = {BigInteger.valueOf(2), BigInteger.ONE, BigInteger.ONE};
WeightedMultiSigOptions options = new WeightedMultiSigOptions(BigInteger.valueOf(3), Arrays.asList(weights));
Account account = newKeyring.toAccount(options);

The Account instance will now store the Klaytn account address to be updated, as well as the keys (in the form of public keys) where the threshold and weights of each key are defined.

6. Creating a transaction

Once you have created an Account instance, you can use it to create an Account Update transaction.

Here is how you create a transaction using caver-js:

// caver-js
const accountUpdate = caver.transaction.accountUpdate.create({
from: senderKeyring.address,
account: account,
gas: 100000,})

And here is how you do it using caver-java:

// caver-java
AccountUpdate accountUpdate = caver.transaction.accountUpdate.create(
TxPropertyBuilder.accountUpdate()
.setFrom(senderKeyring.getAddress())
.setAccount(account)
.setGas(BigInteger.valueOf(100000))
);

7. Signing the transaction

Once you have created an account update transaction, you need to sign it using the keyring that was added to the in-memory wallet. Caver’s in-memory wallet, caver.wallet, has a sign function, just for this purpose.

Here is how you sign a transaction using caver-js:

// caver-js
await caver.wallet.sign(senderKeyring.address, accountUpdate)

And for caver-java:

// caver-java
caver.wallet.sign(senderKeyring.getAddress(), accountUpdate);

If caver.wallet.sign has been successfully executed, you should see that the signature has been assigned to the signatures field of accountUpdate.

Now we are ready to send the transaction to the network.

8. Sending the transaction

Now that we have created and signed the transaction, let’s try sending it to the network. Once this transaction goes through on the network, the old key for your Klaytn account will no longer be usable. Since your old keyring will also no longer be usable, you have to use the new one that stores the new private key.

You can send the signed transaction to the network using caver.rpc.klay.sendRawTransaction.

Below is a sample that shows how to send a transaction with caver-js, using an EventEmitter:

// caver-js
caver.rpc.klay.sendRawTransaction(accountUpdate)
.on('transactionHash', hash => {
console.log(hash)
})
.on('receipt', receipt => {
console.log(receipt)
})

When you are sending a transaction using caver-js, you can get a receipt of the transaction using Promise with the code below:

// caver-js
const receipt = await caver.rpc.klay.sendRawTransaction(accountUpdate)

You can also send a transaction using caver-java like this:

// caver-java
Bytes32 sendResult = caver.rpc.klay.sendRawTransaction(accountUpdate).send();
String txHash = sendResult.getResult();

When the above code is executed, you will get a transaction hash. The result of the transaction can be obtained with the code as shown below:

// caver-java
public String objectToString(Object value) throws JsonProcessingException {
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
return ow.writeValueAsString(value);
}
TransactionReceiptProcessor receiptProcessor = new PollingTransactionReceiptProcessor(caver, 1000, 15);TransactionReceipt.TransactionReceiptData receiptData = receiptProcessor.waitForTransactionReceipt(txHash);System.out.println(objectToString(receiptData));

If the status in the receipt reads true, it means that the transaction has been successfully processed. Transaction type is TxTypeAccountUpdate and the updated AccountKeyWeightedMultiSigcountKeyPublic is returned in the key field in encoded form.

{
blockHash: ‘0xdf8b53b76fc1bc2dde099199026c48b7cae56724b752b1476ae2ab867598a1d5’,
blockNumber: ‘0x33c98c3’,
contractAddress: null,
from: ‘0xf3ecd45eeb2bb22d33d2d83dc2350d18691b5f80’,
gas: ‘0x186a0’,
gasPrice: ‘0x5d21dba00’,
gasUsed: ‘0x13c68’,
key: ‘0x04f86f03f86ce302a103d2df49e8d3b4da20b39a81f422c82c50e29a32b0a001b90d6de72a9cc76535f5e301a103b00854f13c6cb6cabab766c2c4b74bc568568a5600599be6ace18a789fc3b5dce301a1021968807b49413b84eea56d24bfe212166ccafdba2c1ae4d7c1e04a179f28dc5f’,
logs: [],
logsBloom: ‘0x00000…’,
nonce: ‘0x0’,
senderTxHash: ‘0x736f2a35cd4618f00ed4e6a76637b526b2b72e8fdc1a7dab35c3341a2c2027af’,
signatures: [
{ V: ‘0x7f5’, R: ‘0x89e33…’, S: ‘0x1c9a0…’ }
],
status: ‘0x1’,
transactionHash: ‘0x736f2a35cd4618f00ed4e6a76637b526b2b72e8fdc1a7dab35c3341a2c2027af’,
transactionIndex: ‘0x0’,
type: ‘TxTypeAccountUpdate’,
typeInt: 32
}

9. Confirming AccountKey

If the transaction was successful, the account key stored on the network should be updated. You can check the result using caver.rpc.klay.getAccountKey.

Confirm your new accountKey with caver-js like this:

// caver-js
const accountKey = await caver.rpc.klay.getAccountKey(senderKeyring.address)
console.log(accountKey)

Or with caver-java:

// caver-java
AccountKey accountKey = caver.rpc.klay.getAccountKey(senderKeyring.getAddress()).send();
System.out.println(objectToString(accountKey));

The code above will return the account keys stored on the Klaytn account. Since the key has been updated to AccountKeyWeightedMultiSig, the keyType, represented by the key type ID, is 4. For more details on Account Key Type IDs, please refer to Klaytn Docs.

{
keyType: 4,
key: {
threshold: 3,
keys: [
{
weight: 2,
key: { x: ‘0xd2df4…’, y: ‘0x813fe…’ }
},
{
weight: 1,
key: { x: ‘0xb0085…’, y: ‘0x478f5…’ }
},
{
weight: 1,
key: { x: ‘0x19688…’, y: ‘0x9d8b3…’ }
}
]
}
}

10. Updating in-memory wallet’s keyring

If your account key has been updated with the successfully processed transaction, you have to start using the updated key for signing transactions.

For now, the key that is used in the keyring, and stored in the in-memory wallet, is the old one before the update. Sending a transaction in this state will throw an error, as shown below:

Error: Returned error: invalid transaction v, r, s values of the sender

That is why you also have to update the keyring’s keys stored in the in-memory wallet after updating your Klaytn account key.

The private key used in keyring and stored in the in-memory wallet can be updated using caver.wallet.updateKeyring. The newKeyring that contains the updated Klaytn account address and new private key will be passed as parameter.

Below is how you can update in-memory wallet’s keyring using caver-js:

// caver-js
caver.wallet.updateKeyring(newKeyring)

And here is how you can do this using caver-java:

// caver-java
caver.wallet.updateKeyring(newKeyring);

11. Sending a transaction with the updated account

Now that your Klaytn account key has been updated, let’s try sending a transaction using the updated account.

Since the account key stored on the network has been updated, you have to sign the transaction using this new key. In section [10. Updating in-memory wallet’s keyring], we also updated the keys in the keyring in the in-memory wallet, so the transaction will be signed with the updated private keys.

Here we will just send a simple value transfer transaction to confirm the update.

Below is a sample that shows how to create, sign and send a value transfer transaction using caver-js:

// caver-js
const vt = caver.transaction.valueTransfer.create({
from: senderKeyring.address,
to: recipientAddress,
value: 1,
gas: 100000,
})

await caver.wallet.sign(senderKeyring.address, vt)

const vtReceipt = await caver.rpc.klay.sendRawTransaction(vt)

Create a transaction using caver.transaction.valueTransfer, and sign it using the caver.wallet.sign function as already demonstrated. Since the keyring in caver.wallet has been updated in [10. Updating in-memory wallet’s keyring], the transaction will be signed using the new private key. After that, the transaction will be sent to the network using caver.rpc.klay.sendRawTransaction. In this tutorial, the transaction result is returned using Promise.

Below is a sample of the same process, but for caver-java:

// caver-javaValueTransfer vt = caver.transaction.valueTransfer.create(
TxPropertyBuilder.valueTransfer()
.setFrom(sednerKeyring.getAddress())
.setTo(recipientAddress)
.setValue(BigInteger.valueOf(1))
.setGas(BigInteger.valueOf(100000))
);caver.wallet.sign(senderKeyring.getAddress(), vt);Bytes32 vtResult = caver.rpc.klay.sendRawTransaction(vt).send();TransactionReceipt.TransactionReceiptData vtReceiptData = receiptProcessor.waitForTransactionReceipt(vtResult.getResult());

Create a ValueTransfer transaction and sign it using the caver.wallet.sign function. The transaction will be sent to the network with caver.rpc.klay.sendRawTransaction. You can confirm the transaction status using the returned transaction hash.

In this post, we have shown you how to update an account key to AccountKeyWeightedMultiSig. We hope you could follow each step without any problems. In our next post, we will be looking at how to update your key to AccountKeyRoleBased. The process is pretty much similar to this one, but only with a different key type. If you had no problems following this tutorial, the next one should also be a breeze!

If you have any questions or comments, leave them below or visit Klaytn Developers Forum.

Thank you for reading and stay tuned for more!

The complete code used in this tutorial:

One of the cool features of the Klaytn blockchain is that it has decoupled keys from addresses, so that you can update your account key. In this post, we will be explaining how to update your account key to AccountKeyPublic using caver-js and caver-java. For more details on the different types of AccountKey, please refer to Klaytn Docs.

This tutorial assumes that you have the necessary environment setup. If you haven’t yet set up the environment, please refer to caver-js — Prerequisites or caver-java — Prerequisites.

In each section, we will go through little snippets of the whole code; you can check the code in its entirety in the links below.

1. Creating a keyring

First, create an Klaytn account to use for this tutorial. Your account must have sufficient KLAY, because it will be used for sending a transaction to make the necessary update to the key stored on the network.

Caver uses a structure called Keyring to store private key(s) used for Klaytn account addresses and accounts.

Below is how you can create a Keyring instance in caver-js.

// caver-js
const senderKeyring = caver.wallet.keyring.create(senderAddress, senderPrivateKey)

If you have a keystore file instead of a private key string, use caver.wallet.keyring.decrypt to create a Keyring.

And you can create a Keyring instance in caver-java:

// caver-java
SingleKeyring senderKeyring = caver.wallet.keyring.create(senderAddress, senderPrivateKey);

You can also use caver.wallet.keyring.decrypt for java, if you have a keystore file.

2. Adding a keyring to caver in-memory wallet

In this exercise, we will be using an in-memory wallet. If you add a Keyring to caver’s in-memory wallet, the key stored in that Keyring will automatically be used to sign transactions even if you don’t designate a specific key.

This is how you add a Keyring to in-memory wallet using caver-js:

// caver-js
caver.wallet.add(senderKeyring)

It is the same for caver-java:

// caver-java
caver.wallet.add(senderKeyring);

3. Creating a new private key

In order to update Klaytn account’s AccountKey to AccountKeyPublic, you need a private key for your Klaytn account. Here we will use a private key that is randomly generated using the generateSingleKey function. But if there is a specific private key that you want to use, you can use that one.

Below is how you create a new private key string for your Klaytn account using caver-js:

// caver-js
const newKey = caver.wallet.keyring.generateSingleKey()

And for caver-java:

// caver-java
String newKey = caver.wallet.keyring.generateSingleKey();

4. Creating a new keyring

Now that we have created a new private key, we will go on to create a Keyring instance to store this key. You can start using the Keyring instance once the AccountKey in the Klaytn account has been successfully changed.

You can create a Keyring instance that stores the new private key using caver-js as shown below:

// caver-js
const newKeyring = caver.wallet.keyring.create(senderKeyring.address, newKey)

The newKeyring that stores the new private key will sign the transaction using newKey.

Using caver-java, creating a Keyring instance for storing the new private key looks like this:

// caver-java
SingleKeyring newKeyring = caver.wallet.keyring.create(senderKeyring.getAddress(), newKey);

5. Creating an Account instance

The Account class provided by caver contains the information required to update accounts. When making the update to AccountKeyPublic, you need the Klaytn account address that you want to update, as well as the new public key.

You can create an Account instance by calling the toAccount function of the keyring that stores the newly created private key. Here is what it looks like when you are using caver-js:

// caver-js
const account = newKeyring.toAccount()

You can also create an Account instance using caver-java by calling the toAccountfunction:

// caver-java
Account account = newKeyring.toAccount();

The Account instance will now store the Klaytn account address to be updated and the public key derived from the new private key.

6. Creating a transaction

Once you have created an Account instance, you can use it to create an Account Update transaction.

Here is how you create a transaction using caver-js:

// caver-js
const accountUpdate = caver.transaction.accountUpdate.create({
from: senderKeyring.address,
account: account,
gas: 50000,
})

And here is how you do it using caver-java:

// caver-java
AccountUpdate accountUpdate = caver.transaction.accountUpdate.create(
TxPropertyBuilder.accountUpdate()
.setFrom(senderKeyring.getAddress())
.setAccount(account)
.setGas(BigInteger.valueOf(50000))
);

7. Signing the transaction

Once you have created an account update transaction, you need to sign it using the keyring that was added to the in-memory wallet. Caver’s in-memory wallet, caver.wallet, has a sign function.

Here is how you sign a transaction using caver-js:

// caver-js
await caver.wallet.sign(senderKeyring.address, accountUpdate)

And for caver-java:

// caver-java
caver.wallet.sign(senderKeyring.getAddress(), accountUpdate);

If caver.wallet.sign has been successfully executed, you should see that the signature has been assigned to the signatures field of accountUpdate.

Now all that is left for us to do is to send the transaction to the network.

8. Sending the transaction

Now that we have created and signed the transaction, let’s try sending it to the network. Once this transaction goes through on the network, the old key for your Klaytn account will no longer be usable. Since your old keyring will also no longer be usable, you have to use the new one that stores the new private key.

You can send the signed transaction to the network using caver.rpc.klay.sendRawTransaction.

Below is a sample that shows how to send a transaction with caver-js, using an EventEmitter:

// caver-js
caver.rpc.klay.sendRawTransaction(accountUpdate)
.on(‘transactionHash’, hash => {
console.log(hash)
})
.on(‘receipt’, receipt => {
console.log(receipt)
})

When you are sending a transaction using caver-js, you can get a receipt of the transaction result using Promise with the code below:

// caver-js
const receipt = await caver.rpc.klay.sendRawTransaction(accountUpdate)

You can also send a transaction using caver-java like this:

// caver-java
Bytes32 sendResult = caver.rpc.klay.sendRawTransaction(accountUpdate).send();
String txHash = sendResult.getResult();

When the above code is executed, you will get a transaction hash. The result of the transaction can be obtained with the code as shown below:

// caver-java
public String objectToString(Object value) throws JsonProcessingException {
ObjectWriter ow = new ObjectMapper().writer().withDefaultPrettyPrinter();
return ow.writeValueAsString(value);
}
TransactionReceiptProcessor receiptProcessor = new PollingTransactionReceiptProcessor(caver, 1000, 15);TransactionReceipt.TransactionReceiptData receiptData = receiptProcessor.waitForTransactionReceipt(hash);System.out.println(objectToString(receiptData));

If you see that the status has returned true in the receipt, it means that the transaction has been successfully processed. Transaction type is TxTypeAccountUpdate and the updated AccountKeyPublic is returned in the key field in encoded form.

{
blockHash: ‘0x073ae74db35f15c8f763c89ee4cede3f0a9bfb1256d2b28e2e12f2b4c4ccca18’,
blockNumber: ‘0x33c80b0’,
contractAddress: null,
from: ‘0x405e1c9109684626b5b94177335b2db88e974f86’,
gas: ‘0xc350’,
gasPrice: ‘0x5d21dba00’,
gasUsed: ‘0xa028’,
key: ‘0x02a102259cbde6ac40681c0bdb36a7bd714d7f12214f060a3708a964d80a46a8985d94’,
logs: [],
logsBloom: ‘0x00000…’,
nonce: ‘0x0’,
senderTxHash: ‘0x6d0ccf6afb1b715909244fa8d6affdb9a2fcac74f070cffcb10f6cc1b8b0eb8c’,
signatures: [{ V: ‘0x7f5’, R: ‘0xcc180…’, S: ‘0x161b2…’ }],
status: ‘0x1’,
transactionHash: ‘0x6d0ccf6afb1b715909244fa8d6affdb9a2fcac74f070cffcb10f6cc1b8b0eb8c’,
transactionIndex: ‘0x0’,
type: ‘TxTypeAccountUpdate’,
typeInt: 32
}

9. Confirming AccountKey

If the transaction was successful, the account key stored on the network will have been updated. You can check the result using caver.rpc.klay.getAccountKey.

Confirm your new accountKey in caver-js:

// caver-js
const accountKey = await caver.rpc.klay.getAccountKey(keyring.address)console.log(accountKey)

Or in caver-java:

// caver-java
AccountKey accountKey = caver.rpc.klay.getAccountKey(keyring.getAddress()).send();
System.out.println(objectToString(accountKey));

The above code will return the the account key stored on the Klaytn account. Since the key has been updated to AccountKeyPublic, the keyType, representing a key type ID, is 2. For more details on Account Key Type IDs, please refer to Klaytn Docs.

{
keyType: 2,
key: {
x: ‘0x259cb…’,
y: ‘0x707b7…’
}
}

10. Updating in-memory wallet’s keyring

If your account key has been updated with the successfully processed transaction, you have to start using the updated key for signing transactions.

For now, the key that is used in the keyring, and stored in the in-memory wallet, is the old one before the update. Sending a transaction in this state will cause an error, as shown below:

Error: Returned error: invalid transaction v, r, s values of the sender

That is why you also have to update the keyring’s key stored in the in-memory wallet after updating your Klaytn account key.

The private key used in keyring, and stored in the in-memory wallet can be updated using caver.wallet.updateKeyring. The newKeyring that contains the updated Klaytn account address and new private key will be passed as parameter.

Below is how you can update in-memory wallet’s keyring using caver-js:

// caver-js
caver.wallet.updateKeyring(newKeyring)

And here is how you can do this using caver-java:

// caver-java
caver.wallet.updateKeyring(newKeyring);

11. Sending a transaction with the updated account

Your Klaytn account key has been updated, now let’s try sending a transaction using the updated account.

Since the account key stored on the network has been updated, you will now have to sign the transaction using the new key. In section [10. Updating in-memory wallet’s keyring], we also updated the key in the keyring in the in-memory wallet, so when a transaction is sent, it will be signed with this updated key.

Here we will just send a simple value transfer transaction to confirm the update.

The sample code below shows how to create, sign and send a valueTransfer transaction using caver-js:

// caver-js
const vt = caver.transaction.valueTransfer.create({
from: senderKeyring.address,
to: recipientAddress,
value: 1,
gas: 25000,
})

await caver.wallet.sign(senderKeyring.address, vt)

const vtReceipt = await caver.rpc.klay.sendRawTransaction(vt)

Create a transaction using caver.transaction.valueTransfer, and sign it using the caver.wallet.sign function as already demonstrated. Since the keyring in caver.wallet has been updated in [10. Updating in-memory wallet’s keyring], the new private key will be used to sign the transaction. After that, the transaction will be sent to the network using caver.rpc.klay.sendRawTransaction. In this tutorial, the transaction result is returned using Promise.

Below is the same process in caver-java:

// caver-java
ValueTransfer vt = caver.transaction.valueTransfer.create(
TxPropertyBuilder.valueTransfer()
.setFrom(senderKeyring.getAddress())
.setTo(recipientAddress)
.setValue(BigInteger.valueOf(1))
.setGas(BigInteger.valueOf(25000))
);

caver.wallet.sign(senderKeyring.getAddress(), vt);

Bytes32 vtResult = caver.rpc.klay.sendRawTransaction(vt).send();

TransactionReceipt.TransactionReceiptData vtReceiptData = receiptProcessor.waitForTransactionReceipt(vtResult.getResult());

Create a ValueTransfer transaction and sign it using the caver.wallet.sign function. The transaction will be sent to the network with caver.rpc.klay.sendRawTransaction. You can confirm the transaction status using the returned transaction hash.

In this post, we have demonstrated how to update an account key to AccountKeyPublic. We hope that the explanation was straightforward.

In our next post, we will be looking at how to update your key to AccountKeyWeightedMultiSig. The process is pretty much similar to this one, only with a different key type. If you had no problems following this tutorial, the next one should also be a breeze!

Source code links used in this post:

If you have any questions, please visit our Developer Forum. 😉

Thank you for reading and stay tuned for more posts!

Starting from caver v1.6.1, caver.contract supports the fee delegation model. In this post, we will explain how to use fee delegation when using Contract with Caver.

In each section, we will show you little code snippets; you can check the entire code in the links below.

📢 Note
Your account needs to have a sufficient KLAY balance in order to execute the code shown in this tutorial.

1. Adding test account to in-memory wallet

Before deploying and executing a smart contract using caver.contract, you need to add an account with sufficient KLAY to the in-memory caver.wallet. For this you need a deployer account that sends the transaction, and a feePayer account that pays the transaction fee.

This is how you add an account to caver.wallet using caver-js:

// Create keyrings and add to the in-memory wallet - caver-js
const deployerAddress = '0x{address}'
const deployerPrivateKey = '0x{private key}'
const feePayerAddress = '0x{address}'
const feePayerPrivateKey = '0x{private key}'

const deployerKeyring = caver.wallet.keyring.create(deployerAddress, deployerPrivateKey)
caver.wallet.add(deployerKeyring)const feePayerKeyring = caver.wallet.keyring.create(feePayerAddress, feePayerPrivateKey)
caver.wallet.add(feePayerKeyring)

Create a Keyring instance with the address and the private key to be used in the test account with the caver.wallet.keyring.create function as parameter. The Keyring instance can then be added to the in-memory wallet using the caver.wallet.add function.

If you have a keystore instead of a private key, you can create a Keyring instance using caver.wallet.keyring.decrypt. If your account key has been updated with a decoupled key, you can create the Keyring instance using caver.wallet.keyring.create.

You can also use the caver.wallet.add function for caver-java as below:

// Create a keyring and add to the in-memory wallet - caver-java
String deployerAddress = "0x{address}";
String deployerPrivateKey = "0x{private key}";String feePayerAddress = "0x{address}";
String feePayerPrivateKey = "0x{private key}";

SingleKeyring deployerKeyring = caver.wallet.keyring.create(deployerAddress, deployerPrivateKey);
caver.wallet.add(deployerKeyring);

SingleKeyring feePayerKeyring = caver.wallet.keyring.create(feePayerAddress, feePayerPrivateKey);
caver.wallet.add(feePayerKeyring);

2. Deploying smart contract

In this section, we will be demonstrating a simple smart contract that maps keys with values. The contract looks like this:

pragma solidity ^0.5.6;

contract KVstore {
mapping(string=>string) store;

constructor (string memory key, string memory value) public {
store[key] = value;
}

function get(string memory key) public view returns (string memory) {
return store[key];
}

function set(string memory key, string memory value) public {
store[key] = value;
}
}

Below shows how you can deploy a smart contract using fee delegation with caver-js. The abi and byteCode in the source code below are the result of compiling the smart contract above.

// Deploy a smart contract to the Klaytn - caver-js
const byteCode = '0x608060405234801561001057600080fd5b5060405161072d38038061072d8339810180604052604081101561003357600080fd5b81019080805164010000000081111561004b57600080fd5b8281019050602081018481111561006157600080fd5b815185600182028301116401000000008211171561007e57600080fd5b5050929190602001805164010000000081111561009a57600080fd5b828101905060208101848111156100b057600080fd5b81518560018202830111640100000000821117156100cd57600080fd5b5050929190505050806000836040518082805190602001908083835b6020831061010c57805182526020820191506020810190506020830392506100e9565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020908051906020019061015292919061015a565b5050506101ff565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061019b57805160ff19168380011785556101c9565b828001600101855582156101c9579182015b828111156101c85782518255916020019190600101906101ad565b5b5090506101d691906101da565b5090565b6101fc91905b808211156101f85760008160009055506001016101e0565b5090565b90565b61051f8061020e6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063693ec85e1461003b578063e942b5161461016f575b600080fd5b6100f46004803603602081101561005157600080fd5b810190808035906020019064010000000081111561006e57600080fd5b82018360208201111561008057600080fd5b803590602001918460018302840111640100000000831117156100a257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506102c1565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610134578082015181840152602081019050610119565b50505050905090810190601f1680156101615780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102bf6004803603604081101561018557600080fd5b81019080803590602001906401000000008111156101a257600080fd5b8201836020820111156101b457600080fd5b803590602001918460018302840111640100000000831117156101d657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192908035906020019064010000000081111561023957600080fd5b82018360208201111561024b57600080fd5b8035906020019184600183028401116401000000008311171561026d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506103cc565b005b60606000826040518082805190602001908083835b602083106102f957805182526020820191506020810190506020830392506102d6565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156103c05780601f10610395576101008083540402835291602001916103c0565b820191906000526020600020905b8154815290600101906020018083116103a357829003601f168201915b50505050509050919050565b806000836040518082805190602001908083835b6020831061040357805182526020820191506020810190506020830392506103e0565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020908051906020019061044992919061044e565b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061048f57805160ff19168380011785556104bd565b828001600101855582156104bd579182015b828111156104bc5782518255916020019190600101906104a1565b5b5090506104ca91906104ce565b5090565b6104f091905b808211156104ec5760008160009055506001016104d4565b5090565b9056fea165627a7a72305820adabefbb9574a90843d986f100c723c37f37e79f289b16aa527705b5341499aa0029'const abi = [
{
constant: true,
inputs: [{ name: 'key', type: 'string' }],
name: 'get',
outputs: [{ name: '', type: 'string' }],
payable: false,
stateMutability: 'view',
type: 'function',
},
{
constant: false,
inputs: [{ name: 'key', type: 'string' }, { name: 'value', type: 'string' }],
name: 'set',
outputs: [],
payable: false,
stateMutability: 'nonpayable',
type: 'function',
},
{
inputs: [{ name: 'key', type: 'string' }, { name: 'value', type: 'string' }],
payable: false,
stateMutability: 'nonpayable',
type: 'constructor',
},]

let contract = caver.contract.create(abi)const keyString = 'keyString'// Send a FeeDelegatedSmartContractDeploy transaction directly to Klaytn.contract = await contract.deploy({
from: deployerKeyring.address,
feeDelegation: true,
feePayer: feePayerKeyring.address,
gas: 1000000,}, byteCode, keyString, 'valueString')console.log(`The address of deployed smart contract: ${contract.options.address}`)

Create a Contract instance by passing the smart contract’s ABI (Application Binary Interface) as a parameter of the caver.contract.create function. And then deploy the smart contract on Klaytn using the contract.deploy method. The first parameter of the method is the options object, where the values required for sending transactions are defined. From the example above, you can see that feeDelegation: true and feePayer are defined inside the options object. If the feeDelegation field is true, fee delegation transaction will be used for deploying smart contracts. The FeeDelegatedSmartContractDeployWithRatio transaction will be used if the feeRatio field is defined in the options object; otherwise FeeDelegatedSmartContractDeploy transaction will be used.

contract.deploy from the above example creates aFeeDelegatedSmartContractDeploy transaction to deploy a smart contract. Since the keyring of the deployer and fee payer keyring managed by caver.wallet are used for signing transactions before being sent to Klaytn, the fee payer account needs to be defined in the feePayer field when using fee delegation. Pass the byte code of the smart contract as the second parameter, followed by the constructor parameters of the contract.

contract.deploy returns the Contract instance that stores the address of the deployed smart contract.

If the transaction cannot be sent right away because the deployer and fee payer are different , you need a separate signature from each of them.

// The deployer signs the transaction to deploy a smart contract. - caver-js
const deployTx = await contract.sign({
from: deployerKeyring.address,
feeDelegation: true,
gas: 1000000,
}, 'constructor', byteCode, keyString, 'valueString')
console.log(`Deployer signed transaction: `)
console.log(deployTx)

const rlpEncoded = deployTx.getRLPEncoding()

You can get the transaction with the deployer’s signature using the contract.sign function. In the example above, the options object, which includes the data required for creating a transaction, is the first parameter of the contract.sign function. You will notice constructor in the place of the second parameter. contract.sign requires the name of the method as the second parameter, but if you are deploying a smart contract, use constructor instead.

You don’t have to define feePayer in options for contract.sign, since it is simply for adding the deployer’s signature to the transaction. The signedFeeDelegatedSmartContractDeploy transaction returned from contract.sign can be retrieved as an RLP-encoded string using the getRLPEncoding function.

// The fee payer signs the transaction to deploy a smart contract. - caver-js
const deployTx = caver.transaction.decode(rlpEncoded)

await caver.wallet.signAsFeePayer(feePayerKeyring.address, deployTx) // Signs the transaction as a fee payer
const receipt = await caver.rpc.klay.sendRawTransaction(deployTx)
console.log(`The address of deployed smart contract: ${receipt.contractAddress}`)
contract = caver.contract.create(abi, receipt.contractAddress)

The fee payer receives the RLP-encoded string and creates a transaction instance using caver.transaction.decode. The fee payer can sign the FeeDelegatedSmartContractDeploy transaction, returned from caver.transaction.decode, using caver.wallet.signAsFeePayer. Once the transaction has been signed by both the deployer and fee payer, it can be sent to the Klaytn network using caver.rpc.klay.sendRawTransaction.

Use contract.signAsFeePayer if the fee payer has to give the signature first. contract.signAsFeePayer returns the transaction with the fee payer’s signature added to feePayerSignatures. After that the deployer’s signature is added using caver.wallet.sign and the transaction is sent to Klaytn using caver.rpc.klay.sendRawTransaction.

With caver-java, you can deploy a smart contract with fee delegation as shown below:

// Deploy a smart contract to the Klaytn - caver-javaString byteCode = "0x608060405234801561001057600080fd5b5060405161072d38038061072d8339810180604052604081101561003357600080fd5b81019080805164010000000081111561004b57600080fd5b8281019050602081018481111561006157600080fd5b815185600182028301116401000000008211171561007e57600080fd5b5050929190602001805164010000000081111561009a57600080fd5b828101905060208101848111156100b057600080fd5b81518560018202830111640100000000821117156100cd57600080fd5b5050929190505050806000836040518082805190602001908083835b6020831061010c57805182526020820191506020810190506020830392506100e9565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020908051906020019061015292919061015a565b5050506101ff565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061019b57805160ff19168380011785556101c9565b828001600101855582156101c9579182015b828111156101c85782518255916020019190600101906101ad565b5b5090506101d691906101da565b5090565b6101fc91905b808211156101f85760008160009055506001016101e0565b5090565b90565b61051f8061020e6000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c8063693ec85e1461003b578063e942b5161461016f575b600080fd5b6100f46004803603602081101561005157600080fd5b810190808035906020019064010000000081111561006e57600080fd5b82018360208201111561008057600080fd5b803590602001918460018302840111640100000000831117156100a257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506102c1565b6040518080602001828103825283818151815260200191508051906020019080838360005b83811015610134578082015181840152602081019050610119565b50505050905090810190601f1680156101615780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b6102bf6004803603604081101561018557600080fd5b81019080803590602001906401000000008111156101a257600080fd5b8201836020820111156101b457600080fd5b803590602001918460018302840111640100000000831117156101d657600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192908035906020019064010000000081111561023957600080fd5b82018360208201111561024b57600080fd5b8035906020019184600183028401116401000000008311171561026d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506103cc565b005b60606000826040518082805190602001908083835b602083106102f957805182526020820191506020810190506020830392506102d6565b6001836020036101000a03801982511681845116808217855250505050505090500191505090815260200160405180910390208054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156103c05780601f10610395576101008083540402835291602001916103c0565b820191906000526020600020905b8154815290600101906020018083116103a357829003601f168201915b50505050509050919050565b806000836040518082805190602001908083835b6020831061040357805182526020820191506020810190506020830392506103e0565b6001836020036101000a0380198251168184511680821785525050505050509050019150509081526020016040518091039020908051906020019061044992919061044e565b505050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061048f57805160ff19168380011785556104bd565b828001600101855582156104bd579182015b828111156104bc5782518255916020019190600101906104a1565b5b5090506104ca91906104ce565b5090565b6104f091905b808211156104ec5760008160009055506001016104d4565b5090565b9056fea165627a7a72305820adabefbb9574a90843d986f100c723c37f37e79f289b16aa527705b5341499aa0029";
String abi = "[\n" +
" {\n" +
" \"constant\":true,\n" +
" \"inputs\":[\n" +
" {\n" +
" \"name\":\"key\",\n" +
" \"type\":\"string\"\n" +
" }\n" +
" ],\n" +
" \"name\":\"get\",\n" +
" \"outputs\":[\n" +
" {\n" +
" \"name\":\"\",\n" +
" \"type\":\"string\"\n" +
" }\n" +
" ],\n" +
" \"payable\":false,\n" +
" \"stateMutability\":\"view\",\n" +
" \"type\":\"function\"\n" +
" },\n" +
" {\n" +
" \"constant\":false,\n" +
" \"inputs\":[\n" +
" {\n" +
" \"name\":\"key\",\n" +
" \"type\":\"string\"\n" +
" },\n" +
" {\n" +
" \"name\":\"value\",\n" +
" \"type\":\"string\"\n" +
" }\n" +
" ],\n" +
" \"name\":\"set\",\n" +
" \"outputs\":[],\n" +
" \"payable\":false,\n" +
" \"stateMutability\":\"nonpayable\",\n" +
" \"type\":\"function\"\n" +
" },\n" +
" {\n" +
" \"inputs\":[\n" +
" {\n" +
" \"name\":\"key\",\n" +
" \"type\":\"string\"\n" +
" },\n" +
" {\n" +
" \"name\":\"value\",\n" +
" \"type\":\"string\"\n" +
" }\n" +
" ],\n" +
" \"payable\":false,\n" +
" \"stateMutability\":\"nonpayable\",\n" +
" \"type\":\"constructor\"\n" +
" }\n" +
"]";Contract contract = caver.contract.create(abi);
String keyString = "keyString";

SendOptions sendOptionsForDeployment = new SendOptions();
sendOptionsForDeployment.setFrom(deployerKeyring.getAddress());
sendOptionsForDeployment.setGas(BigInteger.valueOf(1000000));
sendOptionsForDeployment.setFeeDelegation(true);
sendOptionsForDeployment.setFeePayer(feePayerKeyring.getAddress());

// Send a FeeDelegatedSmartContractDeploy transaction to the Klaytn directly.
contract.deploy(sendOptionsForDeployment, byteCode, keyString, "valueString");
System.out.println("The address of deployed smart contract:" + contract.getContractAddress());

If the transaction cannot be sent right away because the deployer and fee payer are different, you need a separate signature from each of them.

// The deployer and fee payer each sign the transaction to deploy a smart contract. - caver-java
SendOptions sendOptionsForSigning = new SendOptions();
sendOptionsForSigning.setFrom(deployerKeyring.getAddress());
sendOptionsForSigning.setGas(BigInteger.valueOf(1000000));
sendOptionsForSigning.setFeeDelegation(true);
AbstractTransaction signedTx = contract.sign(sendOptionsForSigning, "constructor", byteCode, keyString, "valueString");String rlpEncoded = signedTx.getRLPEncoding();// The fee payer signs the transaction to deploy a smart contract. - caver-java
AbstractTransaction signedTx = caver.transaction.decode(rlpEncoded);

caver.wallet.signAsFeePayer(feePayer.getAddress(), (AbstractFeeDelegatedTransaction)signedTx);
Bytes32 sendResult = caver.rpc.klay.sendRawTransaction(signedTx).send();String txHash = sendResult.getResult();
TransactionReceiptProcessor receiptProcessor = new PollingTransactionReceiptProcessor(caver, 1000, 15);

TransactionReceipt.TransactionReceiptData receiptData = receiptProcessor.waitForTransactionReceipt(txHash);
System.out.println("The address of deployed smart contract:" + receiptData.getContractAddress());

3. Calling the smart contract function

3.1. call

To confirm that the key and value passed to the constructor have been stored properly, call the get function in the smart contract. The get function simply returns data without changing the contract state. You can call function using contract.call.

Below is how you call the get function using caver-js:

// Read value from smart contract. - caver-js
let valueString = await contract.call('get', keyString)
console.log(`key: ${keyString} / value: ${valueString}`)

You can call the smart contract functions using contract.call. Pass the function name as the first parameter, and the parameters for that function as the second parameter. If you want to define options when calling the function, pass the object as the very first parameter. For more details, please refer to Klaytn Docs.

Below is how you can call the get function using caver-java:

// Read value from smart contract. - caver-java
Utf8String valueString = (Utf8String)contract.call("get", keyString).get(0);
System.out.println("key: " + keyString + " / value: " + valueString.toString());

3.2. send

To make changes to the data stored in the smart contract, call the set function of the deployed smart contract. The set function makes changes to the smart contract’s state, and you need to send a transaction to call it. You can call the function using contract.sendcontract.send will accept the object where the values required for creating a transaction are defined as the first parameter. When you are using fee delegation, you can additionally define feeDelegationfeePayer and feeRatio in the object.

Below is how to call the set function using caver-js:

// Send a FeeDelegatedSmartContractExecutionWithRatio transaction to the Klaytn directly. - caver-js
const setResult = await contract.send({
from: deployerKeyring.address,
feeDelegation: true,
feePayer: feePayerKeyring.address,
feeRatio: 50, // Without feeRatio, `send` will use FeeDelegatedSmartContractExecution
gas: 1000000,
}, 'set', keyString, 'anotherValue')

In the object passed as the first parameter for contract.sendfeeDelegation is defined as true. This means that you will be using fee delegation to execute the set function. Since feeRatio is also defined, FeeDelegatedSmartContractExecutionWithRatio transaction will be used. Pass the name of the function that you want to call as the second parameter, followed by the parameters required by the function. After contract.send creates a transaction, it requires both the deployer and fee payer to give their signatures before sending it to Klaytn. Therefore, feePayer needs to be defined in the first parameter if you are using fee delegation.

If the transaction cannot be sent right away because the deployer and the fee payer are different, they can sign the transactions separately as shown below:

// The deployer and fee payer each sign the transaction to execute a smart contract. - caver-js
const executionTx = await contract.sign({
from: deployerKeyring.address,
feeDelegation: true,
feeRatio: 50, // Without feeRatio, `send` will use FeeDelegatedSmartContractExecution
gas: 1000000,
}, 'set', keyString, 'anotherValue')
console.log(`Deployer signed transaction: `)
console.log(executionTx)

await caver.wallet.signAsFeePayer(feePayerKeyring.address, executionTx) // Signs the transaction as a fee payer
const setResult = await caver.rpc.klay.sendRawTransaction(executionTx)

Deployer signs the transaction using the contract.sign function and adds the signature of the fee payer using the caver.wallet.signAsFeePayer function. Once the transaction is signed, it will be sent to the Klaytn network using caver.rpc.klay.sendRawTransaction.

Once the above transaction has been executed, you can use contract.call to look at the result.

// Read value string from the smart contract. - caver-jsvalueString = await contract.call('get', keyString)
console.log(`After executing set function => key: ${keyString} / value: ${valueString}`)

In caver-java, call the set function like this:

// Send a FeeDelegatedSmartContractExecutionWithRatio transaction to the Klaytn directly. - caver-java
SendOptions sendOptionsForExecution = new SendOptions();
sendOptionsForExecution.setFrom(deployer.getAddress());
sendOptionsForExecution.setGas(BigInteger.valueOf(1000000));
sendOptionsForExecution.setFeeDelegation(true);
sendOptionsForExecution.setFeePayer(feePayer.getAddress());
sendOptionsForExecution.setFeeRatio(BigInteger.valueOf(50)); // Without feeRatio, `send` will use FeeDelegatedSmartContractExecutionTransactionReceipt.TransactionReceiptData receiptData = contract.send(sendOptionsForExecution, "set", "key_inserted", "value_inserted");
System.out.println("Transaction type : " + receiptData.getType());
System.out.println("From : " + receiptData.getFrom());
System.out.println("Fee payer : " + receiptData.getFeePayer());
System.out.println("Fee ratio : " + receiptData.getFeeRatio());

If you need separate signatures from the deployer and fee payer for caver-java:

// The deployer and fee payer each sign the transaction to execute a smart contract. - caver-javaSendOptions sendOptionsForExecution = new SendOptions();
sendOptionsForExecution.setFrom(deployer.getAddress());
sendOptionsForExecution.setGas(BigInteger.valueOf(1000000));
sendOptionsForExecution.setFeeDelegation(true);
sendOptionsForExecution.setFeeRatio(BigInteger.valueOf(50)); // Without feeRatio, `send` will use FeeDelegatedSmartContractExecution

AbstractTransaction executionTx = contract.sign(sendOptionsForExecution, "set", "key_inserted", "value_inserted");
caver.wallet.signAsFeePayer(feePayer.getAddress(), (AbstractFeeDelegatedTransaction)executionTx);

Bytes32 sendResult = caver.rpc.klay.sendRawTransaction(executionTx).send();String txHash_executed = sendResult.getResult();
TransactionReceiptProcessor receiptProcessor = new PollingTransactionReceiptProcessor(caver, 1000, 15);

TransactionReceipt.TransactionReceiptData receiptData = receiptProcessor.waitForTransactionReceipt(txHash_executed);

Once the transaction has been sent, use contract.call to check the result.

// Use short-cut function to read value from smart contract. - caver-java
Utf8String value = (Utf8String)contract.call("get", keyString).get(0);
System.out.println("After executing set function => key: " + keyString + "value: " + value.toString());

Entire Source Code from the Article

We hope this post was helpful for you. For any questions or issues, leave them in the comment section or in our Klaytn Developer Forum.

Thank you for reading!

In this post, we will show how to mint your own NFTs on Klaytn using Truffle.

1. Klaytn?

Klaytn is a public blockchain focused on the metaverse, gamefi, and the creator economy. Officially launched in June 2019, it is the dominant blockchain platform in South Korea and is now undergoing global business expansion from its international base in Singapore.

These business expansion activities are supported by the Klaytn Growth Fund, which aims to grow the ecosystem of companies built on Klaytn. The fund is managed and disbursed by Klaytn Foundation, a Singapore-based non-profit organization established in August 2021.

2. NFT?

NFT stands for Non-Fungible Token, which refers to a unique token that is not interchangeable with other tokens. NFT was born out of Ethereum’s ERC-721, by which each token possesses a unique value. For example, collectible photocards of celebrities and athletes or a Pokémon can represent a non-fungible token. The NFTs on Klaytn follow the KIP-17 standard. More on the KIP-17 standard can be found here: KIPs.

But in this document, we will be focusing more on how we can deploy and mint NFTs on Klaytn. So if you want to know more about NFTs, please refer to other documents.

3. Deploying NFT contracts? Sending tokens?

Let us first begin with a little technical background of NFTs. In order to create NFTs, the NFT smart contract must be deployed on top of Klaytn. You can think of an NFT smart contract as a repository for NFTs. You create a repository, where you can create new NFTs. So we can summarize the key concepts as follows:

4. Deploying NFT contract

While it is also possible to implement a smart contract according to the KIP-17 standard yourself, in this document we will be explaining how you can deploy a smart contract using a KIP-17 reference implementation. You can find this here: Link.

4.1. Installing deploy environment

In klaytn-contracts, which is a Github repository, you can find the easiest way to deploy an NFT contract. Here, we will be using Node API for KAS to deploy a KIP-17 contract. KAS is an API Service developed by GroundX that provides easy access to various features. KAS allows up to 10,000 free API calls per day, so sign up and give it a try!

4.2. Installing Truffle

You can install Truffle using the command below:

$ npm install -g [email protected]

4.3. Cloning klaytn-contract code

You can clone the klaytn-contract repository to your local machine using the command below:

$ git clone https://github.com/klaytn/klaytn-contracts.git

4.4. Updating truffle-config.js

You have to add the access key and secret access key for KAS in the code below. More details on how to obtain access and secret keys can be found here: KAS Docs.

// klaytn-contracts/truffle-config.js:29
const accessKeyId = "ACCESS_KEY";
const secretAccessKey = "SECRET_KEY";

4.5. Installing dependency package

Install the necessary packages using npm install.

klaytn-contracts$ npm install

4.6. Updating migrations/2_contract_migration.js

The contract to be deployed on Truffle can be selected using 2_contract_migration.js. Just modify the file as shown below:

// migrations/2_contract_migration.js
var kip17 = artifacts.require(‘KIP17Token’);
module.exports = function(deployer) {
deployer.deploy(kip17, “Test NFT”, “TN”)
};

4.7. Test deployment on Baobab

Baobab is a Klaytn testnet. You can obtain Baobab’s test KLAY, using the Baobab faucet. Create a new account on KlaytnWallet and then go on the faucet to obtain your test KLAY. Add the private key of the account to truffle-config.js as shown below:

// klaytn-contracts/truffle-config.js:40
const privateKey = “0x123 …”;

If you have added access key, secret key and private key to truffle-config.js, you are now ready to deploy your NFT. All you have to do is to execute the command below. We’ve pasted the entire log for the command execution, so you can check if it works without any problems.

klaytn-contracts$ truffle deploy --network kasBaobab
Compiling your contracts…
===========================
> Compiling ./contracts/GSN/Context.sol
> Compiling ./contracts/Migrations.sol
> Compiling ./contracts/access/Roles.sol
> Compiling ./contracts/access/roles/MinterRole.sol
> Compiling ./contracts/access/roles/PauserRole.sol
> Compiling ./contracts/drafts/Counters.sol
> Compiling ./contracts/introspection/IKIP13.sol
> Compiling ./contracts/introspection/KIP13.sol
> Compiling ./contracts/introspection/KIP13Checker.sol
> Compiling ./contracts/lifecycle/Pausable.sol
> Compiling ./contracts/math/SafeMath.sol
> Compiling ./contracts/mocks/ERC1155ReceiverMock.sol
> Compiling ./contracts/mocks/ERC721ReceiverMock.sol
> Compiling ./contracts/mocks/KIP13/KIP13InterfacesSupported.sol
> Compiling ./contracts/mocks/KIP13/KIP13NotSupported.sol
> Compiling ./contracts/mocks/KIP13CheckerMock.sol
> Compiling ./contracts/mocks/KIP13Mock.sol
> Compiling ./contracts/mocks/KIP17FullMock.sol
> Compiling ./contracts/mocks/KIP17MintableBurnableImpl.sol
> Compiling ./contracts/mocks/KIP17Mock.sol
> Compiling ./contracts/mocks/KIP17PausableMock.sol
> Compiling ./contracts/mocks/KIP17ReceiverMock.sol
> Compiling ./contracts/mocks/KIP37BurnableMock.sol
> Compiling ./contracts/mocks/KIP37Mock.sol
> Compiling ./contracts/mocks/KIP37PausableMock.sol
> Compiling ./contracts/mocks/KIP37ReceiverMock.sol
> Compiling ./contracts/mocks/KIP7BurnableMock.sol
> Compiling ./contracts/mocks/KIP7MetadataMock.sol
> Compiling ./contracts/mocks/KIP7MintableMock.sol
> Compiling ./contracts/mocks/KIP7Mock.sol
> Compiling ./contracts/mocks/KIP7PausableMock.sol
> Compiling ./contracts/mocks/MinterRoleMock.sol
> Compiling ./contracts/mocks/PauserRoleMock.sol
> Compiling ./contracts/token/KIP17/IERC721Receiver.sol
> Compiling ./contracts/token/KIP17/IKIP17.sol
> Compiling ./contracts/token/KIP17/IKIP17Enumerable.sol
> Compiling ./contracts/token/KIP17/IKIP17Full.sol
> Compiling ./contracts/token/KIP17/IKIP17Metadata.sol
> Compiling ./contracts/token/KIP17/IKIP17Receiver.sol
> Compiling ./contracts/token/KIP17/KIP17.sol
> Compiling ./contracts/token/KIP17/KIP17Burnable.sol
> Compiling ./contracts/token/KIP17/KIP17Enumerable.sol
> Compiling ./contracts/token/KIP17/KIP17Full.sol
> Compiling ./contracts/token/KIP17/KIP17Metadata.sol
> Compiling ./contracts/token/KIP17/KIP17MetadataMintable.sol
> Compiling ./contracts/token/KIP17/KIP17Mintable.sol
> Compiling ./contracts/token/KIP17/KIP17Pausable.sol
> Compiling ./contracts/token/KIP17/KIP17Token.sol
> Compiling ./contracts/token/KIP37/IERC1155Receiver.sol
> Compiling ./contracts/token/KIP37/IKIP37.sol
> Compiling ./contracts/token/KIP37/IKIP37MetadataURI.sol
> Compiling ./contracts/token/KIP37/IKIP37Receiver.sol
> Compiling ./contracts/token/KIP37/KIP37.sol
> Compiling ./contracts/token/KIP37/KIP37Burnable.sol
> Compiling ./contracts/token/KIP37/KIP37Holder.sol
> Compiling ./contracts/token/KIP37/KIP37Mintable.sol
> Compiling ./contracts/token/KIP37/KIP37Pausable.sol
> Compiling ./contracts/token/KIP37/KIP37Receiver.sol
> Compiling ./contracts/token/KIP37/KIP37Token.sol
> Compiling ./contracts/token/KIP7/IKIP7.sol
> Compiling ./contracts/token/KIP7/IKIP7Receiver.sol
> Compiling ./contracts/token/KIP7/KIP7.sol
> Compiling ./contracts/token/KIP7/KIP7Burnable.sol
> Compiling ./contracts/token/KIP7/KIP7Metadata.sol
> Compiling ./contracts/token/KIP7/KIP7Mintable.sol
> Compiling ./contracts/token/KIP7/KIP7Pausable.sol
> Compiling ./contracts/token/KIP7/KIP7Token.sol
> Compiling ./contracts/utils/Address.sol
> Compilation warnings encountered:
/Users/kjhman21/Sources/github.com/klaytn/klaytn-contracts/contracts/token/KIP37/KIP37Pausable.sol:79:9: Warning: Unused function parameter. Remove or comment out the variable name to silence this warning.
address operator,
^ — — — — — — — ^
,/Users/kjhman21/Sources/github.com/klaytn/klaytn-contracts/contracts/token/KIP37/KIP37Pausable.sol:80:9: Warning: Unused function parameter. Remove or comment out the variable name to silence this warning.
address from,
^ — — — — — ^
,/Users/kjhman21/Sources/github.com/klaytn/klaytn-contracts/contracts/token/KIP37/KIP37Pausable.sol:81:9: Warning: Unused function parameter. Remove or comment out the variable name to silence this warning.
address to,
^ — — — — ^
,/Users/kjhman21/Sources/github.com/klaytn/klaytn-contracts/contracts/token/KIP37/KIP37Pausable.sol:83:9: Warning: Unused function parameter. Remove or comment out the variable name to silence this warning.
uint256[] memory amounts,
^ — — — — — — — — — — — ^
,/Users/kjhman21/Sources/github.com/klaytn/klaytn-contracts/contracts/token/KIP37/KIP37Pausable.sol:84:9: Warning: Unused function parameter. Remove or comment out the variable name to silence this warning.
bytes memory data
^ — — — — — — — -^
> Artifacts written to /Users/kjhman21/Sources/github.com/klaytn/klaytn-contracts/build/contracts
> Compiled successfully using:
- solc: 0.5.6+commit.b259423e.Emscripten.clang
Starting migrations…
======================
> Network name: ‘kasBaobab’
> Network id: 1001
> Block gas limit: 0 (0x0)
1_initial_migration.js
======================
Deploying ‘Migrations’ 
— — — — — — — — — — —
> transaction hash: 0xb1c4474dd0819f0a92c7a2510acb76c45b704d56b536230ea9809be33a4b2602
> Blocks: 0 Seconds: 0
> contract address: 0x69E2622287EaBDF8FeDC6D1465626A55e9744777
> block number: 54229441
> block timestamp: 1615785044
> account: 0xEB5cCFa5C1750a2d075Df8F7a0f34490d4930b8b
> balance: 78.856641625
> gas used: 140894 (0x2265e)
> gas price: 25 gwei
> value sent: 0 ETH
> total cost: 0.00352235 ETH
> Saving migration to chain.
> Saving artifacts
— — — — — — — — — — — — — — — — — — -
> Total cost: 0.00352235 ETH
2_contract_migration.js
=======================
Deploying ‘KIP17Token’
— — — — — — — — — — —
> transaction hash: 0x55d5f332e7eb86e12d9688acb53fab8fe4c62c4e86eda7ea5ee46f6580b864d9
> Blocks: 0 Seconds: 0
> contract address: 0xc609eB0eE11D4246AC6d0c070c0D2374B53d7BA8
> block number: 54229446
> block timestamp: 1615785049
> account: 0xEB5cCFa5C1750a2d075Df8F7a0f34490d4930b8b
> balance: 78.780642175
> gas used: 2998052 (0x2dbf24)
> gas price: 25 gwei
> value sent: 0 ETH
> total cost: 0.0749513 ETH
> Saving migration to chain.
> Saving artifacts
— — — — — — — — — — — — — — — — — — -
> Total cost: 0.0749513 ETH
Summary
=======
> Total deployments: 2
> Final cost: 0.07847365 ETH

If you see the Summary as shown above, it means your contract has been deployed well. Make sure to remember the deployed contract address. In the above example it is 0xc609eB0eE11D4246AC6d0c070c0D2374B53d7BA8. This is the address to which the NFT contract has been deployed.

4.8. Deploying on Cypress

To deploy your NFT on Cypress, you need a Klaytn account with a balance of at least 0.07847365 KLAY, which is the amount used for the above transaction. You can obtain KLAY in an exchange or a wallet app that supports KLAY purchases.

Enter the private key of the account with KLAY balance to the private key parameter for truffle-config.js and execute.

klaytn-contracts$ truffle deploy --network kasCypress

If you want to modify a part of the contract, you can easily do so by going on klaytn-contracts/contracts/token/KIP17 and make the desired changes before deploying it.

4.9. Minting/sending/burning tokens with caver

Truffle is a nice tool for deploying smart contracts, but a bit heavy if you wanted to execute a method of the smart contract. We will thus explain the various token operations using caver.

4.9.1. Minting/sending/burning tokens with caver-js-ext-kas

Minting/sending/burning tokens is easy with caver-js-ext-kas. You can try it yourself using the sample code below.

const CaverExtKAS = require('caver-js-ext-kas')
// Configuration Part
// Set your KAS access key and secretAccessKey.
const accessKeyId = '{your_accessKeyId}'
const secretAccessKey = '{your_secretAccessKey}'
// const CHAIN_ID_BAOBOB = '1001'
// const CHAIN_ID_CYPRESS = '8217'
const chainId = '1001'
const contractAddress = '{contractAddress}'
const caver = new CaverExtKAS(chainId, accessKeyId, secretAccessKey)
test()
async function test () {
const privateKey = '0x{private key}'
// Create a KeyringContainer instance
const keyringContainer = new caver.keyringContainer()
// Add keyring to in-memory wallet
const keyring = keyringContainer.keyring.createFromPrivateKey(privateKey)
keyringContainer.add(keyring)
// Create a KIP17 instance
const kip17 = new caver.kct.kip17(contractAddress)

// Call `kip17.setWallet(keyringContainer)` to use KeyringContainer instead of KAS Wallet API
kip17.setWallet(keyringContainer)
const tokenId = '1'
const uri = 'http://test.url'
const mintReceipt = await kip17.mintWithTokenURI(keyring.address, tokenId, uri, { from:keyring.address })
console.log(`mint receipt: `)
console.log(mintReceipt)
const transferReceipt = await kip17.transferFrom(keyring.address, keyring.address, tokenId, { from:keyring.address })
console.log(`transfer receipt: `)
console.log(transferReceipt)
const burnReceipt = await kip17.burn(tokenId, { from:keyring.address })
console.log(`burn receipt: `)
console.log(burnReceipt)
}

4.9.2. Minting/sending/burning tokens with caver-java-ext-kas

You can easily mint/sent/burn tokens with caver-java-ext-kas as well. Try it yourself using the sample code below.

package com.klaytn.caver.boilerplate;
import com.klaytn.caver.Caver;
import com.klaytn.caver.account.Account;
import com.klaytn.caver.contract.SendOptions;
import com.klaytn.caver.kct.kip17.KIP17;
import com.klaytn.caver.kct.kip7.KIP7;
import com.klaytn.caver.methods.response.AccountKey;
import com.klaytn.caver.methods.response.Bytes32;
import com.klaytn.caver.methods.response.TransactionReceipt;
import com.klaytn.caver.transaction.response.PollingTransactionReceiptProcessor;
import com.klaytn.caver.transaction.response.TransactionReceiptProcessor;
import com.klaytn.caver.transaction.type.AccountUpdate;
import com.klaytn.caver.transaction.type.ValueTransfer;
import com.klaytn.caver.wallet.keyring.KeyringFactory;
import com.klaytn.caver.wallet.keyring.SingleKeyring;
import okhttp3.Credentials;
import org.web3j.protocol.exceptions.TransactionException;
import org.web3j.protocol.http.HttpService;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.math.BigInteger;
public class BoilerPlate {
private static final String URL_NODE_API = "https://node-api.klaytnapi.com/v1/klaytn";
// Configuration Part
// Set your KAS access key and secretAccessKey.
static String accessKey = "{your_accessKeyId}";
static String secretAccessKey = "{your_secretAccessKey}";
// static String CHAIN_ID_BAOBOB = "1001";
// static String CHAIN_ID_CYPRESS = "8217";
static String chainId = "1001";
static String contractAddress = "{contractAddress}";
public static void main(String[] args) {
// Build a Caver instance.
Caver caver = setCaver(accessKey, secretAccessKey, chainId);
// Run a test.
test(caver);
}
public static void test(Caver caver) {
String testPrivateKey = "0x{private key}";
SingleKeyring deployerKeyring = KeyringFactory.createFromPrivateKey(testPrivateKey);
caver.wallet.add(deployerKeyring);
try {
// Create a KIP17 instance
KIP17 kip17 = new KIP17(caver, contractAddress);
//Mint a NFT token
BigInteger tokenId = BigInteger.ONE;
String uri = "http://test.url";
TransactionReceipt.TransactionReceiptData mintReceiptData = kip17.mintWithTokenURI(deployerKeyring.getAddress(), tokenId, uri, new SendOptions(deployerKeyring.getAddress()));
System.out.println("NFT mint transaction hash : " + mintReceiptData.getTransactionHash());
//Transfer a NFT token
TransactionReceipt.TransactionReceiptData transferReceiptData = kip17.transferFrom(deployerKeyring.getAddress(), deployerKeyring.getAddress(), tokenId, new SendOptions(deployerKeyring.getAddress()));
System.out.println("NFT transfer transaction hash : " + transferReceiptData.getTransactionHash());
//Burn a NFT token
TransactionReceipt.TransactionReceiptData burnReceiptData = kip17.burn(tokenId, new SendOptions(deployerKeyring.getAddress()));
System.out.println("NFT burn transaction hash : " + burnReceiptData.getTransactionHash());
} catch (NoSuchMethodException | IOException | InstantiationException | ClassNotFoundException | IllegalAccessException | InvocationTargetException | TransactionException e) {
e.printStackTrace();
}
}

private static Caver setCaver(String accessKey, String secretAccessKey, String chainID) {
HttpService httpService = new HttpService(URL_NODE_API);
httpService.addHeader("Authorization", Credentials.basic(accessKey, secretAccessKey));
httpService.addHeader("x-chain-id", chainID);
return new Caver(httpService);
  }
}

In this post we introduce you to Klaytn v1.5.0’s new feature called the “State Migration”, which allows you to keep only the latest states on the chain starting from a certain block while dumping the unnecessary states and data. In the end you can save up to 75% of node storage!

Overview

What is the State Trie?

On Klaytn, all account information is stored in a data structure called the Merkle Patricia Trie, also called the state trie. This trie is stored in the Klaytn Node storage.

Every block in the blockchain has a state trie. In other words, one Klaytn block contains one state trie, and it stores all the account information.

What is State Migration?

State Migration is a process by which the state trie before a specific block is completely deleted from the node storage. By keeping only the state tries from that block along with the blocks that will be added afterwards, the node will eventually have only the fresh chaindata that you need for operating Endpoint Nodes (EN), which saves a lot of space.

State Migration consists of the following steps:

1. Choose the block for Migration

Choose which block’s state trie you want remained in the EN Storage. In other words, you are determining a certain point from which you want to keep the data.

2. Copy the block’s state trie

Once you have chosen the block for migration, a new database can be created. By traversing the state trie of the that block in the existing database, every node in the state trie is copied to the new database.

NOTE: As the state trie of the specified block is being copied, new blocks are continuously being added to the blockchain.

3. Validate the copied state trie

Once the state trie of the specified block has been copied into the new database, it should now be validated to see if all the data has been copied intactly.

4. Delete the old database, start storing on the new database

After validation, it deletes the old database and starts storing the state tries for newly added blocks in the new database.

Recommended System Requirements

In order to perform State Migration, a m5.4xlarge instance is required on AWS at minimum. To complete one session of State Migration, you would need about 50 hours (for Cypress, as of August 2020). This is the time that is required for synchronizing the latest blocks, traversing the block’s state trie, copying the trie nodes into the new database, and validating them. You will need less time with a larger instance. It takes about 30 hours with m5.8xlarge.

Prerequisites

To run State Migration, Klaytn v1.5.0 or later is required. To update, download Klaytn 1.5.0 binary (link), stop the existing node, replace the downloaded binary and then install Klaytn again. The update process may vary according to the system environment. For details, please refer to the EN installation guide.

Getting Started with State Migration

Run State Migration

To run State Migration, you must be able to use Admin API on your Klaytn node. Connect to the node via IPC or temporarily activate Admin API on RPC/WS. For detailed information about how to connect to the Klaytn node via IPC or using Admin API, please refer to JSON-RPC APIs and Admin APIs.

> ken attach ~/datadir/ken.ipcWelcome to the Klaytn JavaScript console!
instance: Klaytn/v1.5.0+94c52174fe/linux-amd64/go1.14.1
datadir: /data/kend/datamodules: admin:1.0 debug:1.0 governance:1.0 istanbul:1.0 klay:1.0 net:1.0 personal:1.0 rpc:1.0 txpool:1.0 web3:1.0> admin.startStateMigration()null

If your start was successful, admin.startStateMigration() will return null. Once State Migration has kicked off, the state trie of the specified block will be traversed and then copied into the new database while new blocks are continuously being processed.

Log at the Start of State Migration

After a successful start of State Migration, the following logs will be shown in the log file:

INFO[07/20,20:44:35 +09] [5] State migration is prepared expectedMigrationStartingBlockNumber=29911450…INFO[07/20,20:55:51 +09] [5] State migration is started block=29911450 root=06b082d~60b893INFO[07/20,20:56:06 +09] [44] Add a new stakingInfo to stakingInfoCache and stakingInfoDB blockNum=29894400INFO[07/20,20:56:06 +09] [46] Start setting a new database for state trie migration blockNum=29911450INFO[07/20,20:56:06 +09] [46] LevelDB configurations path=/data/kend/data/klay/chaindata/statetrie_migrated_29911450/0 levelDBCacheSize=36 openFilesLimit=97 useBufferPool=true compressionType=none compactionTableSize(MB)=2 compactionTableSizeMultiplier=1.000INFO[07/20,20:56:06 +09] [46] LevelDB configurations path=/data/kend/data/klay/chaindata/statetrie_migrated_29911450/1 levelDBCacheSize=36 openFilesLimit=97 useBufferPool=true compressionType=none compactionTableSize(MB)=2 compactionTableSizeMultiplier=1.000INFO[07/20,20:56:06 +09] [46] LevelDB configurations path=/data/kend/data/klay/chaindata/statetrie_migrated_29911450/2 levelDBCacheSize=36 openFilesLimit=97 useBufferPool=true compressionType=none compactionTableSize(MB)=2 compactionTableSizeMultiplier=1.000INFO[07/20,20:56:06 +09] [46] LevelDB configurations path=/data/kend/data/klay/chaindata/statetrie_migrated_29911450/3 levelDBCacheSize=36 openFilesLimit=97 useBufferPool=true compressionType=none compactionTableSize(MB)=2 compactionTableSizeMultiplier=1.000INFO[07/20,20:56:06 +09] [46] Created a new database for state trie migration newStateTrieDB=/data/kend/data/klay/chaindata/statetrie_migrated_29911450INFO[07/20,20:56:06 +09] [47] Persisted nodes from memory database by Cap nodes=0 size=0.00B time=6.031µs flushnodes=0 flushsize=0.00B flushtime=5.704µs livenodes=1 livesize=0..00B

Monitoring State Migration

Checking State Migration Progress with Logs

While State Migration is ongoing, you can regularly check the progress in logs as shown below:

Progress Logs Printed with a 10-second Interval

INFO[07/20,20:56:28 +09] [5] State migration progress progress=0% totalRead=2321 totalCommitted=0 pending=37134 read=2321 readElapsed=22.184223002s processElapsed=33.163185ms written=0 writeElapsed=17.257µs elapsed=22.217s totalElapsed=22.21783843sINFO[07/21,08:44:33 +09] [5] State migration progress progress=64.917% totalRead=106657041 totalCommitted=106654913 pending=13469 read=19456 readElapsed=11h36m47.910769711s processElapsed=5m7.371806884s written=19717 writeElapsed=5m25.733869118s elapsed=10.147s totalElapsed=11h48m26.757580868s

Checking State Migration Status via RPC API

You can also check the progress of State Migration via RPC API. The result below shows that the state trie copying is about 64% complete.

> admin.stateMigrationStatus{
committed: 106743172,
err: “null”,
isMigration: true,
migrationBlockNumber: 29911450,
pending: 12036,
progress: 64.9169921875,
read: 106745105
}

Validation of the Copied State Trie

Once copying the state trie is done, your logs will look like as shown below. Next, you have to validate whether the state trie has been intactly copied.

The status of the validation can be checked in the logs as shown below:

Validation Status Logs

INFO[07/23,11:11:22 +09] [5] State migration progress progress=100% totalRead=368572444 totalCommitted=368572444 pending=0 read=5374 readElapsed=61h36m58.773140567s processElapsed=17m45.115580174s written=6488 writeElapsed=16m36.198342833s elapsed=4.207s totalElapsed=62h15m15.550216445sINFO[07/23,11:11:22 +09] [5] State migration : Copy is done totalRead=368572444 totalCommitted=368572444 totalElapsed=62h15m15.550264864s committed per second=1644.564INFO[07/23,11:11:22 +09] [6] CheckStateConsistencyParallel is started root=0x06b082dd3a077dee8fa7d0016ea9e96f64d8d40f3332ecff44c779b35f60b893 len(children)=16INFO[07/23,11:11:32 +09] [6] CheckStateConsistencyParallel progress cnt=35793…

Completion of State Migration

Checking State Migration Completion Logs

Once the validation for the copied state tries is done, State Migration is officially complete. If you see logs printed as below, it means that the state migration has been successful.

State Migration Completion Log

INFO[07/25,09:20:12 +09] [6] CheckStateConsistencyParallel is done cnt=649698856 err=nilINFO[07/25,09:20:12 +09] [5] State migration is completed copyElapsed=62h15m15.550264864s checkElapsed=46h8m50.500767758s

Results: Checking Chaindata Data Size Reduction

The chaindata storage for Cypress, Klaytn’s mainnet, is as follows (as of August 2020):

> du -d1 -h651G ./statetrie
56G ./body
2.7G ./misc
240K ./bridgeservice
60G ./header
6.5G ./txlookup
40G ./receipts
815G .

After booting a new EN and performing State Migration for Cypress on this EN, the chaindata is reduced as shown below.

> du -d1 -h129G ./statetrie_migrated_29911450
61G ./body
3.0G ./misc
240K ./bridgeservice
69G ./header
7.3G ./txlookup
47G ./receipts
315G .

(The reason why the data for other directories has increased is because new blocks were added in the process of the Migration.)

Things to Keep in Mind During State Migration

Here are some things to keep in mind during State Migration:

Conclusion

As demonstrated above, State Migration greatly reduces data in the storage on a Klaytn node. But having to manually carry out State Migration may be a bit too demanding from the node operator’s perspective. That is why Klaytn Team is planning to keep on providing Cypress’ latest chaindata reduced by State Migration, which will be performed periodically to optimize storage usage. With this chaindata, a node operator won’t have to migrate state tries him or herself, and instead directly downloads and replaces the chaindata on his/her node. To download the latest snapshot of the chaindata, check out the following links: CypressBaobab.

We hope that this post is helpful in freeing up your storage size. Klaytn Team is always committed to making your Klaytn experience as convenient as possible. Thank you for reading and stay tuned for our next post!