[Caver-js] 시작하기

Caver-js

caver-js는 개발자가 HTTP 또는 웹소켓 연결을 사용하여 Klaytn 노드와 상호작용할 수 있도록하는 자바스크립트 API 라이브러리

주요기능

  • HTTP 및 웹소켓을 통한 Klaytn의 JSON-RPC 클라이언트 API의 완전한 구현
  • Klaytn 트랜잭션, 계정 및 계정 키 유형 지원
  • Klaytn 네트워크에서 스마트 컨트랙트를 배포하고 실행하기위한 자바스크립트 스마트 컨트랙트 패키지
  • Klaytn 계정 관리를 위한 인메모리 지갑
  • 수수료 위임 지원
  • Klaytn Wallet 키 형식 지원
  • RLP에서 트랜잭션 오브젝트의 인코딩/디코딩
  • 트랜잭션 객체의 서명
  • web3-js 애플리케이션을 caver-js로 쉽게 포팅

Klaytn에 트랜잭션을 보낼 때 주의사항

Klaytn은 고정된 가스 가격 (25 ston = 25 * 10^9)을 사용합니다. 만일 다른 가스 가격의 트랜잭션이 Klaytn 네트워크에 제출되면 트랜잭션은 거절됩니다.

준비사항

설치

빠른 시작: KLAY 전송하기

keystore file을 사용해 KLAY를 전송하는 간단한 KLAY 전송 트랜잭션 예시

const fs = require('fs')
const Caver = require('caver-js')
const caver = new Caver('https://api.baobab.klaytn.net:8651/')

async function testFunction() {
    // Read keystore json file
    const keystore = fs.readFileSync('./keystore.json', 'utf8')

    // Decrypt keystore
    const keyring = caver.wallet.keyring.decrypt(keystore, 'password')
    console.log(keyring)

    // Add to caver.wallet
    caver.wallet.add(keyring)

    // Create value transfer transaction
    const vt = new caver.transaction.valueTransfer({
        from: keyring.address,
        to: '0xb4c25d57583bc3103d01459105a94d53c6f62393',
        value: caver.utils.toPeb(1, 'KLAY'),
        gas: 25000,
    })

    // Sign to the transaction
    const signed = await caver.wallet.sign(keyring.address, vt)

    // Send transaction to the Klaytn blockchain platform (Klaytn)
    const receipt = await caver.rpc.klay.sendRawTransaction(signed)
    console.log(receipt)
}

testFunction()

이 트랜잭션 확인하기
실제 보낸 계정의 잔액은 줄어들었고, 받는 계정은 잔액이 늘어난 것을 확인할 수 있었음 (1번계정에서 2번계정으로 보냈음)

caver-js 시작하기

Klaytn 노드에 접속하기

const Caver = require('caver-js')
const caver = new Caver('url~') // url에 baoban테스트넷의 klaytn노드, 혹은 호스트와 포트를 변경해 각자의 노드에 연결 가능(EN 실행 중인 경우)

Keyring 관리

Keyring은 Klaytn 계정의 주소와 private Key(s)가 포함된 구조

1. SingleKeyring: 내부에 key속성을 정의하고, 하나의 private key를 저장한다.
2. MultipleKeyring: 내부에 keys속성을 정의하고, 여러개의 private key를 담은 배열로 구현된다.
3. RoleBasedKeyring: 이 type의 keys속성은 2차원 배열([ [], [], [] ])로 구현된다.
   배열의 첫번째 요소는 roleTransactionKey, 두번째 요소는 roleAccountUpdateKey, 세번째 요소는 roleFeePayerKey.

Keyring 생성

// test.js
const Caver = require('caver-js')
const caver = new Caver('https://api.baobab.klaytn.net:8651/')

async function testFunction() {
    const keyring = caver.wallet.keyring.generate() // *
    console.log(keyring)
}
testFunction()


testFunction()의 * 부분 내용을 바꿔서 가지고 있는 개인키를 사용해 Keyring을 만들 수 있다.

지갑키를 사용하거나 주소와 개인키를 사용해서 Keyring을 만들 수 있다.
caver.wallet.keyring.createWithMultipleKey('주소', ['개인키1', '개인키2', ...]) 이런 식으로 여러 개인키로 MultipleKeyring을 생성할 수도 있다.
caver.wallet.keyring.createWithRoleBasedKey()로 RoleBasedKeyring도 생성 가능
여기에 주소 뒤에 입력되는 key배열에서

