지난 번엔 트러플(Truffle)을 이용해서 이더리움 네트워크에 NFT를 발행하는 방법을 소개했는데요. 이번엔 이더리움 대신 클레이튼(Klaytn) 네트워크에 NFT를 발행하는 방법을 다뤄 보겠습니다.
이더리움도 그렇지만, 클레이튼에서도 다양한 방법으로 스마트계약을 배포할 수 있습니다. 이더리움처럼 트러플을 사용할 수도 있고, Klaytn IDE나 클레이튼에서 제공하는 API 서비스인 KAS를 사용해도 되죠.
NFT 발행은 트러플로 하면 간단하지만, 트러플은 지난 번에 한번 사용했기에 이 글에서는 Klaytn SDK 속에 포함된 caver-js를 사용해 NFT 스마트계약을 한번 처리해 보기로 하겠습니다. 실제로 dApp을 개발하거나 Web3 관련 프로젝트를 해야할 경우라면 caver-js 같은 자바스크립트 라이브러리는 꼭 필요합니다.
Caver-js 설치하기
Caver-js는 HTTP 또는 웹소켓 연결을 통해 클레이튼 EN과 상호작용할 수 있도록 해주는 자바스크립트 라이브러리죠. 이더리움의 web3.js와 같은 역할이라고 보면 되는데요. 예전엔 web3.js 같은 인터페이스를 사용하다가 최근(v1.4.1이후) web3.js와는 다른 인터페이스로 변경되었죠.
caver-js는 다음과 같은 패키지들로 구성됩니다.

