[BlockChain] Web 3 js를 사용해보자!

kysung95·2021년 4월 23일
22

BlockChain

목록 보기
3/5
post-thumbnail

안녕하세요. 김용성입니다.
DApp을 만들 때는 Ethereum을 많이 사용하는데요.
오늘은 Web3 js에 대해 간단히 설명드린 후 사용 예제코드를 작성해보도록 하겠습니다.

Web3 js

Ethereum 네트워크는 여러 사용자(EOA)들이 연결되어있습니다. 또한 특정 역할을 수행하는 smart contract들로 구성되어있죠. 우리가 이 smart contract를 실행하기 위해서는 아래의 내용들을 전송해야합니다.

  • 스마트 컨트랙트의 주소
  • 실행하고자 하는 함수, 그리고
  • 그 함수에 전달하고자 하는 변수들

이더리움 노드들은 JSON-RPC라고 불리는 언어로만 소통할 수 있기 때문에, 이는 일반적으로 사람들이 읽고 분석하기에는 굉장히 어렵습니다. 예시를 보여드리겠습니다.

//JSON_RPC example
{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{"from":"0xa89e8ea54c3d12ba8054cbcab988870e02923434","to":"0xd46e8dd67c5d32be8058bb8eb970870f02335324","gas":"0x76c0","gasPrice":"0x9184e72a000","value":"0x9184e72a","data":"0xd46e8dd67c5d32be8d46e8dd67c5d32be8c23e54eb970870f072445675058bb8eb970870f072445675"}],"id":1}

활용하기가 어렵게 생겼죠?
우리가 DApp을 만들기 위해서는 해당 서비스를 이용하는 사람들간의 smart contract를 통해 거래, 혹은 ether 획득 등을 handling해야하는데 JSON-RPC를 통해 모든 것을 구현하자니 머리가 아픕니다.
그래서 Web3 js라는 녀석이 등장하였습니다. 이름에서 알 수 있 듯이 JavaScript 기반 모듈이죠.
(Web3 j라고 Java 기반도 존재합니다.)
DApp 생태계를 그림으로 표현하면 다음과 같습니다.

이더리움 블록체인 네트워크와 통신하기 위해서 MetaMask, Infura라는 인터페이스가 존재하고 DAPP을 개발하는 개발자들은 Web3 API를 사용하여 웹과 모바일을 개발할 수 있습니다.
오늘 저는 이 Web3 js를 사용하여 infura에 연결하고, 특정 address의 0.2ether를 또다른 address로 송금하는 코드를 작성해보고자 합니다.

(ethereum mainnet을 활용하면 정말 좋겠지만 저는 mainnet ether를 가지고 있지 않기 때문에 Ropsten test net을 활용할께요.)

준비물

MetaMask wallet 생성

MetaMask Chrome Extension 링크

이곳에서 메타마스크를 통해 ethereum wallet을 생성해줍니다.

여기서 말하는 12개의 영문 단어는 mnemonic code로 우리의 계정을 복구할 때 필요한 것이예요. 이러한 mnemonic code는 블록체인 환경에서 필수적으로 사용되는 것인데요. 해당 12개의 영문 단어를 수정 일체 없이 종이에 써서 기록해두거나 USB에 보관해주세요. 로컬에 기록해놓아도 되지만, mnemonic code는 드라이브가 아닌 별도 독립된 저장소에 유저들이 편하게 본인의 계정 복구정보를 기입하게끔 만들어진 것이기 때문에 로컬 드라이브에 저장하는 것은 그 목적과 맞지 않다고 볼 수 있습니다. 이러한 metamask 뿐만 아니고 여러분들이 somesing 이나 여타 여러가지 DApp 서비스들을 살펴보면 이러한 mnemonic code가 제공될텐데요. 그것들도 마찬가지로 해커의 손이 닿지않는 곳에 안전하게 보관하셔야 합니다.

그렇게 wallet을 만들고나면 Create Account버튼을 클릭하여 새로운 account를 만들면됩니다.

wallet을 만든 후에는 private 키를 발급받을 수 있는데요. 외부 유출이 안되는 곳에 저장을 해주세요. 이따 코드를 작성할 때 필요한 준비물입니다.

faucet contract를 사용하여 ropsten ether 발급

