Common Architecture of Caver

Tech at Klaytn
Klaytn
Published in
10 min readMar 2, 2021

See the list of articles here.

🇰🇷: Caver의 Common Architecture 알아보기

Caver is a Software Development Kit (SDK) that helps you use Klaytn more easily, recently launched by Caver on v1.5.0. In this post, we will introduce the definition, motivation, and usage of Common Architecture.

You can find further details here: KIP-34 Klaytn SDK Common Architecture

1. What is Common Architecture?

Let us first define Common Architecture. Common Architecture is a new software architecture for Klaytn’s development environment, shared by all Klaytn SDKs.

2. Why Do We Need Common Architecture?

Caver is currently provided in two languages: Javascript (caver-js) and Java (caver-java). And there have been some difficulties before the Common Architecture was introduced:

Structure/Terminology Inconsistency
Due to the lack of a common structure on Caver, the implementation structures in caver-js and caver-java have been inconsistent. The inconsistencies in the terminologies have also hampered communication.

Usability
The differences in implemented structures and terminologies also led to inconsistent usability.

Language scalability
The inconsistent usability of Caver meant that a developer familiar with caver-js would need to invest time and effort to use caver-java. This has hindered BApp development using multiple languages.

Caver attempted to mitigate these problems by introducing the Common Architecture in the hopes of facilitating multi-language programming with Caver.

3. Structure of Common Architecture

Let us now take a look at the structure of the Common Architecture. Below is a diagram for the Layers of the Common Architecture.

KCT Layer

The KCT Layer allows you to deploy and execute of KCT (Klaytn Compatible Token) contracts easily. The KCT layer provides features related to the KIP-7 fungible token standard and the KIP-17 non fungible token standard.

Contract Layer

The Contract Layer helps you use smart contracts more conveniently. You can deploy and execute smart contracts on Klaytn with the features provided by this layer using smart contract’s ABI (Application Binary Interface) and bytecode.

ABI Layer

The ABI Layer allows you to encode/decode using the smart contract’s ABI. It contains the essential functionalities for using smart contracts and is also being used internally by the Contract Layer.

Transaction Layer

The Transaction Layer supports various transactions types of Klaytn implemented as separate classes. You can also find transaction hash, or RLP encode/decode.

Wallet Layer

The Wallet Layer provides features that use Klaytn Account on Caver. On the Wallet Layer, users are given a Keyring, which is a structure for storing Klaytn account addresses and private keys on Caver, along with a KeyringContainer with an in-memory wallet to manage the Keyring.

Account Layer

The Account Layer provides data structures and functions that are required for updating Klaytn Account’s AccountKey. Account is used for Account Update Transaction (AccountUpdate/FeeDelegatedAccountUpdate/FeeDelegatedAccountUpdateWithRatio), and contains the Klaytn Account address to be updated, and the new AccountKey.

RPC Layer

The RPC Layer enables you to easily communicate with Klaytn nodes via RPC.

Utils Layer

On the Utils Layer, you can find the utility functions needed for developing BApps.

This was a brief description about the Common Architecture of Caver. Please visit KIP-34 For more details. We will now take a look at how to use Caver and the Common Architecture with some examples.

4. How to send KLAY using Caver

Let’s first take a look at how to send KLAY using Caver. It follows these steps:

Create a keyring → add a keyring to the in-memory wallet → create a transaction → sign the transaction → send it to the Klaytn Network

Let’s go over each step in more detail with code snippets.

4.1. Creating a Keyring

First, the keyring for sending KLAY has to be generated. Keep in mind that you need to have sufficient KLAY balance on your Klaytn Account for this step. You can obtain Testnet KLAY on Baobab Network: Baobab Klaytn Wallet.

Caver-js creates a keyring for decrypting keystore files via caver.wallet.keyring.decrypt as shown below:

// keyring creation — caver-jsconst keystore = fs.readFileSync('keystore.json', 'utf8')
const senderKeyring = caver.wallet.keyring.decrypt(keystore, 'password')

Caver-java also creates a keyring through the same process:

// creating keyring — caver-javaFile file = new File('./keystore.json');
ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
KeyStore keystore = objectMapper.readValue(file, KeyStore.class);
AbstractKeyring senderKeyring = caver.wallet.keyring.decrypt(keystore, "password");

4.2. Adding a Keyring to the In-Memory Wallet

The keyring created in the previous step will be added to the in-memory wallet.

To add the keyring to the in-memory wallet using caver-js, use caver.wallet.add:

