본문 바로가기
etc/블록체인 뉴딜일자리사업

KAS와 node.js를 이용한 블록체인 지갑 기능 구현하기

by vellahw 2023. 6. 26.

 

💭 교육 36일차

팀플을 위한.. 토큰 발행 실습을 했다. KAS와 caver-js 모듈을 사용했고 caver-js를 이용하면 klaytn 노드와 상호작용할 수 있는데, require로 모듈들을 가져와 손쉽게 사용할 수 있는게 너무 신기하다. 노드 만세 ^^** 회원가입 기능을 만들어 DB에 저장하고, 로그인하면 유저의 지갑 정보를 가져오고, 토큰 충전/거래/이동 등의 기능을 만들어봤다. 다른건 다 괜찮은데 caver의 함수들을 이해하는게 조금 버거웠다. 하지만 공식 문서를 읽으며 이해하고자 노력했다!


 

 

 

✔️ KAS AccessKey 발급

카카오에서 컨트랙트를 쉽게 발행할 수 있게 해준 API인 KAS를 사용할 것이다. 사용하기 위해선 KAS 회원가입을 해야한다.

 

 

💡 KAS란 ?

더보기

KAS(Klaytn API Service)는 블록체인 플랫폼인 Klaytn(클레이튼)을 API로 사용할 수 있는 서비스이다. KAS를 사용하면 기존 Klaytn을 사용했을 때보다 개발의 용이성, 생산성, 효율성을 높여서 블록체인 애플리케이션을 개발할 수 있다.

일반적으로 클레이튼 혹은 이더리움에 트랜잭션을 보내려면 비싼 비용을 지불하고 고성능의 노드를 준비해 운영해야 했다. 또한 여러 계정을 만들고 이들의 개인키-공개키 쌍을 일일이 관리해야 했다.

하지만, KAS를 사용하면 노드를 설치, 운영하거나 계정 키를 관리할 필요가 없고 트랜잭션 전송과 관련된 모든 작업을 REST API 혹은 SDK로 실행할 수 있다.. 고 한다. (출처)

 

=> 06/30 추가) KAS가 제공하는 함수들을 하나하나 뜯어보며 함수들의 기능에 대해선 이해했지만 "그래서 왜?" 써야 하는지에 대해선 명확히 이해가 되지 않았었다. 내가 만든 내 카이카스 지갑의 개인키 넣으며 실습을 해서, KAS를 이용해서 카이카스 지갑을 연동할 수 있는거라고 생각했는데 그게 아니다! 강사님께서 해주신 말을 이해하고 정리해서 팀원분들께 공유한 사진을 첨부한다. 😋

 

결론은 KAS가 제공해주는 함수들을 사용하면 지갑을 만들 수 있고, 토큰을 충전할 수 있고, 토큰의 잔여량도 확인할 수 있기 때문에 굳이 카이카스 같은 지갑을 연동할 필요가 전혀 없다는 것이다. 가상의 지갑을 만들어 관리할 수 있는 것! 강사님 말씀으로는 KAS가 수수료 대납도 해준다고 한다!

 

 

회원가입 후 AccessKey를 생성해야한다. KAS 이용에 필요한 키들을 발급받는 것이다.

왼쪽 Security-Credential 메뉴로 들어간 후 우측 AccessKey 생성 버튼을 누른다.

 

AccessKey 다운로드 버튼을 클릭하면 json 파일이 내려받아진다.

사진에서 보이듯 시크릿 엑세스 키는 따로 복구할 수 없으니 별도로 복사하거나 다운로드 받아두어야 한다!

 

 

 

✔️ KAS 불러오기

이제 노드를 이용해 연결해보자.

폴더를 하나 생성해서 VSCode에서 열어주었다.

폴더 안에 내려받은 json 파일을 넣어주고, kip7.js 파일을 하나 생성해주었다. 이 파일에 KAS의 함수들을 사용한 토큰, 지갑 관련 함수를 작성할 것이다.

 

// caver-js-ext-kas 모듈 로드
// : 클레이튼에 있는 컨트랙트 가져오기
const CaverExtKAS = require('caver-js-ext-kas')
// class 생성
const caver = new CaverExtKAS()
// fs 모듈 로드
const fs = require('fs')
// dotenv 로드 (환경변수 설정)
require('dotenv').config()

// KAS에 접속하기 위한 ID, PASSWORD 파일을 로드 
const kas_info = require('./kas.json')
console.log(kas_info)

kip7.js 파일에 위와 같이 작성했다.

 

클레이튼에 있는 컨트랙트를 가져오기 위해 caver-js-ext-kas 모듈을 로드했다.

fs 모듈은 파일 처리와 관련된 전반적인 작업을 하기 위해 로드해주었다.

