크립토 좀비 - 6

Lumi·2021년 11월 29일
0

크립토 좀비

목록 보기
6/7
post-thumbnail

일단 나도 처음해보는 작업이기 떄문에 전체적으로 혼자 진행을 해보고 코드를 분석함으로써 어떻게 작동을 하는지 작성해 보겠다.

이 코드의 기준은 HTML에서만 작동을 하게 된다.

  • React같은 도구를 사용하고 싶다면 그에 따른 방법을 따라야 한다.

🔥 전체 코드

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>CryptoZombies front-end</title>
    <script language="javascript" type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script language="javascript" type="text/javascript" src="web3.min.js"></script>
    <script language="javascript" type="text/javascript" src="cryptozombies_abi.js"></script>
  </head>
  <body>
    <div id="txStatus"></div>
    <div id="zombies"></div>

    <script>
      var cryptoZombies;
      var userAccount;

      function startApp() {
        var cryptoZombiesAddress = "YOUR_CONTRACT_ADDRESS";
        cryptoZombies = new web3js.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress);

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

        cryptoZombies.events.Transfer({ filter: { _to: userAccount } })
        .on("data", function(event) {
          let data = event.returnValues;
          getZombiesByOwner(userAccount).then(displayZombies);
        }).on("error", console.error);
      }

      function displayZombies(ids) {
        $("#zombies").empty();
        for (id of ids) {
          // 우리 컨트랙트에서 좀비 상세 정보를 찾아, `zombie` 객체 반환
          getZombieDetails(id)
          .then(function(zombie) {
            // HTML에 변수를 넣기 위해 ES6의 "template literal" 사용
            // 각각을 #zombies div에 붙여넣기
            $("#zombies").append(`<div class="zombie">
              <ul>
                <li>Name: ${zombie.name}</li>
                <li>DNA: ${zombie.dna}</li>
                <li>Level: ${zombie.level}</li>
                <li>Wins: ${zombie.winCount}</li>
                <li>Losses: ${zombie.lossCount}</li>
                <li>Ready Time: ${zombie.readyTime}</li>
              </ul>
            </div>`);
          });
        }
      }

      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);
        });
      }

      function feedOnKitty(zombieId, kittyId) {
        $("#txStatus").text("Eating a kitty. This may take a while...");
        return CryptoZombies.methods.feedOnKitty(zombieId, kittyId)
        .send({ from: userAccount })
        .on("receipt", function(receipt) {
          $("#txStatus").text("Ate a kitty and spawned a new Zombie!");
          getZombiesByOwner(userAccount).then(displayZombies);
        })
        .on("error", function(error) {
          $("#txStatus").text(error);
        });
      }

      function levelUp(zombieId) {
        $("#txStatus").text("좀비를 레벨업하는 중...");
        return CryptoZombies.methods.levelUp(zombieId)
        .send({ from: userAccount, value: web3.utils.toWei("0.001") })
        .on("receipt", function(receipt) {
          $("#txStatus").text("압도적인 힘! 좀비가 성공적으로 레벨업했습니다.");
        })
        .on("error", function(error) {
          $("#txStatus").text(error);
        });
      }

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

      function zombieToOwner(id) {
        return cryptoZombies.methods.zombieToOwner(id).call()
      }

      function getZombiesByOwner(owner) {
        return cryptoZombies.methods.getZombiesByOwner(owner).call()
      }

      window.addEventListener('load', function() {

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

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

      })
    </script>
  </body>
</html>
  • 한개씩 다루어 보겠다.

🔥 기본 셋팅

우리가 앞서 만들 컨트랙트를 배포하기 위헤서는 일단 web3.js를 사용해야 한다.

이 부분은 React같은 경우에는 npm install web3를 통해서 사용 가능하지만

이 글은 html에 대해서 다루기 떄문에 script, css를 부르는 것처럼 호출 해야 한다.

head부분에 있는

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

이 부분이 web3.js를 사용하겠다는 의미이다.

이더리움 노드들은 JSON-RPC라고 하는 JSON구조로 소통을 하기 떄문에 사용자 입장에서는 읽기에 매우 불편한 구조라고 할 수가 있다.

  • 그러기 떄문에 Web3.js를 사용해야 하는 것이다.

이후 우리는 프로바이더가 필요하다.

  • 코드를 읽고, 쓰기를 처리하기 위해 어떠 노드와 통신할수 있는 것을 말한다.

프로바이더와 같은 역할을 하는 것이 이더리움의 메타마스크, 클레이튼의 Kaikas 이다.

 window.addEventListener('load', () => {
  if (typeof web3 !== 'undefined') {
    web3js = new Web3(web3.currentProvider);
  } else {
  	alert("메타마스크를 이용하세요1!");
  }
  startApp();
})

이렇게 간단하게 웹이 로딩될떄에 이벤트를 추가해 확인 가능하다.

  • startApp() 이 부분은 뒤에서 다루어 보겠다.