// adding keyring to in-memory wallet — caver-jscaver.wallet.add(senderKeyring)

caver.wallet.add can also be used with caver-java for the same purpose:

// adding keyring to in-memory wallet — caver-javacaver.wallet.add(senderkeyring);

4.3. Creating a Transaction

Now a transaction can be created to actually send KLAY. In this example, a ValueTransfer transaction will be employed.

Using caver-js to create a ValueTransfer transaction would look like this:

// creating transaction — caver-jsconst vt = caver.transaction.valueTransfer.create({
from: senderKeyring.address,
to: recipientAddress,
value: caver.utils.convertToPeb(1, caver.utils.klayUnit.KLAY.unit),
gas: 25000,
})

Below is how you can send KLAY with a ValueTransfer transaction using caver-java.

Using caver-java, you can define the parameters for the constructor when creating a transaction, but this will lead to too many parameters and many parameters are optional. A builder pattern is provided for convenience. The code below shows you how to create a ValueTransfer transaction using the builder pattern:

// creating transaction — caver-javaValueTransfer vt = caver.transaction.valueTransfer.create(
TxPropertyBuilder.valueTransfer()
.setFrom(senderKeyring.getAddress())
.setTo(recipientAddress)
.setValue(new BigInteger(
caver.utils.convertToPeb(
BigDecimal.valueOf(1),
Utils.KlayUnit.KLAY ) ))
.setGas(BigInteger.valueOf(25000))
);

4.4. Signing the Transaction

Now the created transaction will be signed using the keyring. The keyring has already been added to the in-memory wallet, so we will now demonstrate how to sign using this wallet.

Signing a transaction using caver-js looks like this:

// sign transaction — caver-jsawait caver.wallet.sign(senderKeyring.address, vt)

And with caver-java:

// signing transaction — caver-javacaver.wallet.sign(senderKeyring.getAddress(), vt);

As shown above, when you call the sign function of the in-memory wallet, it will parse the first parameter and get keyring of the address to sign the transaction with the private key of that keyring. The result is saved in signatures within the transaction.

4.5. Sending to Klaytn Network

Now we are ready to send the signed transaction to the Klaytn network. The transaction will be sent to Klaytn Node using the RPC Layer.

Below is how you send a transaction using caver-js. When using caver-js to send transactions, you can handle the outcome in two different ways. First is using promise, where a transaction receipt can be returned immediately. And another way is using EventEmitter, where you can handle events related to transactionHash, receipt and error.

// sending transaction and retrieving receipt — caver-js// Using Promise - use await or then
const receipt = await caver.rpc.sendRawTransaction(vt)// Using event emitter
caver.rpc.klay.sendRawTransaction(vt)
.on('transactionHash', (hash) => {console.log(hash)})
.on('receipt', (r) => {console.log(r)})

If you are using caver-java:

// sending transaction and retrieving receipt — caver-javaBytes32 sendResult = caver.rpc.klay.sendRawTransaction(vt).send();
String txHash = sendResult.getResult();
TransactionReceiptProcessor receiptProcessor = new PollingTransactionReceiptProcessor(caver, 1000, 15);
TransactionReceipt.TransactionReceiptData receiptData = receiptProcessor.waitForTransactionReceipt(txHash)

After caver.rpc.klay.sendRawTransaction is complete, transaction hash will be returned. With caver-java you can track the status of the transaction with the hash using TransactionReceiptProcessor. Once the transaction goes through, a KLAY will then be transferred.

5. Creating Your Own Token with Caver

In this section we will show you how to create and transfer KIP-7 tokens using Caver. All you need to do is to follow these steps:

creating a keyring → adding the keyring to the in-memory wallet → deploying a KIP-7 contract → calling the contract function of the deployed KIP-7 contract

You can deploy a token contract on the network and execute that contract using the KIP-7 instance, returned as a result of the deployment. We will now walk you through the process below.

5.1. Creating a keyring and adding it to the in-memory wallet

In order to create a token, you need to generate a keyring and add it to the in-memory wallet. Since this process is the same as 4.1–2, we will skip over the details.

Below is the code for creating a keyring and adding it to the in-memory wallet using caver-js:

// creating a keyring and adding it to in-memory wallet — caver-jsconst keystore = fs.readFileSync('keystore.json', 'utf8')
const deployerKeyring = caver.wallet.keyring.decrypt(keystore, 'password')
caver.wallet.add(deployerKeyring)