Caver-js를 사용하기 위해 프로젝트 디렉터리에 caver-js 패키지를 설치합니다. NFT 스마트계약을 발행하기 위해 @klaytn/contracts
패키지도 함께 설치합니다.
$ npm i caver-js @klaytn/contracts
NFT 계약 만들기
Caver-js 설치가 완료되었다면, 이제 NFT 발행을 위한 스마트계약을 하나 만들어 보겠습니다. 클레이튼에서 NFT는 KIP17 규약에 따른 스마트계약이며 이더리움의 ERC-721과 호환됩니다.
우선 contracts 디렉터리를 만들고 계약 파일을 하나 추가합니다.
$ mkdir -p contracts
proglang-nft$ touch contracts/ProgLangNFT.sol
여기서는 클레이튼에서 미리 만들어서 제공하는 KIP17Token 계약을 상속받아 사용하기로 하겠습니다.
// SPDX-License-Identifier: MIT
pragma solidity ^0.5.1;
import "@klaytn/contracts/token/KIP17/KIP17Token.sol";
contract ProgLangNFT is KIP17Token {
constructor() public KIP17Token("ProgLang NFT", "PROGLANG") {
}
}
계약은 프로그래밍 언어의 아이콘 파일들을 NFT로 만들어 발행하려고 합니다. NFT 이름은 “ProgLang NFT”라고 주었습니다.
계약을 작성했다면 이제 Solidity 컴파일러로 컴파일하면 됩니다. Solidity 컴파일러는 편한 것으로 사용하면 되지만, 여기서는 solcjs를 래핑한 @0x/sol-compiler를 사용했습니다.
$ npx sol-compiler
Pre-fetching solidity versions: 0.5.17...
Downloading soljson-v0.5.17+commit.d19bba13.js...
Compiling 24 contracts (ProgLangNFT.sol,KIP17Token.sol,KIP17Full.sol,KIP17.sol,IKIP17.sol,IKIP13.sol,IERC721Receiver.sol,IKIP17Receiver.sol,SafeMath.sol,Address.sol,Counters.sol,KIP13.sol,KIP17Enumerable.sol,IKIP17Enumerable.sol,KIP17Metadata.sol,IKIP17Metadata.sol,KIP17MetadataMintable.sol,MinterRole.sol,Roles.sol,KIP17Mintable.sol,KIP17Burnable.sol,KIP17Pausable.sol,Pausable.sol,PauserRole.sol) with Solidity 0.5.17+commit.d19bba13...
ProgLangNFT artifact saved!
컴파일이 완료되면 프로젝트 디렉터리 내 artifacts
하위 디렉터리 내에 컴파일된 결과물이 JSON 파일로 저장됩니다.
NFT 계약 배포
계약 컴파일이 완료되었으면 이제 배포할 차례입니다. 이제 caver-js를 쓸 때가 되었네요.
우선 임의의 디렉터리를 하나 만들고 그 속에 deploy.js
라는 파일을 하나 추가합니다. 이름은 뭐 아무거나 줘도 상관 없습니다. 그런 다음처럼 코드를 추가합니다.
// scripts/deploy.js
const fs = require('fs')
const Caver = require('caver-js')
const Artifact = require('../artifacts/ProgLangNFT.json')
async function deploy() {
const caver = new Caver('https://your.en.url:8651/')
// Add a keyring to caver.wallet
const privateKey = fs.readFileSync(".secret").toString().trim()
const deployer = caver.wallet.keyring.createFromPrivateKey(privateKey)
caver.wallet.add(deployer)
const gas = 150000000
const abi = Artifact.compilerOutput.abi
const data = Artifact.compilerOutput.evm.bytecode.object
const contract = caver.contract.create(abi)
const deployed = await contract.deploy({from: deployer.address, gas}, data)
console.log(deployed.options.address)
}
deploy()
이 코드를 잠깐 설명하자면, 우선 Klaytn EN의 URL을 입력하여 Caver 객체를 생성합니다. 그리고 개인키(private key)를 사용하여 키링(keyring)을 만들고 그 키링을 caver.wallet
에 추가합니다. 이렇게 추가한 키링은 나중에 계약을 발행할 때 사용됩니다.
// Add a keyring to caver.wallet
const privateKey = fs.readFileSync(".secret").toString().trim()
const deployer = caver.wallet.keyring.createFromPrivateKey(privateKey)
caver.wallet.add(deployer)
그런 다음 앞서 컴파일한 JSON 파일로부터 ABI 데이터를 불러와서 caver.contract.create
메서드로 계약 객체를 만듭니다.
const abi = Artifact.compilerOutput.abi
const data = Artifact.compilerOutput.evm.bytecode.object
const contract = caver.contract.create(abi)
마지막으로 생성된 계약 객체의 deploy
메서드를 호출하여 계약을 발행합니다. 이 때 키링 속에 들은 발행자 주소와 발행에 필요한 가스(gas) 값, 그리고 컴파일된 계약의 바이트코드(bytecode)를 data 값으로 전달합니다.
const deployed = await contract.deploy({from: deployer.address, gas}, data)
스크립트 코드를 실행해 보면 다음과 같이 계약이 발행되고, 발행된 계약의 주소(address) 값이 출력되는 것을 확인할 수 있습니다.
$ node scripts/deploy.js
0xB3F5d9713aE31D3b45bEA0e2b4519207f82A311e
제대로 발행되었는지 Klaytnscope에서 확인해 봅니다. 여기서는 테스트를 위해 바오밥(Baobab) 테스트넷에 배포했습니다.
https://baobab.scope.klaytn.com/account/0xb3f5d9713ae31d3b45bea0e2b4519207f82a311e?tabId=txList

