Crypto Zombie - 6

Hong·2022년 12월 18일
0




🧟‍♂️

🏭 zombiefactory.sol
 - 🍞 zombiefeeding.sol
    - 👼 zombieHelper.sol
		- 💥 zombieAttack.sol
			- 🪙 erc721.sol
			- 🧑‍🔬 safemath.sol
				- 🧸 zombieOwnership.sol

🪙 erc721.sol
🧑‍🔬 safemath.sol
🚧 ownable.sol


📡 index.html



Chapter 1

왜 web3.js가 필요할까?

//JSON-RPC방식
{"jsonrpc":"2.0","method":"eth_sendTransaction","params":[{"from":"0xb60e8dd61c5d32be8058bb8eb970870f07233155","to":"0xd46e8dd67c5d32be8058bb8eb970870f07244567","gas":"0x76c0","gasPrice":"0x9184e72a000","value":"0x9184e72a","data":"0xd46e8dd67c5d32be8d46e8dd67c5d32be8058bb8eb970870f072445675058bb8eb970870f072445675"}],"id":1}

//web3 라이브러리 사용방식
CryptoZombies.methods.createRandomZombie("Vitalik Nakamoto 🤔")
  .send({ from: "0xb60e8dd61c5d32be8058bb8eb970870f07233155", gas: "3000000" })

이더리움 노드들은 JSON-RPC방식을 통해 서로 소통한다.

JSON-RPC방식은 사람이 읽기 어렵다.
이러한 문제를 web3.js가 해결하며 web3.js를 통해 자바스크립트 interface로 우리는 이더리움 노드들과 상호작용할 수 있다.




Chapter 2

web3.js provider

Web3.js에서 Web3 provider를 설정하는 것은 내가 작성한 스마트 컨트랙트 코드의 읽기와 쓰기를 어떤 노드가 처리해줄 것인지 선택하는 것과 같다.

이는 전통적인 웹 앱에서 API 호출을 위해 원격 웹 서버의 URL을 설정하는 것과 같다.
나의 PC에 이더리움 노드를 설치하고 provider로 지정할 수 있지만, 이더리움 전체 노드의 용량이 크기 때문에 비효율적인 방법이다.
때문에 DApp의 개발자들의 편의성을 위해 각 PC에서 이더리움 노드를 운영할 필요가 없도록 하기 위해 Infura서비스가 만들어졌다.

그리고 메타마스크는 내부적으로 Infura의 서버를 Web3 provider로 사용한다
(provider변경도 가능함).




Chapter 3

web3.js 컨트랙트 인스턴스화하기(instantiate)

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

📡 index.html

// myContract 인스턴스화
<!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>

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

	  //웹에서 해당 html앱을 실행할 때(load) 다음과 같은 함수를 실행한다
      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는 내가 작성한 스마트 컨트랙트와 통신하기 위해 2가지를 필요로 한다

  • 컨트랙트의 주소
    내가 작성한 코드를 remix나 truffle등을 통해 이더리움 블록체인 네트워크에 배포하면
    contract 주소를 얻을 수 있다.
    내가 배포한 contract를 가지고 web3.js를 통해 무엇인가를 하고 싶다면(javascript, html, css등으로 무엇인가 앱을 만들고 싶다면) 내가 이더리움 네트워크에 배포한 contract주소를 알아야 한다

  • ABI
    ABI는 Application Binary Interface의 줄임말이다. 기본적으로 JSON 형태로 나의 컨트랙트 메소드를 표현하는 것이다.
    ABI는 remix등의 solidity 컴파일러로부터 얻을 수 있다.




Chapter 4

컨트랙트 함수 호출하기

📡 index.html

<!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>

    <script>
      var cryptoZombies;

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

      function getZombieDetails(id) {
        return cryptoZombies.methods.zombies(id).call()
      }
		
	  //ZombieFactory.sol의 zombieToOwner mappring을 이용함
      function zombieToOwner(id) {
        return cryptoZombies.methods.zombieToOwner(id).call()
      }
	  
	  // ZombieHelper.sol의 function getZombiesByOwner(address _owner)함수를 이용함
      function getZombiesByOwner(owner) {
        return cryptoZombies.methods.getZombiesByOwner(owner).call()
      }

		...

      })
    </script>
  </body>