The same process using caver-java:

// creating keyring and adding it to in-memory wallet — caver-javaFile file = new File('./keystore.json');
ObjectMapper objectMapper = ObjectMapperFactory.getObjectMapper();
KeyStore keystore = objectMapper.readValue(file, KeyStore.class);
AbstractKeyring deployerKeyring = caver.wallet.keyring.decrypt(keystore, "password");
caver.wallet.add(deployerKeyring);

5.2. Deploying a KIP-7 contract

To deploy a KIP-7 token contract, call the deploy method of the KIP7 class provided by Caver’s KCT Layer. The parameters to pass are the necessary information for deploying the token contract, and the keyring address.

Here is how you call the deploy function using caver-js:

// KIP-7 token deployment — caver-jsconst initialSupply = new BigNumber('1000000000000000000')
const params = { name: 'TestToken', symbol: 'TTK', decimals: 18, initialSupply }
const kip7 = await caver.kct.kip7.deploy(params, deployerKeyring.address)

And with caver-java:

// KIP-7 token deployment — caver-javaBigInteger initialSupply = new BigInteger("1000000000000000000");
KIPDeployParams params = new KIP7DeployParams("TestToken", "TTK", 18, initialSupply);
KIP7 kip7 = caver.kip7.deploy(caver, params, deplyerKeyring.getAddress());

This is what goes on inside when the KIP-7 deploy function is called to create a token contract:

First, a SmartContractDeploy transaction is created using the parameter passed by the deploy method. Then the transaction will be signed via the keyring stored in the in-memory wallet, and the signed transaction will be sent to Klaytn Network to deploy the KIP-7 token contract. The KIP-7 instance will then be returned, containing the deployed addresses. You can use them to run contracts deployed on the network.

5.3. Calling the Deployed KIP-7 Contract Function

We will continue from the point where the KIP-7 instance was returned as a result of deploying the token contract. To execute the contract, you have to call the KIP-7 instance method. Depending on the type, smart contract functions mainly work in two ways.

5.3.1. Functions that retrieve contract data

A function does not create any transactions internally when it simply retrieves the contract state without changing it. In this example we will use a function that returns “name”, which is one of the metadata of KIP-7 token contract.

Here is how you call the name function using caver-js:

// retrieving the name of the KIP-7 token contract- caver-jsconst name = await kip7.name()

And with caver-java:

// retrieving the name of the KIP-7 token contract — caver-javaString name = kip7.name();

Following the simple steps above lets you retrieve the name of an actual KIP-7 token contract.

5.3.2. Functions that change the contract state

A function that does make changes to the contract state can also be executed by calling the KIP7 method. However, the process is actually a bit more complicated since the transaction for modifying contract state must also be transmitted to the network. When the user calls this method, a SmartContractExecution transaction is created on the basis of the passed parameters, and the transaction is signed using the keyring stored in the in-memory wallet and then sent to the Klaytn Network.

We will demonstrate this by using the transfer function for sending tokens.

Here is how you call the transfer function using caver-js:

// Sending KIP-7 token — caver-jsconst opts = { from: deployerKeyring.address }
const recipientAddress = '0x5b9e34df6b9bf6ced39c02874e041ffd4bef851d'
const value = 1
const receipt = await kip7.transfer(recipientAddress, value, opts)

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

// Sending KIP-7 token — caver-javaSendOptions opts = new SendOptions();
opts.setFrom(deployerKeyring.getAddress());
String recipientAddress = "0x5b9e34df6b9bf6ced39c02874e041ffd4bef851d"
BigInteger value = BigInteger.ONE
TransactionReceipt.TransactionReceiptData receiptData = kip7.transfer(recipientAddress, value, opts);

With both caver-js and caver-java, you will have to pass the necessary data for creating transactions as the last parameter. kip7.transfer uses opts.from to the set sender of the SmartContractExecution transaction. After the transaction is complete, a receipt containing the results will be returned.

6. Conclusion

In this post we looked at the definition of, and the motivation for developing the Common Architecture with some examples. With the introduction of the Common Architecture, multi-language programming with Caver has been given much more consistency. We expect that experienced users of caver-js and caver-java will be able to use Caver in other languages as well.

You can find the entire code for the tutorial linked below:

Sending KLAY (ValueTransfer transaction)

Creating tokens

We highly encourage you to experience Caver’s Common Architecture yourself.

For any questions or comments, visit Klaytn’s Developer Forum.

Thank you for reading!

--

--