1. 첫번째 원소(rolTransactionKey): PrivateKey 3개
2. 두번째 원소(roleAccountUpdateKey): PrivateKey 1개
3. 세번째 원소(roleFeePayerKey): PrivateKey 2개

caver-js에 Keyring 추가하기

caver.wallet.add(keyring) // Keyring인스턴스를 사용해 caver-js의 인메모리 지갑에 keyring 추가
//caver.wallet.newKeyring 또는 caver.wallet.add는 Keyring 인스턴스를 반환
 
// keystore file (JSON파일)을 사용해서 추가할 수도 있다.
const decrypted = caver.wallet.keyring.decrypt({
	// 여기에 keystore파일의 내용 추가
}, 'password')

caver.wallet에 Keyring을 추가하면 caver.wallet에서 Keyring을 조회할 수 있다. MultipleKeyring, RoleBasedKeyring 모두 가능

트랜잭션 전송

Baobab 네트워크에서 caver-js를 사용하여 KLAY를 보내는 방법

송금 트랜잭션 전송

트랜잭션을 네트워크에 보내려면 2단계를 거쳐야한다.

1) 트랜잭션 서명하기(caver-js 지갑을 통해 서명 가능)
트랜잭션을 Klaytn에 보내기 전에 트랜잭션에 서명을 먼저 해야한다.

// test.js
const Caver = require('caver-js')
const caver = new Caver('https://api.baobab.klaytn.net:8651/')

async function testFunction() {
    // Add a keyring to caver.wallet
    const keyring = caver.wallet.keyring.createFromPrivateKey('0x{private key}')
    caver.wallet.add(keyring)
    // PrivateKey로 keyring을 생성하고 caver-js지갑에 추가

    // Create a value transfer transaction
    const valueTransfer = new caver.transaction.valueTransfer({
        from: keyring.address,
        to: '0x176ff0344de49c04be577a3512b6991507647f72',
        value: 1,
        gas: 30000,
    })

    // Sign the transaction via caver.wallet.sign
    await caver.wallet.sign(keyring.address, valueTransfer)

    const rlpEncoded = valueTransfer.getRLPEncoding()
    console.log(`RLP-encoded string: ${rlpEncoded}`)
    // RLP인코딩된 트랜잭션의 문자열을 콘솔에 출력
}

testFunction()

// 내 지갑주소 2개로 test. 송금하는 계정의 PrivateKey로 keyring 생성, caver-js지갑에 추가
// 보내는 계정 주소에 두번째 계정 주소 넣기 ??
RLP-encoded string: 0x08f87e1a8505d21dba0082753094b4c25d57583bc3103d01459105a94d53c6f62393019406545b70b18068ccf5ee6eb5eb1f465c5dbe4f05f847f8458207f5a0d83da2c2d3a72227c43e8d331be8f7849a56e463946709456d96d4f843845468a00cc6dca77b022404691b83ff17c31778af9bd1f645fc0b065153ab573a53a4c6

2) RLP 인코딩된 서명된 트랜잭션을 caver.rpc.klay.sendRawTransaction 을 통해 Klaytn에 전송

// test.js
const Caver = require('caver-js')
const caver = new Caver('https://api.baobab.klaytn.net:8651/')

async function testFunction() {
    const rlpEncoding = `0x{RLP-encoded string}` //위의 코드를 실행해서 얻은 RLP인코딩된 문자열을 여기에 삽입
    
    // Send the transaction using `caver.rpc.klay.sendRawTransaction`.
    const receipt = await caver.rpc.klay.sendRawTransaction(rlpEncoding)
    console.log(receipt) // 콘솔에 영수증을 출력한다.
}

testFunction()

caver.wallet 없이 트랜잭션에 서명하고 klaytn에 서명된 트랜잭션을 보낼 수도 있다.

// test.js
const Caver = require('caver-js')
const caver = new Caver('https://api.baobab.klaytn.net:8651/')

async function testFunction() {
    // Create a value transfer transaction
    const keyring = caver.wallet.keyring.createFromPrivateKey('0x{private key}')
    const valueTransfer = new caver.transaction.valueTransfer({
        from: keyring.address,
        to: '0x176ff0344de49c04be577a3512b6991507647f72',
        value: 1,
        gas: 30000,
    })

    // Sign the transaction via transaction.sign
    await valueTransfer.sign(keyring)

    // Send the transaction to the Klaytn using `caver.rpc.klay.sendRawTransaction`.
    const receipt = await caver.rpc.klay.sendRawTransaction(valueTransfer)
    console.log(receipt)
}