지갑을 연결해야 하기 때문에 프라이빗 키 같은 개인 정보를 감추기 위해 dotenv를 로드했다.

 

KAS에 접속하기 위해 내려받기 한 kas.json 파일을 kas_info 라는 이름으로 로드해주었다.

 

//accesskeyID, secretAccessKey 저장
const accesskeyID = kas_info.accessKeyId
const secretAccessKey = kas_info.secretAccessKey

// testnet의 chainid 지정
const chainid = 1001

// 생성자 함수 호출 
caver.initKASAPI(chainid, accesskeyID, secretAccessKey)

그리고 이어서, kas_info라는 이름으로 kas.json 파일에 접근하여 accesskeyID(인증 아이디)와 secretAccessKey(인증 패스워드)를 변수로 저장해주었다.

 

KAS API를 사용하기 위해서는 사용할 chain id인증키를 세팅해야한다. 

Baobab(Klaytn 테스트넷) 테스트넷을 사용하기 때문에 체인ID는 1001로 지정했고, 인증키로는 accesskeyID와 secretAccessKey가 필요하다.

caver.initKASAPI 함수를 사용하면 Node API, Wallet API, Token History API, Anchor API, KIP-17 API, KIP-7 API, 그리고 KIP-37 API에서 사용되는 인증키를 한 번에 초기화한다.. 고 함 (출처)

 

 

✔️ 외부 지갑 연결하기

이제 외부 지갑인 메타 마스크를 연결할 것인데 개인키가 필요하다. 

(🔻 개인키 찾기)

 

 

개인키를 복사해서 env 파일에 키와 값으로 등록해준다. private_key 라는 이름으로 등록해주었다.

 

// KAS에서 외부 지갑을 사용하기 위해서는 지갑을 등록해주어야 한다.
const keyringContainer = new caver.keyringContainer()
const keyring = keyringContainer.keyring.createFromPrivateKey(process.env.private_key)

keyringContainer.add(keyring)

kip7.js에 이어서 작성한다.

KAS의 keyringContainer를 이용하면 지갑의 개인키를 이용해 해당 지갑(계정)으로 접근할 수 있는 것 같다. (참고)

keyring 변수에는 연결할 지갑 주소 + 개인키가 저장된다

 

 

✔️ 토큰 생성 함수 작성

// 토큰 생성 함수 (이름, 심볼, 소수점, 만들 양)
async function create_token(_name, _symbol, _decimal, _amount) {
    // kas에서 만든 kip7 배포
    const kip7 = await caver.kct.kip7.deploy(
        {
            name : _name,
            symbol : _symbol,
            decimals : _decimal,
            initialSupply : _amount
        },
        // 수수료 낼 지갑 주소
        keyring.address,
        keyringContainer
    )

    const addr = kip7._address
    console.log("-> addr: ", addr)
    
    return '토큰 발행 완료'
}

kip7.js에 이어 토큰 생성 함수를 작성했다.

일반적으로 토큰을 생성할 때 필요한 정보는 토큰의 이름과 심볼, 토큰의 소수점 자리, 공급될 양이 필요하기 때문에 매개변수로 넣어줬다.

 

async와 await는 자바스크립트의 비동기 처리 패턴 중 가장 최근에 나온 문법으로, await가 붙은 함수는 해당 동작이 완료되기 전까지 다음 동작으로 넘어가지 않는다.

 

클레이튼 문서에 따르면 caver.kct.kip7는 KIP-7을 구현하는 스마트 컨트랙트를 클레이튼 블록체인 플랫폼에서 쉽게 다룰 수 있도록 도와준다.

caver.kct.kip7.deploy()의 매개변수로는 토큰의 정보와 컨트랙트를 배포하는 주소가 들어가기 때문에 토큰의 이름과 심볼, 소수점 자리, 공급량을 정의해주었고,  keyring을 이용해 내 지갑의 주소와 계정 자체(? keyringContainer) 넣어주었다.

 

kip7._address는 토큰 발행 주소가 담겨있다. 토큰 발행 주소를 변수 addr에 저장한다.

// 함수 호출
create_token('test', 'TST', 0, 1000000)

함수를 만들었으니 호출해보았다.

js 파일을 실행하고 오류 없이 addr이 찍혀나오면 정상적으로 된 것!

 

콘솔로 찍힌 addr 즉, 토큰 컨트랙트 주소를 이용하여 아래 방법에 따르면 토큰을 추가할 수 있다.

직접 입력한 심볼과 소숫점 자리, 최초 공급량이 뜨는 것을 확인할 수 있다!

아래에서 이 TST 토큰을 사용해 토큰을 주고 받는 테스트를 할 것이기 때문에 토큰을 추가해주자.

 