우리가 DApp을 만들다보면 여러 contract를 만들어보고 실험해보는데에 분명히 ether가 필요할거예요. 물론 돈이 아주 많은 부자라서 실제 mainnet의 ether를 구매해서 사용할 수 있다면 좋겠지만 저는 그렇지 않기때문에 mainnet이 아닌 ropsten test net을 사용하게 되었고요. ropsten testnet 에서 사용할 수 있는 ether를 제가 링크에 걸어놓은 사이트에서 발급받을 수가 있어요.

Ropsten ether faucet contract 제공 사이트: Ropsten Ethereum Faucet

여러분들이 전 단계에서 생성한 account의 주소를 적어주면 됩니다.

MetaMask에서는 address를 쉽게 클립보드에 저장할 수가 있습니다. 이 기능을 사용하면 편리합니다.

그렇게 이더를 발급받는데 성공하면 다음과 같은 메시지가 하단에 뜨는 것을 확인할 수 있을겁니다.
(ether가 바로 들어오지 않을 수도 있어요. pending상태가 길어질 경우 최대 몇시간까지도 소요되는 경우가 있습니다.)

infura 회원가입 및 infura_token 발급

infura 란??

Infura는 이더리움 블록체인 상에서 통합개발환경(IDE) 서비스를 제공하는 플랫폼이라고 할 수 있습니다. 쉽게 말하자면 개발자가 자신의 컴퓨터에 프라이빗 이더리움 블록체인을 구축하거나 geth나 Parity 등 클라이언트 소프트웨어를 설치하여 직접 메인넷이나 테스트넷에 접속하지 않더라도 편리한 Dapp 개발이 가능하도록 돕는 플랫폼이라고 할 수 있겠습니다.

개발자는 Infura에서 Dapp 프로젝트를 생성하고 작성한 스마트 컨트랙트를 손쉽게 배포, 실행, 테스트해 볼 수 있습니다. 따라서 개발 효율이 높아지고 시간이나 비용을 단축할 수 있다는 장점도 있습니다.
우리는 스마트 컨트랙트를 통해 이더를 전송해볼 예정이기에 infura에 가입해서 프로젝트를 생성해야 합니다.

infura 링크: infura.io

회원가입을 한 후 프로젝트를 생성해준 뒤 프로젝트에서 settings에 들어가보면 다음과 같은 화면이 보일겁니다.

저 곳에 있는 project Id가 infuranet을 통해 smart contract를 사용할 때의 준비물입니다.
이제는 vs Code를 켜주세요!

Web3 코드 작성

npm 모듈 설정

디렉토리를 하나 만든 후 npm init을 통해 node project로 만들어줍니다.

$ npm init

오늘 만들 코드에서 필요한 모듈은 다음과 같습니다.

"command-line-args": "^5.1.1",
"ethereumjs-tx": "^1.3.7",
"node-rest-client-promise": "^3.1.1",
"web3": "^1.3.5"

  • command-line-args: 명령어를 실행할 때 argument를 받아와서 실행할 수 있게 해주는 모듈입니다. 아마 다른 프로젝트에서도 사용해보신 분들이 계실거라 생각합니다.
  • node-rest-client-promise: client와 server의 비동기적 통신을 가능하게 해주는 모듈입니다. contract 함수는 모두 다 비동기로 처리되기 때문에 해당 모듈을 설치해주어야 합니다.
  • web3: web3 js 모듈입니다.
  • ethereum-tx: ethereum transaction 객체를 만들 때 필요한 모듈입니다. (처음에는 최신 버전으로 설치했었는데 계속해서 오류가 발생하더라구요. 생성자 관련 오류가 발생하고, invalid sender 오류가 발생하여 가장 stable하다고 평가받는 1.3.7버전으로 진행하였습니다. 여러분들도 1.3.7 버전으로 설치하시면 차질없이 진행가능할 것입니다.)

디렉토리 구조

프로젝트 directory 구조는 다음과 같습니다.

여기서 infura_token 파일에는 제가 준비물 단계에서 말씀드렸던 Project Id 값이 들어갑니다.
또한 private_key에는 MetaMask에서 생성한 account의 private key값을 넣어줍니다.
(크리티컬한 요소들-private key, infura token은 코드 내부에 넣지 말아주세요.
물론 command-line-args 모듈을 사용하지 않고 js파일 내부에 private key와 infura token을 넣어준 뒤 진행해도 되지만 어디까지나 블록체인의 꽃은 보안이라는점! 유념하시고 이런식으로 진행하시는게 좋다고 생각합니다.)

이제 sendEther.js 코드를 작성해보도록 하겠습니다.

// sendEther.js

