커버 이미지의 출처는 다음과 같습니다 : https://cryptozombies.io/ko/

크립토좀비 사이트 내용을 정리하였으며, 컨텐츠 관련 모든 권리는 Loom Network에 있습니다.

6) 앱 프론트엔드 그리고 Web3.js

1. Web3.js 소개

Web3.js가 뭔가요?

  1. 스마트 컨트랙트의 주소
  2. 실행하고자 하는 함수
  3. 함수에 전달하고자 하는 변수들
// 그래... 이런 방법으로 모든 함수 호출을  작성할  있길 빌겠네!
// 오른쪽으로 스크롤하게 ==>
{
  "jsonrpc":"2.0",
  "method":"eth_sendTransaction",
  "params":
  [
    {
      "from":"0xb60e...",
      "to":"0xd46e...",
      "gas":"0x76c0",
      "gasPrice":"0x9184...",
      "value":"0x9184e72a",
      "data":"0xd46..."
    }
  ],
  "id":1
}
CryptoZombies.methods.createRandomZombie("Vitalik Nakamoto 🤔")
  .send({ from: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", gas: "300

설정하기 1 : 패키지 도구 사용

# NPM을 사용할 때
npm install web3

# Yarn을 사용할 때
yarn add web3

# Bower를 사용할 때
bower install web3

# ...기타 등등.

설정하기 2 : 프로젝트에 포함 시키기

<script language="javascript" type="text/javascript" src="web3.min.js"></script>

2. Web3 프로바이더(Provider)

Infura

var web3 = new Web3(new Web3.providers.WebsocketProvider("wss://mainnet.infura.io/ws"));

메타마스크(Metamask)

메타마스크의 Web3 프로바이더 사용하기

// 메타마스크에서 제공하는 템플릿 코드
// - 사용자가 메타마스크를 설치했는지 확인하고
// - 설치가 안 된 경우 우리 앱을 사용하려면
// - 메타마스크를 설치해야 한다고 알려줌
window.addEventListener('load', function() {

  // Web3가 브라우저에 주입되었는지 확인(Mist/MetaMask)
  if (typeof web3 !== 'undefined') {
    // Mist/MetaMask의 프로바이더 사용
    web3js = new Web3(web3.currentProvider);
  } else {
    // 사용자가 Metamask를 설치하지 않은 경우에 대해 처리
    // 사용자들에게 Metamask를 설치하라는 등의 메세지를 보여줄 것
  }

  // 이제 자네 앱을 시작하고 web3에 자유롭게 접근할 수 있네:
  startApp()

})

3. 컨트랙트와 대화하기

컨트랙트 주소

컨트랙트 ABI

Web3.js 컨트랙트 인스턴스화 하기

// myContract 인스턴스화
var myContract = new web3js.eth.Contract(myABI, myContractAddress);

4. 컨트랙트 함수 호출하기

Call

myContract.methods.myMethod(123).call()
복습

Send

myContract.methods.myMethod(123).send()
참고

데이터 받기

function getZombieDetails(id) {
  return cryptoZombies.methods.zombies(id).call()
}

// 함수를 호출하고 결과를 가지고 무언가를 처리:
getZombieDetails(15)
.then(function(result) {
  console.log("Zombie 15: " + JSON.stringify(result));
});
{
  "name": "H4XF13LD MORRIS'S COOLER OLDER BROTHER",
  "dna": "1337133713371337",
  "level": "9999",
  "readyTime": "1522498671",
  "winCount": "999999999",
  "lossCount": "0" // Obviously.
}
참고

5. 메타마스크와 계정

메타마스크에서 사용자 계정 가져오기

var userAccount = web3.eth.accounts[0]
var accountInterval = setInterval(function() {
  //  계정이 바뀌었는지 확인
  if (web3.eth.accounts[0] !== userAccount) {
    userAccount = web3.eth.accounts[0];
    // 새 계정에 대한 UI로 업데이트 하기 위한 함수 호출
    updateInterface();
  }
}, 100);

6. 트랜잭션 보내기

send 함수를 이용해 스마트 컨트랙트의 데이터를 변경하는 방법

  1. 트랜잭션을 전송(send)하려면, 함수를 호출한 사람의 from 주소가 필요함(Solidity 코드에서는 msg.sender)
    • 함수를 호출한 사람은 DApp의 사용자가 될 것이므로
    • 메타마스크가 나타나 서명을 하도록 하게 됨
  2. 트랜잭션 전송(send)은 가스를 소모함
  3. 사용자가 트랜잭션 전송을 하고 난 후 실제 블록체인에 적용시까지는 상당한 지연이 발생함
    • 트랜잭션이 블록에 포함될 때까지 기다려야 함
    • 이더리움의 평균 블록 시간이 15초임
    • 만약 이더리움에 보류중 거래가 많거나
    • 사용자가 가스 가격을 지나치게 낮게 보낸 경우
    • 트랜잭션에 블록이 포함되길 기다려야 하고 이는 몇 분씩 걸릴 수 있음

함수 호출 예시 : 좀비 만들기

function createRandomZombie(string _name) public {
  require(ownerZombieCount[msg.sender] == 0);
  uint randDna = _generateRandomDna(_name);
  randDna = randDna - randDna % 100;
  _createZombie(_name, randDna);
}
function createRandomZombie(name) {
  // 시간이 꽤 걸릴 수 있으니, 트랜잭션이 보내졌다는 것을
  // 유저가 알 수 있도록 UI를 업데이트해야 함
  $("#txStatus").text("Creating new zombie on the blockchain. This may take a while...");
  // 우리 컨트랙트에 전송하기:
  return CryptoZombies.methods.createRandomZombie(name)
  .send({ from: userAccount })
  .on("receipt", function(receipt) {
    $("#txStatus").text("Successfully created " + name + "!");
    // 블록체인에 트랜잭션이 반영되었으며, UI를 다시 그려야 함
    getZombiesByOwner(userAccount).then(displayZombies);
  })
  .on("error", function(error) {
    // 사용자들에게 트랜잭션이 실패했음을 알려주기 위한 처리
    $("#txStatus").text(error);
  });
}
참고

7. Payable 함수 호출하기

payable 함수

function levelUp(uint _zombieId) external payable {
  require(msg.value == levelUpFee);
  zombies[_zombieId].level++;
}

Wei란?

// 이렇게 하면 1 ETH를 Wei로 바꿀 것이네
web3js.utils.toWei("1");
CryptoZombies.methods.levelUp(zombieId)
.send({ from: userAccount, value: web3js.utils.toWei("0.001") })

8. 이벤트(Event) 구독하기

새로운 이벤트 구독하기

event NewZombie(uint zombieId, string name, uint dna);
cryptoZombies.events.NewZombie()
.on("data", function(event) {
  let zombie = event.returnValues;
  // `event.returnValue` 객체에서 이 이벤트의 세 가지 반환 값에 접근할 수 있네:
  console.log("새로운 좀비가 태어났습니다!", zombie.zombieId, zombie.name, zombie.dna);
}).on("error", console.error);

indexed 사용하기

event Transfer(address indexed _from, address indexed _to, uint256 _tokenId);
// `filter`를 사용해 `_to`가 `userAccount`와 같을 때만 코드를 실행
cryptoZombies.events.Transfer({ filter: { _to: userAccount } })
.on("data", function(event) {
  let data = event.returnValues;
  // 현재 사용자가 방금 좀비를 받았네!
  // 해당 좀비를 보여줄 수 있도록 UI를 업데이트할 수 있도록 여기에 추가
}).on("error", console.error);

지난 이벤트에 대해 질의하기

cryptoZombies.getPastEvents("NewZombie", { fromBlock: 0, toBlock: "latest" })
.then(function(events) {
  // `events`는 우리가 위에서 했던 것처럼 반복 접근할 `event` 객체들의 배열이네.
  // 이 코드는 생성된 모든 좀비의 목록을 우리가 받을 수 있게 할
});

예시: 좀비 전투의 히스토리 기록

9. 마무리하기

이번 레슨은 일부러 기초적인 것을 다뤘네. 우리는 자네에게 스마트 컨트랙트와 상호작용 할 때 필요한 핵심 로직을 보여주고 싶었네. 하지만 코드에서 Web3.js 부분은 꽤 반복적인 게 많기 때문에 전체 구현을 하는 데에 너무 많은 시간을 쓰고 싶지는 않았네. 이번 레슨을 더 길게 만들어 다른 새로운 개념을 소개하지도 않을 것이네.

그러니 이런 구현은 기초적인 것으로 놔두고, 우리 좀비 게임 프론트엔드의 완전한 구현을 위해 생각해볼 것들의 체크리스트를 알려주겠네. 자네가 이걸 자네만의 것으로 만들어보고 싶다면:

  1. attack, changeName, changeDna, 그리고 ERC721 함수인 transfer, ownerOf, balanceOf 함수를 구현하게. 이런 함수들의 구현은 우리가 다룬 모든 다른 send 트랜잭션과 동일할 것이네.

  2. 자네가 setKittyContractAddress, setLevelUpFee, 그리고 withdraw를 실행할 수 있는 “관리 페이지”를 구현하게. 다시 한번 말하지만, 프론트엔드에 더 특별한 로직은 없네 - 이러한 구현들은 우리가 이미 다룬 함수들과 동일할 것이네. 자네는 그저 해당 컨트랙트를 배포했던 이더리움 주소에서 이 함수들을 호출했는지 확인하면 되네. 이 함수들은 onlyOwner 제어자를 가지고 있으니 말이야.

  3. 이 앱에서 구현하고 싶은 다른 몇 가지 화면이 있을 수 있네:

a. 개별 좀비 페이지: 특정 좀비에 대한 영구적인 링크를 통해 그 좀비의 정보를 볼 수 있는 곳이지. 이 페이지에서는 좀비의 외관과 이름, 주인(사용자 프로필 페이지에 대한 링크와 함께), 승리/패배 횟수, 전투 기록, 기타 등등을 보여줄 것이네.

b. 사용자 페이지: 영구적인 링크를 통해 사용자의 좀비 군대를 볼 수 있는 곳이지. 개별 좀비를 클릭하여 해당 페이지를 볼 수 있을 것이고, 자네가 메타마스크에 로그인되어 있고, 군대를 가지고 있다면 좀비를 클릭해 공격할 수도 있을 것이네.

c. 홈페이지: 현재 사용자의 좀비 군대를 볼 수 있는, 사용자 페이지의 한 종류이지(우리가 index.html에서 구현학 시작헀던 곳이네).

  1. UI 상에서 사용자가 크립토키티를 먹이로 줄 수 있는 방법이 있어야 하겠지. 홈페이지에서 각 좀비 옆에 “먹이 주기” 같은 버튼을 만들고, 사용자가 고양이의 ID를 입력하게 하는 텍스트 박스를 만들 수 있겠지(또는 그 고양이의 URL, 예를 들면: https://www.cryptokitties.co/kitty/578397). 이 버튼은 feedOnKitty 함수를 호출할 것이네.

  2. UI 상에서 한 사용자가 다른 사용자의 좀비를 공격할 수 있는 방법이 있어야 할 것이네.

이를 구현하는 하나의 방법은 한 사용자가 다른 사용자의 페이지로 들어가면, “이 좀비 공격하기” 버튼을 보여주는 것이네. 사용자가 그 버튼을 클릭하면, 현재 사용자의 좀비 군대를 포함하는 모달 창을 띄우고 “어떤 좀비로 공격하시겠습니까?” 메세지를 보여주면 되네.

또 사용자의 홈페이지에서 각 좀비 옆에 “좀비 공격하기” 버튼을 둘 수도 있네. 사용자가 그걸 클릭하면, 사용자가 좀비의 ID를 입력하여 찾을 수 있는 찾기 영역을 가지는 모달 창을 띄울 수 있겠지. 또는 “아무 좀비나 공격하기” 같은 옵션을 줘서 임의로 찾을 수도 있을 것이네.

그리고 쿨다운 기간이 아직 다 지나지 않은 사용자의 좀비는 회색 처리를 할 수도 있겠지. UI 상에서 사용자에게 해당 좀비로는 아직 공격할 수 없고 얼마나 더 기다려야 하는지 보여줄 수 있도록 말이야.

  1. 사용자의 홈페이지에는 각 좀비의 이름 또는 DNA를 바꾸고, 일정 비용을 내고 레벨업을 할 수 있는 옵션이 있을 수 있네. 사용자의 레벨이 충분하지 않으면 어떤 옵션들을 회색 처리를 할 수 있곘지.

  2. 새로운 사용자들을 위해, createRandomZombie()를 호출해 군대의 첫 번째 좀비를 만들 수 있는 입력 창과 함께 환영 메세지를 보여줄 수 있네.

  3. 마지막 챕터에서 논의한 것처럼, 우리 스마트 컨트랙트에 indexed 프로퍼티로 사용자의 address를 가지는 Attack 이벤트를 추가하고 싶을 수 있네. 이를 통해 실시간 알림을 만들 수 있을 것이네 - 사용자에게 그의 좀비가 공격당하면 알림 창을 띄워 알려주어, 그를 공격한 사용자/좀비를 보여주고 복수할 수 있게 하는 것이지.

  4. 또한 일종의 프론트엔드 캐시 계층을 구현하여 똑같은 데이터를 위해 Infura에 계속 접근하지는 않도록 하고 싶을 수 있네(우리의 현재 displayZombies 구현은 인터페이스를 새로고침할 때마다 각 좀비에 대해 getZombieDetails를 호출하지 - 하지만 현실적으로 우리의 군대에 추가된 새 좀비에 대해서만 이 함수를 호출하면 되네).

  5. 실시간 채팅방을 만들어 자네가 다른 사용자들의 군대를 부술 때마다 그 사용자를 도발할 수 있도록 하는 것? 제발 만들어 주게…

이건 시작일 뿐이네 - 우리는 더 많은 기능들을 생각해낼 수 있을 것이야 - 그리고 이미 많은 목록을 만들었지.

이런 전체 인터페이스를 만드는 데에는 많은 프론트엔드 코드를 써야 할 것이기에(HTML, CSS, 자바스크립트, 그리고 React나 Vue.js 같은 프레임워크), 이런 전체 프론트엔드를 만드는 것은 이것만으로 10개 레슨으로 전체 코스를 만들어야 할 것이야. 그러니 이 엄청난 구현은 자네에게 맡기겠네.

참고: 우리 스마트 컨트랙트는 분산화되어 있지만, 우리 DApp과 상호작용 할 수 있는 이 프론트엔드는 어딘가의 웹 서버에 완전히 중앙화되어 있을 것이네.

하지만, 우리가 Loom Network에서 만들고 있는 SDK를 쓰면, 중앙화된 웹 서버 대신 곧 자신만의 DAppChain에서 프론트엔드를 제공할 수 있을 것이네. 이더리움과 Loom DAppChain 간의 저런 방식으로, 전체 앱이 100% 블록체인 상에서 돌아갈 것이네.

at4am의 프로필 이미지

at4am

2019년 06월 13일

글쓴이의 더 많은 글 읽어보기