</html>

Web3.js는 컨트랙트의 함수를 호출하기 위해 사용할 두 개의 메소드를 가지고 있다

  • call
//123을 매개 변수로 myMethod라는 이름의 함수를 call했다
myContract.methods.myMethod(123).call()

call은 view와 pure 함수를 위해 사용한다.
로컬 노드에서만 실행하고, 블록체인에 트랜잭션을 만들지 않는다.
view와 pure 함수는 읽기 전용이고 블록체인에서 상태를 변경하지 않는다.
가스를 전혀 소모하지 않고, 메타마스크에서 트랜잭션에 서명하라고 사용자에게 창을 띄우지도 않는다.

  • send
//123을 매개 변수로 myMethod라는 이름의 함수를 호출하는 트랜잭션을 send했다
myContract.methods.myMethod(123).send()

send는 트랜잭션을 만들고 블록체인 상의 데이터를 변경한다.
view와 pure가 아닌 모든 함수에 대해 send를 사용해야 하는 것이다.
트랜잭션을 send하는 것은 사용자에게 가스를 지불하도록 하고, 메타마스크에서 트랜잭션에 서명하라고 창을 띄울 것이다. Web3 프로바이더로 메타마스크를 사용할 때, send()를 호출하면 자동으로 이 모든 것이 이루어지고, 내가 작성하는 코드에 어떤 특별한 것도 추가할 필요가 없다.




Chapter 5

메타마스크 계정 변경에 따른 UI업데이트하기

프론트 엔드에서 User가 선택한 계정이 소유하고있는 좀비 캐릭터를 보여주고 싶다
그리고 User가 메타마스크 계정을 언제든지 바꿀 수 있기 때문에
계정이 바뀌는 동작을 반영하여 UI를 업데이트 하고자 한다

📡 index.html

var userAccount = web3.eth.accounts[0]

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

여기서는 userAccount가 여전히 web3.eth.accounts[0]과 같은지 확인하기 위해 100밀리초마다 확인하고 있다(setInterval함수를 사용).
그렇지 않다면, userAccount에 현재 활성화된 계정을 다시 할당하고, 화면을 업데이트하기 위한 함수를 호출한다.




Chapter 6

📡 index.html

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

      function displayZombies(ids) {
        $("#zombies").empty();
        //getZombiesByOwner(userAccount)로 부터 return 받은 [0, 13, 47]형식으로 받은 ids에 대해 for반복문을 실행해준다
        for (id of ids) {
          // 해당 id마다 컨트랙트에서 좀비 상세 정보를 찾아, `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>`);
          });
          
          //for문이 완료되면 입력한 userAccount의 모든 zombie들의 정보가 div zombie 테그에 들어가게 됨
        }
      }

	...

      })
    </script>
  </body>
</html>

따라서 displayZombies 함수는 다음과 같은 것을 할 것이다.

  • 먼저 이미 무언가가 #zombies div의 안에 들어 있는 상황을 고려하여 이 div의 내용을 비운다
    (이렇게 하면 사용자가 그들의 MetaMask 계정을 변경하면 새로운 좀비 군대를 로딩하기 전에 기존의 것을 삭제할 것이다).
  • for문 반복을 통해 각 id마다 getZombieDetails(id)를 호출해서 좀비에 대한 모든 정보를 불러온다. 그리고서
  • 프론트 엔드 화면에 표시하기 위해 HTML 템플릿에 좀비에 대한 정보를 집어넣고, 해당 템플릿을 #zombies div에 붙여넣는다.



Chapter 7

send함수를 이용해 스마트 컨트랙트의 데이터를 변경한다

📡 index.html