이제 우리는 기본적인 도구들은 모두 준비가 되었다.

🔥 컨트랙트 주소, ABI

우리가 웹 IDE에서 배포를 하게 되면 고유의 컨트랙트 주소와 ABI가 생긴다는 것을 알 수가 있다.

이는 해당 컨트랙트의 생태계내 위치를 말하기 떄문에 우리는 그 값에 접근을 하여 컨트랙트를 활용 해야 한다.

startApp이 그 부분을 처리하고 있다.

일단 윈도우가 로드되면 프로바이더가 있는지를 확인하고 그후 처리를 해준뒤에 startApp()함수를 실행한다.

그러면 변수에 값을 할당함으로써 인스턴스화 가능하다.

function startApp() {
        var cryptoZombiesAddress = "YOUR_CONTRACT_ADDRESS";
        cryptoZombies = new web3js.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress);
}
  • 이 부분이 그것을 처리하고 있다.

🔥 pure,view함수 처리

컨트랙트에서 pure, view함수는 트랜잭션을 처리하지 않는다.

그러기 떄문에 web3.js에서도 이 함수들만 따로 호출하는 방법이 있는 이는 바로 call()이다.

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

      function zombieToOwner(id) {
        return cryptoZombies.methods.zombieToOwner(id).call()
      }

      function getZombiesByOwner(owner) {
        return cryptoZombies.methods.getZombiesByOwner(owner).call()
      }

이와 같은 함수들이 그러한 처리를 하고 있다.

🔥 상태 변경 함수 처리

그러면 pure, view함수가 아닌 컨트랙트는 어떻게 호출해야 할까??

이번에는 send()를 사용하게 된다.

처음 작성을 하다보니 사세하게 어떻게 작동하는지는 모르겠다 ㅠㅠ

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);
        });
      }

좀비를 만드는 함수이다.

일단 receipt는 트랜잭션이 블록에 포함될 떄, 즉 좀비가 생성이 되었을때 발생을 하게 된다.

error는 블럭에 포함되지 못하였을떄 에러를 보여주는 코드이다.

function feedOnKitty(zombieId, kittyId) {
        $("#txStatus").text("Eating a kitty. This may take a while...");
        return CryptoZombies.methods.feedOnKitty(zombieId, kittyId)
        .send({ from: userAccount })
        .on("receipt", function(receipt) {
          $("#txStatus").text("Ate a kitty and spawned a new Zombie!");
          getZombiesByOwner(userAccount).then(displayZombies);
        })
        .on("error", function(error) {
          $("#txStatus").text(error);
        });
      }
      
 function levelUp(zombieId) {
        $("#txStatus").text("좀비를 레벨업하는 중...");
        return CryptoZombies.methods.levelUp(zombieId)
        .send({ from: userAccount, value: web3.utils.toWei("0.001") })
        .on("receipt", function(receipt) {
          $("#txStatus").text("압도적인 힘! 좀비가 성공적으로 레벨업했습니다.");
        })
        .on("error", function(error) {
          $("#txStatus").text(error);
        });
      }

나머지 함수들도 블록이 포함될떄와 에러가 발생하였을떄를 처리해주는 것이다.

  • 이떄 toWei는 사용자가 이더를 보내야 한다는 의미이다.

🔥 displayZombies

 function displayZombies(ids) {
        $("#zombies").empty();
        for (id of ids) {
          // 우리 컨트랙트에서 좀비 상세 정보를 찾아, `zombie` 객체 반환
          getZombieDetails(id)
          .then(function(zombie) {
            // HTML에 변수를 넣기 위해 ES6의 "template literal" 사용
            // 각각을 #zombies div에 붙여넣기
            $("#zombies").append(`<div class="zombie">
              <ul>
                <li>Name: ${zombie.name}</li>
                <li>DNA: ${zombie.dna}</li>
                <li>Level: ${zombie.level}</li>
                <li>Wins: ${zombie.winCount}</li>
                <li>Losses: ${zombie.lossCount}</li>
                <li>Ready Time: ${zombie.readyTime}</li>
              </ul>
            </div>`);
          });
        }
      }

함수이름 그대로 가지고 있는 좀비들을 화면에 띄워주는 역할을 하게 된다.

🔥 계정 변경 처리

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

만약 계정이 바뀌게 되었을떄를 처리해주는 부분이다.

🔥 후기

나도 자세하게는 모르겠다...ㅠㅠㅠ

완전 처음해보는 작업이다 보니 확실하게 아는게 아니라서 구체적으로 설명을 할수가 없었다..

나도 개인적으로는 아 그냥 이런게 있구나, 이런식으로 활용하는구나 라고 생각을 하게 되었다.

  • 실제로는 그냥 Truffle를 사용하면 되지 않을까...??
profile
[기술 블로그가 아닌 하루하루 기록용 블로그]

0개의 댓글