testFunction()
// 영수증이 바로 출력됨

영수증 확인

// Using a promise - async/await
const receipt = await caver.rpc.klay.sendRawTransaction(rawTransaction)
console.log(receipt)

// Using a promise
caver.rpc.klay.sendRawTransaction(rawTransaction).then(console.log)

// Using an event emitter
caver.rpc.klay.sendRawTransaction(rawTransaction).on('receipt', console.log)

프로미스(promise) 및 이벤트 이미터(event emitter)를 통해 트랜잭션을 전송한 결과를 가져올 수 있다.

const receipt = await caver.rpc.klay.getTransactionReceipt('0x{transactionHash}')
console.log(receipt)

위 코드로 트랜잭션에 대한 영수증을 콘솔에 출력할 수 있다. 트랜잭션의 실행 결과는 영수증의 status를 통해 확인할 수 있다. (위의 영수증 이미지 참고)

다른 트랜잭션 타입 실행하기

트랜잭션 수수료 위임

위 코드를 실행하면 RLP 인코딩된 문자열이 출력된다.

0x09f8841c8505d21dba0082c35094b4c25d57583bc3103d01459105a94d53c6f62393059406545b70b18068ccf5ee6eb5eb1f465c5dbe4f05f847f8458207f6a04169a9ca7a18add672d1d98d13879f1ec2371c36f654d03fb9d6c977f91eb688a038a6c346060703ef764e947b8f097e2bda296b3d5442d3e42da61f9cb53152d880c4c3018080

caver.wallet에 수수료 납부자 키도 같이 있다면
caver.wallet.signAsFeePayer(feePayer.address, feeDelegatedTx)를 호출하여 수수료 납부자의 서명을 feeDelegatedTx에 넣을 수 있다. caver.wallet에 수수료 납부자의 키가 없다면 납부자는 트랜잭션 발신자가 서명한 RLP인코딩된 문자열(위 코드로 생성된 문자열)에서 feeDelegatedTx를 새로 만들고 서명을 직접 추가해야한다. 그러면 발신자의 서명, 수수료 납부자의 서명이 모두 첨부된 RLP인코딩된 문자열이 출력된다.

0x09f8dc1c8505d21dba0082c35094b4c25d57583bc3103d01459105a94d53c6f62393059406545b70b18068ccf5ee6eb5eb1f465c5dbe4f05f847f8458207f6a04169a9ca7a18add672d1d98d13879f1ec2371c36f654d03fb9d6c977f91eb688a038a6c346060703ef764e947b8f097e2bda296b3d5442d3e42da61f9cb53152d894b4c25d57583bc3103d01459105a94d53c6f62393f847f8458207f5a02088127459de5f84fa931464192e44f49560cccd45cb1577a872107d2a436fd2a03a958554cb19a8682dda12ecab4d14eb2891f992c2d95d39a6f5510f8997619a

트랜잭션 발신자와 수수료 납부자 모두 트랜잭션에 서명했으므로 이 트랜잭션을 klaytn에 전송할 수 있다. RLP인코딩된 문자열을 rlpEncoded에 대입한다.

// test.js
const Caver = require('caver-js')
const caver = new Caver('https://api.baobab.klaytn.net:8651/')

async function testFunction() {
    const rlpEncoded = '0x{RLP-encoded string}'
    const receipt = await caver.rpc.klay.sendRawTransaction(rlpEncoded)
    console.log(receipt)
}

testFunction()

이렇게 하면 FeeDelegatedValueTransfer의 결과 영수증을 출력할 수 있다.

-> 발신자: 1번계정, 수수료 납부자: 2번계정으로 했음
Klaytn Wallet에서 계정을 확인해보면 수수료가 2번 계정에서 빠져나갔고, TX Type이 Fee Delegated Value Transfer인 것을 알 수 있다.

계정 업데이트

klaytn계정의 PrivateKey를 바꾸고싶다면, 기억해야할 3가지 중요한 점이 있다.

1. Klaytn은 자신에게 전송되는 모든 트랜잭션을 검증한다.
2. 검증을 위해서는 PrivateKey와 대응되는 PublicKey가 필요하다.
3. PrivateKey를 새로운 것으로 바꾸는 것은 PublickKey를 새로운 것으로 바꾸는 것이 선행되어야 한다.
   이 new PublicKey는 new PrivateKey로부터 만들어진 것이어야한다.