NFT 발행하기
계약 배포가 정상적으로 완료되었으면, 이제 계약에 의해 NFT 토큰을 발행해 봅니다. 토큰 발행 역시 앞서 소개한 deploy.js
스크립트와 같은 방법으로 caver-js를 사용해서 처리하면 됩니다. 차이점은, 앞서는 계약의 deploy
메서드를 호출했다면, 이번엔 send
메서드를 호출한다는 점만 다를 뿐입니다.
const deployed = caver.contract.create(abi, contractAddress)
const receipt = await deployed.send({from: deployer.address, gas},
'mintWithTokenURI', toAddress, 1, `${baseURI}/1`)
앞선 코드와는 달리 이번엔 caver.contract.create
메서드의 두번 째 인자 값으로 앞서 발행한 계약의 주소값을 입력하였음에 유의하세요. 그리고 여기서는 NFT를 발행하기 위해 KIP17Token 계약 속의 mintWithTokenURI
메서드를 호출하고 있음에도 유의하세요. 물론 실제 프로젝트에서라면 이 발행(mint) 기능을 프로젝트 니즈에 맞춰 커스터마이징해서 사용하면 될 듯 하구요. mintWithTokenURI
메서드의 세 번째 인자는 토큰의 URI, 즉 토큰의 메타데이터에 대한 URI를 지정하면 됩니다.
그럼 스크립트를 한번 실행해 보겠습니다.
$ node scripts/mint.js
{
blockHash: '0xc9f9bbd69f64df82ac500079312422aea53da83d8a158c94edb1f42c0f8da89a',
blockNumber: 83587526,
contractAddress: null,
from: '0x4899970a72fd38c5e391d8013f98acb14afb93de',
gas: '0x8f0d180',
gasPrice: '0x5d21dba00',
gasUsed: 258674,
input: '0x50bb4e7f0000000000000000000000007eb6a920e40039ffb6ea45613347a5fb202e9d2d00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000005168747470733a2f2f7261772e67697468756275736572636f6e74656e742e636f6d2f736a6f6f6e6b2f6d792d6e66742f6d61737465722f6d657461646174612f70726f676c616e672f746f6b656e732f31000000000000000000000000000000',
logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000000200000000000000000000000040000000000000000000000000008000000000000000000040000000000000000000000000000020000000000000000000800000000000000000000000010000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000004000000002000000000000000000000000000000000000000000000000000060000000000000000000000000000000000002000000000000000000000000000000',
nonce: '0x1a',
senderTxHash: '0x072b0a83204a5aafdd9298cfe7cdbdae375072cde9c7a3e98dc0227c592a8fc2',
signatures: [
{
V: '0x7f6',
R: '0x29972e0b1961afa5a1017a8e2389abb0f9123e32ff0a6d3afc9eb1e16321843a',
S: '0x3d97887a51d8d5176f660730bf93ca7452206353241f537319749c7d0491f1f'
}
],
status: true,
to: '0xb3f5d9713ae31d3b45bea0e2b4519207f82a311e',
transactionHash: '0x072b0a83204a5aafdd9298cfe7cdbdae375072cde9c7a3e98dc0227c592a8fc2',
transactionIndex: 0,
type: 'TxTypeSmartContractExecution',
typeInt: 48,
value: '0x0',
events: {
Transfer: {
address: '0xB3F5d9713aE31D3b45bEA0e2b4519207f82A311e',
blockNumber: 83587526,
transactionHash: '0x072b0a83204a5aafdd9298cfe7cdbdae375072cde9c7a3e98dc0227c592a8fc2',
transactionIndex: 0,
blockHash: '0xc9f9bbd69f64df82ac500079312422aea53da83d8a158c94edb1f42c0f8da89a',
logIndex: 0,
id: 'log_9d104069',
returnValues: [Result],
event: 'Transfer',
signature: '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef',
raw: [Object]
}
}
}
짜잔!! 첫 번째 토큰이 발행되었네요!
역시 이번에도 Klaytnscope에서 한번 확인해 볼게요.
https://baobab.scope.klaytn.com/nft/0xb3f5d9713ae31d3b45bea0e2b4519207f82a311e?tabId=nftTransfer

NFT 계약 주소로 검색하면 오픈시에서도 확인할 수 있겠죠.
https://testnets.opensea.io/assets/baobab/0xb3f5d9713ae31d3b45bea0e2b4519207f82a311e/1

발행한 ProgLang NFT 컬렉션을 오픈시에서 확인해 보세요!
