오늘은 truffle, ganache를 이용해 숫자 카운터 컨트랙트를 만들고 배포해보자.
그리고 이를 리액트를 이용한 웹 페이지에서 메타마스크를 통해 접근할 수 있도록 하는
디앱화 해보자.
1. 컨트랙트 생성, 배포 (#22 참조)
루트 디렉토리에 truffle 폴더를 만들고, 여기서 컨트랙트를 작성, 컴파일, 배포한다.
npx truffle init
생성된 contracts 폴더에 다음과 같이 counter 컨트랙트를 만든다.
/* truffle/contracts/Counter.sol */
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
contract Counter {
uint256 private _count;
function current() public view returns (uint256) {
return _count;
}
function increment() public {
_count += 1;
}
function decrement() public {
_count -= 1;
}
}
이제 ganache 네트워크를 구동한다.
npx ganache-cli
ganache 네트워크가 구동되면 이를 메타마스크에 추가하고, 0번 계정을 불러온다.
이제 truffle에서 컨트랙트를 컴파일 하고 배포한다. ( #22 참조 )
/* truffle/migrations/2_deploy_Counter.js */
const Counter = artifacts.require("Counter");
// 파일의 이름이 아닌 컨트랙트의 이름을 가져와야 한다.
module.exports = function (deployer) {
deployer.deploy(Counter);
};
truffle compile
truffle migration
이 후, truffle console로 들어가 컨트랙트를 호출한다.
truffle console
Counter.deployed().then(instance => it = instance)
이제 컨트랙트 내부의 함수를 실행해보면 된다.
it.current()
// 0
it.increment()
it.current()
// 1
잘 배포가 되었고, 문제 없이 동작한다면 이제 이를 리액트와 메타마스크를 통해 동작시켜보자.
2. 리액트를 이용해 ui 만들기
루트디렉토리에서 리액트 앱을 만들고 web3 라이브러리를 설치한다.
npm i web3
create-react-app front
2.1 메타마스크와 블록체인 네트워크 연결
커스텀 훅을 이용해 account와 web3의 상태를 app.jsx (app.js의 확장자를 바꾼다)에 전달해줄 것이다.
/* front/src/hooks/useWeb3.jsx */
import { useEffect, useState } from 'react'
import Web3 from 'web3/dist/web3.min'
const useWeb3 = () => {
const [ account, setAccount ] = useState(null)
const [ web3, setWeb3 ] = useState(null)
useEffect(() => {
(async () => {
if(!window.ethereum) return
// 메타마스크가 설치되어 있지 않다면 함수 종료
const [ address ] = await window.ethereum.request({
method : 'eth_requestAccounts'
})
setAccount(address)
const web3 = new Web3(window.ethereum)
setWeb3(web3)
// 메타마스크가 설치되어 있다면 메타마스크와
// 메타마스크 안에 등록된 계정을 기반으로 web3, account 값 변경
})();
}, [])
return [ web3, account ]
}
export default useWeb3;
이는 네트워크 상의 컨트랙트와는 그다지 관계가 없고, 메타마스크를 통해 네트워크와 연결해주기 위함이다.
2.2 컨트랙트 랜더링
이제 우리가 배포한 컨트랙트를 불러올 컴포넌트를 만들어준다.
이 때, 블록체인 네트워크에서 원하는 컨트랙트를 가져오려면 abi와 ca가 필요한데,
아까 truffle에서 compile시 생성된 json 파일안에 그 데이터가 저장되어 있다.
이 json파일을 가져오는데, Contracts라는 폴더를 src 폴더에 만들고, 그 안에 json 파일을 복사해 넣어준다.
/* front/src/Contracts/Counter.json */
// truffle/build/contracts/Counter.json을 복사 붙여넣기 한다.
이제 카운터 컴포넌트를 작성한다.
/* front/src/components/Counter.jsx */
import React, { useState, useEffect } from 'react';
import CounterContract from '../Contracts/Counter.json'
const Counter ({web3, account}) => {
const [ count, setCount ] = useState(0)
const [ deployed, setDeployed ] = useState()
useEffect(() => {
(async () => {
if(deployed) return
const Deployed = new Web3.eth.Contract(
CounterContract.abi,
"0xA87bB5c6174d6CE1517fF5664b735720CD62A691"
// CA를 써주면 된다
)
// abi, CA를 통해 블록체인 네트워크 상에 존재하는 컨트랙트를
// 특정해 가져온다.
const count = await Deployed.methods.current().call()
// 컨트랙트의 현재 숫자값을 가져온다.
setCount(count)
// 리액트의 상태를 컨트랙트의 현재 숫자로 바꿔준다
setDeployed(Deployed)
// deployed의 상태를 컨트랙트로 바꿔준다.
})()
},[])
return (
<div>
<h2> Counter : { count } </h2>
<button> + </button>
<button> - </button>
</div>
)
}
export default Counter;
다음으로 App.js 파일을 App.jsx 파일로 확장자만 바꿔준 후, 컴포넌트와 훅의 값을 가져온다.
/* front/src/App.jsx */
import React from 'react'
import useWeb3 from './hooks/useWeb3'
import Counter from './components/Counter'
const App = () => {
const [ web3, account ] = useWeb()
if(!account) return 'metamask로 로그인 해주세요'
return (
<div>
<span> Account : {account} </span>
<Counter web3={web3} account={account}/>
</div>
)
}
export default App;
이제 Counter 컴포넌트에서 +, - 버튼을 클릭했을 때 각각 컨트랙트의 증가, 감소 함수를 발동하게끔 이벤트를 넣어준다.
/* front/src/components/Counter.jsx */
// ...중략
const Counter = ({web3, account}) => {
// ...중략
const increment = async () => {
// 이 increment는 컴포넌트 내에서 선언한 함수이다.
const result = await deployed.methods.increments().send({
// 이 increment는 컨트랙트 내에 존재하는 함수를 호출하는 것이다.
from : account
})
if( !result ) return
const current = await deployed.methods.current().call()
// 함수 호출 후, 바뀐 count값을 다시 가져온다
setCount(current)
// 컴포넌트의 count 변수의 상태를 바꿔준다.
}
const decrement = async () => {
const result = await deployed.methods.decrement().send()({
from : account
})
if(!result) return
const current = await deployed.methods.current().call()
setCount(current)
}
useEffect(( => {
// ...중략
}, [])
return (
<div>
<h2> Counter : {count} </h2>
<button onClick = {increment}>+</button>
<button onClick = {decrement}>-</button>
</div>
)
}
export default Counter;
이렇게 하고 +, - 버튼을 클릭할 경우, 컨트랙트 내의 함수를 발동하기 위해 메타마스크에서 서명을 요구하는 창이 뜬다.
이를 승인하면 계정에서 eth가 조금 빠져나가는 동시에 컨트랙트 내부ㅠ의 count 값이 바뀌는 것을 확인할 수 있다.
그런데 이 경우 내가 업데이트한 컨트랙트의 여러 가지 데이터들을 다른 사람들은 실시간으로 업데이트 받지 못한다는
문제가 있다.
이런 문제를 보완하기 위해서는 웹소켓 통신의 도움을 받아야 하는데, 이는 다음 글에서 알아보자.
'Blockchain' 카테고리의 다른 글
#25 메인 네트워크에서 작동하는 토큰 만들기 (0) | 2022.07.20 |
---|---|
#24 다른 네트워크 참여자의 상태 업데이트 (0) | 2022.07.14 |
#22 Truffle을 이용한 컨트랙트 배포 (0) | 2022.07.12 |
#21 smart contract (0) | 2022.07.11 |
#20 개인 네트워크 만들기 (geth) (0) | 2022.07.03 |