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);
  }
}