그다음 PrivateKey를 변경하는 과정은 아래 step을 따른다.

1. new keyring을 만들기 위한 new PrivateKey를 준비한다.
2. 필요한 type에 맞는 keyring을 생성한다. (single, multiple, role-based keyring)
3. new keyring에서 account 인스턴스를 생성한다. 이 account 인스턴스는 (나의)klaytn 계정의 새로운 PublicKey를 포함한다.
4. account 인스턴스를 입력 파라미터로 받는 AccountUpdate 트랜잭션을 Klaytn에 전송한다.
5. 기존 keyring을 new keyring으로 교체한다.

AccountKey를 변경하기 위해서는 caver.transaction.accountUpdate의 account 필드에 klaytn 계정 주소와 업데이트할 account Key가 들어있는 account 인스턴스를 넣는다. 위 코드를 실행하면 개인키 업데이트 및 계정 정보 업데이트 결과가 출력된다. Klaytnscope에서 보기

multiple private key로 account instance를 생성해서 account key를 업데이트할 수 있다. (여기서 AccountKeyWeightMultiSig 사용)

AccountKeyRoleBased를 이용한 AccountKey 업데이트

// Create an account instance with roles using AccountKeyRoleBased. In the account instance created, each role has a public key that corresponds to one private key.
const newPrivateKeys = caver.wallet.keyring.generateRoleBasedKeys([1, 1, 1])
// role 마다 공개키를 1개 사용하는 예시
// 이들 각각은 개인키 1개에 대응된다.
const newKeyring = caver.wallet.keyring.createWithRoleBasedKey(sender.address, newPrivateKeys)

const account = newKeyring.toAccount()

스마트 컨트랙트

caver.contract package는 klaytn의 스마트 컨트랙트와 쉽게 상호작용하게 해준다. ABI가 주어지면 스마트 컨트랙트의 모든 메소드를 자동으로 자바스크립트 호출로 변환해서 스마트 컨트랙트가 마치 자바스크립트 객체인것 처럼 상호작용할 수 있다.

bin파일에 바이트코드가 담겨있고 abi파일이 출력된다.
이렇게 얻은 ABI파일의 내용을 사용해서 컨트랙트 인스턴스를 만들 수 있다.
스마트 컨트랙트 메서드들이 ABI를 통해 컨트랙트 인스턴스 내부에서 관리된다.

만약 스마트 컨트랙트가 이미 배포되었고 배포된 컨트랙트의 주소를 알고있다면, 컨트랙트 인스턴스의 두번째 인자로 그 주소를 넣어주면 된다.

const contractInstance = new caver.contract(abi, '0x{address}') // 여기에 컨트랙트 주소 같이 넣어줌

이렇게 하고 실행하면 컨트랙트는 배포되어 주소를 갖고있으므로 위에는 null값이었던 주소 필드에 컨트랙트 주소가 입력되어 출력된다.

컨트랙트 인스턴스가 생성되면 바이트코드를 data필드에 전달하는 것으로 컨트랙트를 배포할 수 있다. caver.contract는 배포 및 실행을 위해서 트랜잭션을 전송한다. 트랜잭션 서명에는 caver.wallet에 있는 keyring을 사용한다.
caver.contract를 사용해 스마트 컨트랙트를 수수료 위임 트랜잭션으로 배포하는 것은 아직 지원되지 않음. 이걸 하기 위해서는 caver.transaction.feeDelegatedSmartContractDeploy 혹은 caver.transaction.feeDelegatedSmartContractDeployWithRatio를 사용해야한다.

sending a Transaction with multiple signers

Klaytn 계정의 AccountKey가 AccountKeyMultiSig 또는 AccountKeyRoleBased인 경우 각 키를 관리하는 사람이 다를 수 있다. 이처럼 트랜잭션에 서명해야하는 사람이 여럿인 경우, 서명을 수집하고 트랜잭션을 보내는 방법에 대해 알아보자.

먼저 Klaytn계정의 AccountKey를 AccountKeyWeightedMultiSig로 업데이트한다.

참고