💡 주소값 json 파일로 저장하기

위에서 작성한 토큰 생성 함수(create_token)의 16번째 줄에 토큰 컨트랙트 주소를 변수로 담은 부분이 있다.

앞으로 토큰 거래 함수, 지갑 거래 함수 등을 작성할건데 이때마다 주소를 일일이 콘솔에서 찍어 확인해 복사, 붙여넣기하는건 비효율적이다.

따라서 fs 모듈을 사용하여 json 파일로 주소를 저장해줄 것이다.

 

create_token 함수의 retrun문 바로 위에 아래 코드를 추가한다.

// 토큰의 주소값을 json 파일 안에 대입
const kip7_address = {
    address : addr
}
//json을 문자열로 변환
const data = JSON.stringify(kip7_address)
//json 파일의 형태로 저장 (이름, 데이터)
fs.writeFileSync('./kip7.json', data)

kip7_address라는 이름으로 변수를 생성하여 address라는 키로 연동된 지갑 주소(addr)을 저장한다.

JSON.stringify() 함수를 이용해 주소를 JSON 문자열로 변환하여 data 라는 변수로 저장해주었고, fs 모듈의 writeFileSync를 이용해 저장될 파일을 지정해주었다. 동기로 파일에 데이터를 쓸 수 있는 메서드이다. 

 

kip7.json 파일을 생성하여 키로 address를 입력하고 값으로 토큰의 주소 값을 넣어준다.
이 작업을 통해 kip7.json 파일을 로드하면 address : 토큰 발행 주소 데이터를 불러올 수 있다.

 

 

 

✔️ 토큰 거래하기

서로 다른 계정에서 토큰을 주고 받는 기능을 위해 지갑을 새로 생성했다.

카이카스 상단의 계정관리 아이콘-생성 버튼을 눌러 이름을 입력하고 생성하면 된다.

 

// 토큰을 거래하는 함수 선언 
async function transfer(_address, _amount) {
    // 발행한 토큰을 지갑에 추가
    const token_info = require('./kip7.json')
    const kip7 = await new caver.kct.kip7(token_info.address)
    kip7.setWallet(keyringContainer)

    const receipt = await kip7.transfer(
        _address,
        _amount, 
        {
            from : keyring.address
        }
    )

    console.log("-> receipt: ", receipt)
    return '토큰 거래 완료'
}

create_token 함수 아래에 토큰 거래 함수를 작성했다. 

토큰 거래에는 토큰을 받을 주소와 거래할 토큰의 양이 필요하기 때문에 매개변수로 넣어줬다.

require를 이용해 지갑 개인키가 등록된 kip7.json을 로드해서 token_info 라는 변수로 저장해주었다.

setWallet 함수는 컨트랙트를 배포하거나 실행할 때 사용하는 지갑을 지정할 수 있는 함수다. keyringContainer를 지갑으로 설정해준다.

kip7.transfer 안에 토큰을 받을 주소와 보낼 양, 보내는 지갑 주소(keyring.address)를 넣어주고 그 결과를 receipt 변수 안에 담는다.

 

// 새로 생성한 지갑 주소 넣기
transfer('0x739563DD6d5a8C0419775F83A8066B347C2da97b', 50))

transfer 함수에 매개변수로 새로 생성한 지갑 주소(토큰을 보낼 지갑 주소)와 보낼 양을 넣어주고 함수를 호출하도록 한 뒤, 파일을 실행시켜보자.

 

파일을 실행하면 transfer 함수는 receipt를 콘솔로 찍어주도록 했기 때문에 receipt가 찍혀나오면 정상 작동한 것이다.

 

새로 생성한 계정(토큰을 받은 계정)에서 토큰 목록 버튼을 눌러 토큰 발행 주소(kip7.json에 저장해둔 주소)를 입력해 토큰을 추가해주면 transfer 함수를 통해 이동된 토큰이 추가된다! 기존 계정은 보낸만큼 토큰 수가 줄었을 것이다.

 

 

 

✔️ 토큰 이동하기

유저가 토큰 발행자에게 토큰을 보내주는 함수를 작성할 것이다. (transaction의 주체자가 발행자인 것) 프라이빗 키를 이용하기 때문에 실제론 잘 안 쓸 것이라고 함?!