<!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>

	...
    
      //name을 입력해서 랜덤좀비를 생성하고 UI에 표현하는 함수를 만든다
      function createRandomZombie(name) {
          // 시간이 꽤 걸릴 수 있으니, 트랜잭션이 보내졌다는 것을
          // 유저가 알 수 있도록 텍스트를 입력해줌으로써 UI를 업데이트함
          $("#txStatus").text("Creating new zombie on the blockchain. This may take a while...");
      
          // 내가 배포한 컨트랙트에 name이라는 입력값을 createRandomZombie함수에 보낸다
          return CryptoZombies.methods.createRandomZombie(name)
          .send({ from: userAccount }) //.send({ from: userAccount, gas: 3000000 })와 같이 gas를 지정할 수도 있다
          .on("receipt", function(receipt) { //정상적으로 랜덤좀비가 생성된 경우
            $("#txStatus").text("Successfully created " + name + "!");
            // 블록체인에 트랜잭션이 반영되었으며, UI를 다시 반영함
            getZombiesByOwner(userAccount).then(displayZombies);
          })
          .on("error", function(error) { // 정상적으로 랜덤좀비가 생성되지 않은 경우
            // 사용자들에게 트랜잭션이 실패했음을 알려주기 위한 처리
            $("#txStatus").text(error);
      	});
      }


	  //zombieId에게 kittyId를 먹이로주고 이것을 UI에 표현하는 함수를 만든다 
      function feedOnKitty(zombieId, kittyId) {
          // 시간이 꽤 걸릴 수 있으니, 트랜잭션이 보내졌다는 것을
          // 유저가 알 수 있도록 텍스트를 입력해줌으로써 UI를 업데이트함
          $("#txStatus").text("Eating a kitty. This may take a while...");
		  
          // 내가 배포한 컨트랙트에 zombieId, kittyId이라는 입력값을 feedOnKitty함수에 보낸다
          return CryptoZombies.methods.feedOnKitty(zombieId, kittyId)
          .send({ from: userAccount })
          .on("receipt", function(receipt) {
            $("#txStatus").text("Ate a kitty and spawned a new Zombie!");
		  
          // 블록체인에 트랜잭션이 반영되었으며, UI를 다시 반영함
            getZombiesByOwner(userAccount).then(displayZombies);
          })
          .on("error", function(error) { // 정상적으로 랜덤좀비가 생성되지 않은 경우
            // 사용자들에게 트랜잭션이 실패했음을 알려주기 위한 처리
            $("#txStatus").text(error);
      	});
      }

		...
        
    </script>
  </body>
</html>

send 함수는 call 함수와 다른 부분이 있다.

  • 트랜잭션을 전송(send)하려면 함수를 호출한 사람의 from 주소가 필요하다(솔리디티 코드에서는 msg.sender가 될 것이다).
    이는 우리 DApp의 사용자가 되어야 할 것이다, 메타마스크가 나타나 그들에게 서명을 하도록 할거다.

  • 트랜잭션 전송(send)은 가스를 소모한다.

  • 사용자가 트랜잭션 전송을 하고 난 후 실제로 블록체인에 적용될 때까지는 상당한 지연이 발생할 것이다. 트랜잭션이 블록에 포함될 때까지 기다려야 하는데, 이더리움의 평균 블록 시간이 15초이기 때문이다.
    만약 이더리움에 보류 중인 거래가 많거나 사용자가 가스 가격을 지나치게 낮게 보낼 경우, 우리 트랜잭션이 블록에 포함되길 기다려야 하고, 이는 몇 분씩 걸릴 수 있다.
    때문에 이 코드의 비동기적 특성을 다루기 위한 로직이 필요하게 될 거다.




Chapter 8

Web3.js에서 특별한 처리가 필요한 다른 종류의 함수 : payable 함수

ZombieHelper.sol을 다시 들여다 보면 levelUp함수를 payable로 설정했었다(ether를 보냄으로써 좀비를 레벨업 시킬 수 있었다)

📡 index.html

function levelUp(uint _zombieId) external payable {
  require(msg.value == levelUpFee);
  zombies[_zombieId].level++;
}
<!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>

		...

	  //zombieId를 입력받아 좀비를 levelUp하는 함수를 만든다
      function levelUp(zombieId) {
        $("#txStatus").text("좀비를 레벨업하는 중..."); //동기 처리가 시간이 걸리는 동안 사용자에게 보여줄 UI를 만든다 
        return CryptoZombies.methods.levelUp(zombieId)
        .send({ from: userAccount, value: web3js.utils.toWei("0.001") }) // levelUp함수가 payable함수이기 때문에 wei나 ether를 보낼 수 있다
        .on("receipt", function(receipt) {
          $("#txStatus").text("압도적인 힘! 좀비가 성공적으로 레벨업했습니다.");
        })
        .on("error", function(error) {
          $("#txStatus").text(error);
        });
      }

		...

      })
    </script>
  </body>