const Caver = require('caver-js')
const caver = new Caver('https://api.baobab.klaytn.net:8651/')
const testAddress = '0x{address}'
const testPrivateKey = '0x{PrivateKey}'
async function testFunction() {
    console.log(`=====> Example for account key update to AccountKeyWeightedMultiSig`)
    // Add account to in-memory wallet
    let account = caver.klay.accounts.createWithAccountKey(testAddress, testPrivateKey)
    caver.klay.accounts.wallet.add(account)
    // Create new private key array
    const newKeyArray = [caver.klay.accounts.create().keys, caver.klay.accounts.create().keys, caver.klay.accounts.create().keys]
    console.log(`new private key array=> ${newKeyArray}`)
    // Create options object for defining threshold and weight
    const options = { threshold: 2, weight: [1,1,1] }
    // Create AccountForUpdate instance
    const key = caver.klay.accounts.createAccountForUpdate(account.address, newKeyArray, options)
    console.log(`AccountForUpdate instance => `)
    console.log(key)
    // Create transaction object
    const accountUpdateTx = {
        type: 'ACCOUNT_UPDATE',
        from: account.address,
        key,
        gas: 90000,
    }
    // Send transaction
    const receipt = await caver.klay.sendTransaction(accountUpdateTx)
    console.log(`Account Update Transaction receipt => `)
    console.log(receipt)    
    // Get accountKey from network
    const accountKeyFromNetwork = await caver.klay.getAccountKey(account.address)
    console.log(`Result of account key update to AccountKeyWeightedMultiSig`)
    console.log(`Account address => ${account.address}`)
    console.log(`accountKeyFromNetwork =>`)
    console.log(accountKeyFromNetwork)
    // Update account's key in in-memory wallet
  caver.klay.accounts.wallet.updateAccountKey(account.address, newKeyArray)
}
testFunction()

순차적으로 서명하기

이제 여러 PrivateKey를 사용해서 트랜잭션에 순차적으로 서명하는 방법을 알아보자. 위에서 한 계정을 AccountKey가 공개키 3개로 구성된 AccountKeyWeightedMultiSig로 upsate했다. 이는 이 Klaytn 계정이 개인키를 3개 쓸 수 있으며, 계정 사용자 1명당 개인키 1개를 사용하는 것을 의미한다.(같은 Klaytn 계정을 사용자 3명이 공유하는 상황)

const Caver = require('caver-js')
const caver = new Caver('https://api.baobab.klaytn.net:8651/')

async function testFunction() {
    const user1 = caver.wallet.keyring.createWithSingleKey('0x4889fae0e032022cbcbe3da60bea36dd3cd25b18', '0xb1c2167829eb110b2b4b6836d66e70edc4289c1b43afeeada2322f17861a8b06')
    const user2 = caver.wallet.keyring.createWithSingleKey('0x4889fae0e032022cbcbe3da60bea36dd3cd25b18', '0xf937afaa2c5e9ce3b15c5b4f33a139d3d1d29a130c9c3df5cf78b4f1c828ceb3')
    const user3 = caver.wallet.keyring.createWithSingleKey('0x4889fae0e032022cbcbe3da60bea36dd3cd25b18', '0x6e459c121c8c8b26f9fa497b1fb76b076eb797e02f77f3575b8054f12af83a09')

    const transaction = new caver.transaction.valueTransfer({
        from: user1.address,
        to: '0xb4c25d57583bc3103d01459105a94d53c6f62393',
        value: 1,
        gas: 70000,
    })

    // user1이 트랜잭션에 서명
    await transaction.sign(user1)

    // Create a value transfer transaction from the RLP-encoded string
    const rlpEncoding = transaction.getRLPEncoding()
    const transactionFromRLP = new caver.transaction.valueTransfer(rlpEncoding)

    // user2가 트랜잭션에 서명
    await transactionFromRLP.sign(user2)
    // user3가 트랜잭션에 서명
    await transactionFromRLP.sign(user3)

    console.log(transactionFromRLP.signatures)
}

testFunction()

transactionFromRLP.signatures에 3개의 서명이 있음을 확인할 수 있다. 이렇게 Account의 모든 사용자가 서명했다면,
await caver.rpc.klay.sendRawTransaction(transactionFromRLP)로 트랜잭션을 Klaytn에 보낸다.

서명된 raw transaction들을 결합하기

여러개의 서명이 첨부된 상태의 RLP 인코딩된 raw transaction들을 받은 경우, 이것들을 모든 서명이 첨부된 하나의 raw transaction으로 합칠 수 있다.

좋은 웹페이지 즐겨찾기