async function transfer_from(_private, _amount) {
     // 발행한 토큰을 지갑에 추가
     const token_info = require('./kip7.json')
     const kip7 = await new caver.kct.kip7(token_info.address)
     kip7.setWallet(keyringContainer)
 
     // 토큰 발행자의 지갑 주소
     const owner = keyring.address
     console.log("-> owner: ", owner)
 
     // 유저의 지갑 주소를 keyringContainerd에 등록
     const keyring2 = keyringContainer.keyring.createFromPrivateKey(
         _private
     )

     keyringContainer.add(keyring2)

     // 내 지갑에 있는 일정 토큰을 다른 사람이 이동 시킬 수 있도록 권한 부여
     // approve(권한을 받을 지갑의 주소, 토큰의 양, from)
     await kip7.approve(owner, _amount,{
            from : keyring2.address
        })

     const receipt = await kip7.transferFrom(
        keyring2.address,
        owner,
        _amount,
        {
            from : owner
        }
     )

     console.log('-> receipt: ', receipt)
     return "토큰 이동 완료"
}

transfer_from 함수의 매개변수로는 보내는 사람의 개인키와 보낼 양이 필요하다.

세번째 줄까지는 위에서 작성한 transfer 함수와 같고, 발행한 토큰을 지갑에 저장해준다.

keyring.address로 토큰 발행자의 주소에 접근하여 owner 변수에 저장해준다.

 

keyringContainer.keyring.createFromPrivateKey() 함수의 매개변수에 transfer_from 함수의 매개변수에 입력될 프라이빗 키를 대입하여 유저의 지갑 주소를 변수 keyring2로 저장하고, keyringContainer에 저장해준다. ( keyringContainer.add(keyring2) )

 

kip7.approve 함수를 통해 내 지갑에 있는 일정 토큰을 다른 사람이 이동 시킬 수 있는 권한을 부여할 수 있다.

매개변수로 권한을 받을 지갑 주소(owner: 토큰 발행자), 토큰의 양, 토큰을 보내는 지갑 주소(keyring2.address: 유저)를 넣어주면 된다.

 

kip7.transferFrom 함수를 통해 토큰이 이동될 것이다. 매개변수로는 토큰을 보내는 지갑 주소, 토큰 발행자의 주소, 보낼 토큰의 양, 토큰을 받는 지갑 주소를 입력한다. 그리고 이 kip7.transferFrom 함수의 결과값이 receipt 변수에 담긴다.

 

// 두번째 지갑 프라이빗키 
transfer_from('프라이빗키 입력', 5)

함수를 호출해주고 kip7.js 를 실행해보자/

 

실행하면 사진과 같이 owner 주소값은 콘솔에 찍히는데 reciept를 실행하는 과정에서 오류가 발생한다. 이게 테스트상에선 당연히 발생하는 문제라고...? 말해주셨는데 사실 제대로 못들었다.... 강사님이 올려주신 코드로 돌려봐도 똑같은 오류가 발생한다.... 

 

 

 

✔️ 토큰 양 확인하기

// 토큰 양 확인
async function balance_of(_address) {
    const token_info = require('./kip7.json')
    const kip7 = await new caver.kct.kip7(token_info.address)
    kip7.setWallet(keyringContainer)
 
    const balance = await kip7.balanceOf(_address)

    console.log("-> balance: ", balance)
    return balance
}

특정 지갑 주소가 가지고 있는 토큰의 양을 확인하는 함수를 작성한다.

kip7.balanceOf 함수에 매개변수로 주소값을 넣으면 해당 지갑의 토큰 양을 확인할 수 있다.

 

 

 

✔️ 지갑 생성하기

// 지갑을 생성해주는 함수
async function create_wallet(){
    const wallet = await caver.kas.wallet.createAccount()
    console.log("-> 생성된 지갑: ", wallet)
    return wallet.address // 지갑에 있는 주소 리턴
}

create_wallet() // 함수 호출

caver.kas.wallet.createAccount() 함수를 이용하면 지갑을 생성할 수 있다.

 

함수를 호출하면 위와 같은 키와 값을 가진 지갑이 생성된다.

create_wallet 함수는 해당 지갑이 가지고 있는 주소를 리턴해준다.

 

 

✔️ 외부에서 함수 호출 할 수 있도록 하기

KAS를 이용해 필요한 함수들은 kip7.js에 모두 작성하였다. 다음으로 로그인, 마이페이지 등의 기능을 만들고 할건데 kip7.js 한 파일에 모든 기능들을 다 넣을 수 없다. 

따라서 라우팅을 사용하여 '모듈화'를 해줄 것! 

 

// 외부에서 함수 호출 할 수 있도록 하기
module.exports = {
    create_token, transfer, transfer_from, balance_of
}

위처럼 module.exports =  { 함수명, 함수명, ... } 를 작성하면 작성한 함수들을 다른 파일들에서 가져다 쓸 수 있게 된다.

 

만든 함수들을 연결하여 토큰 발행, 로그인 등을 구현하고 기능에 따라 라우팅 하는 법은 다음에 이어 포스팅 하겠다!

 

댓글