일단 나도 처음해보는 작업이기 떄문에 전체적으로 혼자 진행을 해보고 코드를 분석함으로써 어떻게 작동을 하는지 작성해 보겠다.
이 코드의 기준은 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>
우리가 앞서 만들 컨트랙트를 배포하기 위헤서는 일단 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구조로 소통을 하기 떄문에 사용자 입장에서는 읽기에 매우 불편한 구조라고 할 수가 있다.
이후 우리는 프로바이더가 필요하다.
프로바이더와 같은 역할을 하는 것이 이더리움의 메타마스크, 클레이튼의 Kaikas 이다.
window.addEventListener('load', () => {
if (typeof web3 !== 'undefined') {
web3js = new Web3(web3.currentProvider);
} else {
alert("메타마스크를 이용하세요1!");
}
startApp();
})
이렇게 간단하게 웹이 로딩될떄에 이벤트를 추가해 확인 가능하다.
이제 우리는 기본적인 도구들은 모두 준비가 되었다.
우리가 웹 IDE에서 배포를 하게 되면 고유의 컨트랙트 주소와 ABI가 생긴다는 것을 알 수가 있다.
이는 해당 컨트랙트의 생태계내 위치를 말하기 떄문에 우리는 그 값에 접근을 하여 컨트랙트를 활용 해야 한다.
startApp
이 그 부분을 처리하고 있다.
일단 윈도우가 로드되면 프로바이더가 있는지를 확인하고 그후 처리를 해준뒤에 startApp()
함수를 실행한다.
그러면 변수에 값을 할당함으로써 인스턴스화 가능하다.
function startApp() {
var cryptoZombiesAddress = "YOUR_CONTRACT_ADDRESS";
cryptoZombies = new web3js.eth.Contract(cryptoZombiesABI, cryptoZombiesAddress);
}
컨트랙트에서 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);
});
}
나머지 함수들도 블록이 포함될떄와 에러가 발생하였을떄를 처리해주는 것이다.
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);
만약 계정이 바뀌게 되었을떄를 처리해주는 부분이다.
나도 자세하게는 모르겠다...ㅠㅠㅠ
완전 처음해보는 작업이다 보니 확실하게 아는게 아니라서 구체적으로 설명을 할수가 없었다..
나도 개인적으로는 아 그냥 이런게 있구나, 이런식으로 활용하는구나 라고 생각을 하게 되었다.