</html>

wei, ether

wei이더의 가장 작은 하위 단위다 하나의 이더는 10^18개의 wei이다.
보내는 value를 wei로 입력할 때 0이 너무 많은게 문제가 될듯하지만 web3.js에는
ether 단위를 wei 단위로 쉽게 바꿔주는 method가 있다

// 이렇게 하면 1 ETH를 Wei로 바꿀 것이다
web3js.utils.toWei("1");


/*
내가 작성한 DApp에서, 나는 levelUpFee = 0.001 ether로 설정했다. 
때문에 levelup 함수를 호출할 때, 아래의 코드를 써서 사용자가 0.001 이더를 보내게 할 수 있다.
*/
CryptoZombies.methods.levelUp(zombieId)
.send({ from: userAccount, value: web3js.utils.toWei("0.001") })



Chapter 9

이벤트(Event) 구독하기(indexed를 통해 원하는 데이터만 필터링하기)

zombiefactory.sol을 다시 생각해보면, 새로운 좀비가 생성될 때마다 매번 호출되던 NewZombie라는 이벤트가 있었다
이건 DApp에서 어떤 좀비가 생성되든지 항상 알림을 보내는 event다.
사용자의 좀비만이 아니라 다른 사람이 좀비를 생성해도 event가 찍힌다는 것이다.

event NewZombie(uint zombieId, string name, uint dna);

  • 새로운 좀비 수신하기

근데 현재 사용자가 만든 좀비에 대해서만 알림을 보내고 싶다면 어떻게 해야 할까?
indexed 사용하면 된다.

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 사용하기

이벤트를 필터링하고 현재 사용자와 연관된 변경만을 수신하기 위해, 우리의 ERC721을 구현할 때 Transfer 이벤트에서 했던 것처럼 우리의 솔리디티 컨트랙트에 indexed 키워드를 사용해야 한다.
이 경우, _from_toindexed가 적용되어 있기 때문에,
우리 프론트엔드의 이벤트 리스너에서 이들을 필터링할 수 있다.

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;
  // 현재 사용자가 방금 좀비를 생성했다는 event를 받았다!
  // 때문에 해당 좀비를 보여줄 수 있도록 UI를 업데이트할 수 있도록 코드를 추가했다
  getZombiesByOwner(userAccount).then(displayZombies);
}).on("error", console.error);

지난 이벤트 데이터 들고오기

우린 getPastEvents를 이용해 지난 이벤트들에 대해 질의를 하고,
fromBlocktoBlock 필터들을 이용해 이벤트 로그에 대한 시간 범위를 솔리디티에 전달할 수 있다(여기서 "block"은 이더리움 블록 번호를 나타낸다).

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

왜 Event를 사용하는가?

데이터를 블록체인에 기록하는 것은 솔리디티에서 가장 비싼 비용을 지불하는 작업 중 하나였다. 하지만 이벤트를 이용하는 것은 가스 측면에서 훨씬 더 저렴하다.

여기서 단점이 되는 부분은 스마트 컨트랙트 자체 안에서는 이벤트를 읽을 수 없다는 것이다. 하지만 히스토리로 블록체인에 기록하여 앱의 프론트엔드에서 읽기를 원하는 데이터가 있다면, 이는 새겨놓아야 할 중요한 사용 예시이다.

예를 들어, 우린 이것을 좀비 전투의 히스토리를 event를 통해 기록용으로 사용할 수 있다.
좀비가 다른 좀비를 공격할 때마다, 그리고 누군가 이길 때마다 우린 이벤트를 생성할 수 있다. 스마트 컨트랙트는 추후 결과를 계산할 때 이 데이터가 필요하지 않지만, 사용자들이 앱의 프론트엔드에서 찾아볼 수 있는 유용한 데이터다.

📡 index.html

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

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

		...
        
    </script>
  </body>
</html>



🧟‍♂️ 전체코드

📡 index.html

나머지 파일들은 이전과 같음

<!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>
profile
Notorious

0개의 댓글