const optionDefinitions = [
  { name: "infuraFileToken", type: String },
  { name: "privateKey", type: String }, // add private argument (private key는 코드안에 직접 집어넣지 않고 따로 파일을 만들어서 fs모듈로 불러왔습니다.)
]

const commandLineArgs = require("command-line-args")
const options = commandLineArgs(optionDefinitions)

var Web3 = require("web3")
var fs = require("fs")
var Tx = require("ethereumjs-tx") // ethereumjs-tx version 1.3.7 사용. (최신 버전을 사용할 경우에는 constructor 관련 에러와 invalid sender 에러가 떠서 버전 수정하였습니다.)
var infura_token = fs.readFileSync(options.infuraFileToken, "utf8")
var private_key = fs.readFileSync(options.privateKey, "utf8")
var node_host = `https://ropsten.infura.io/v3/${infura_token}`

var web3 = new Web3(node_host)

const send_account = "0xF2050Aa76D860167e35D4b34CDB4Ccc841d215F6" // send account
const receive_account = "0x8A2fd42c47C41888FaA695cd3272Ca0B048a2eA7" //  receiver account

const privateKeyBuffer = Buffer.from(private_key, "hex")

web3.eth.getTransactionCount(send_account, (err, txCount) => { // (1)
  const txObject = {
    nonce: web3.utils.toHex(txCount),
    gasLimit: web3.utils.toHex(1000000),
    gasPrice: web3.utils.toHex(web3.utils.toWei("10", "gwei")),
    to: receive_account,
    value: "0x2C68AF0BB140000", // 0.2 hexcode 변경 
  }

  const tx = new Tx(txObject)
  tx.sign(privateKeyBuffer)

  const serializedTx = tx.serialize()
  const raw = "0x" + serializedTx.toString("hex")

  web3.eth  
    .sendSignedTransaction(raw) //(2)
    .once("transactionHash", hash => {
      console.info("transactionHash", "https://ropsten.etherscan.io/tx/" + hash) // tx가 pending되는 즉시 etherscan에서 tx진행상태를 보여주는 링크를 제공해줍니다.
    })
    .once("receipt", receipt => {
      console.info("receipt", receipt) // 터미널에 receipt 출력
    })
    .on("error", console.error)
})

annotations

(1) getTransactionCount 메소드를 호출하여 현시점 transaction count를 파악한 뒤 해당 값을 콜백함수 arguments로 넣어줬습니다.

(2) 생성된 transaction을 전송합니다.

value 같은 경우에는 0x${hexcode}를 사용해서 넣어주어야합니다. 제가 0.2 비트코인을 보낸다고 했었죠??

hex 코드 변환 사이트: rapidtables

위 사이트에서 원하는 값을 입력하면 변환해줍니다.

(참고로 제가 입력한 0x2C68AF0BB140000가 decimal number로 200000000000000000 이고 참고로 이게 0.2 ether에 해당됩니다!)

저는 await async를 사용하지 않고 콜백과 promise를 통해 비동기 처리를 해주었어요. await async 코드로 변경시켜보시는 것도 좋을 것 같네요 ㅎㅎ

결과물 (reciept, etherscan)

작성한 web3 코드를 수행할 때는 다음과 같이 명령어에 입력해줍니다.

$ node 파일이름.js --infuraFileToken ./infura_token --privateKey ./private_key

다음과 같이 코드가 잘 동작하며 transaction이 생성되고 receipt가 잘 받아와지는 것을 확인하실 수 있습니다.


또한 제가 etherscan에서 해당 transaction 링크를 바로 받아올 수 있게끔 구현했기 때문에 터미널 창을 통해 etherscan으로 바로 연결해서 확인도 가능할 것입니다.

etherscan 사이트에서 조회

저는 오늘 새벽에 해당 transaction을 실행시켜보았습니다 :) 잘 작동하더라구요. ㅎㅎ

마무리

web3 js가 생각보다 재미있더군요. 여러분들도 docs를 읽어보면서 ropsten이나 kovan의 testnet으로 여러가지 contract를 실행해보시는게 어떨까요?? web3 공식문서 링크는 하단에 걸어놓겠습니다ㅎㅎ

web3 공식문서: https://web3js.readthedocs.io/en/v1.3.4/

긴 글 읽어주셔서 감사합니다.

profile
김용성입니다.

1개의 댓글

comment-user-thumbnail
2021년 5월 5일

잘보고갑니다 ㅎㅎ

답글 달기