- Truffle : mkdir truffle -> cd truffle -> npx truffle init
- Front : create-react-app- front
- 블록체인 네트워크 : npx ganache-cli
Ganache
로 실행한 지갑 주소와 개인키를 미리 복사해놓습니다.
- 지갑 1 : 0xe75E29893E906eD60BF47FfDA91dCFCf3035069d
- 개인키 1 : 0x6f73aae168847459a07b139b18f0eddc2459c33720ecc705fabd0bd62974963d
Counter.sol
파일 생성
pragma solidity ^0.8.15;
contract Counter {
uint256 private _count;
// public이 아닌 private기 때문에 _count 상태변수를 가져오는 함수를 만들어주었습니다.
function current() public view returns(uint256){
return _count
}
function increment() public {
_count += 1;
}
function decrement() public {
_count -= 1;
}
}
Solidity Code
로 상태변수count
를 가져올getter
함수와 상태변수를 변경할setter
함수들을 작성합니다.
truffle
디렉토리로 이동 및 npx truffle init
truffle config.js
환경설정 세팅 ( development
주석 풀기 )truffle complie
로 컴파일링 실행 => build
디렉토리에 솔리디티 코드를 컴파일링한 json
파일을 확인합니다.migrations
디렉토리에 2_deploy_Counter.js
파일 생성 => migration 디렉토리 안에 불러온 코드들을 배포할것입니다.truffle migration
으로 배포 -> 메타마스크의 지갑에서 Transaction을 발생하여 가스비가 빠져나간것을 확인할 수 있습니다.truffle console
이동 => Counter
입력하면 abi
및 bytecode
내용이 담겨져 있는 객체를 보여줍니다.Counter.deployed().then(instance => it = instance)
then
에 있던 값을 전역에서 사용하도록 변수에 담아줍니다.it
인스턴스안에 methods
에 배포한 smart contract
가 담겨있는 것을 확인할 수 있습니다.it.current.call()
을 하면 BN { negative : 0, words: [ 0, <1 empty item> ] ~~~ }
이런 값이 나오는데 words
의 첫번째 index
의 있는 값이 count 값입니다.it.increment()
-> 상태변수를 바꾸는 함수이기 때문에 Tx을 발생시킵니다.Ganache
에서는 Transaction
발생시 자동으로 mining
이 되기 때문에 mining
을 해주지 않아도 됩니다.it.current.call()
을 해보면 값이 0 -> 1로 바뀐것을 확인할 수 있습니다.const Counter = artifacts.require('Counter');
describe('Counter Test', () => {
let counter;
it('Counter deployed', async () => {
counter = await Counter.deployed(); // 배포된 결과물을 가져옴
console.log(counter);
});
it('get current', async () => {
console.log(await counter.current.call()); //return _count
});
it('increment', async () => {
await counter.increment(); // TxHash값과 TxReceipt값이 나옴
const result = await counter.current.call(); // return _count
console.log(result.toNumber()); // 1
});
it('decrement', async () => {
await counter.decrement(); // TxHash값과 TxReceipt값이 나옴
const result = await counter.current.call(); // return _count
console.log(result.toNumber()); // 0
});
});
// CA : '0x1F1F1B55CAAD018D1efCd34fedecB165b74b6413'
truffle test
로 테스트 코드를 실행하여 정상적으로smart contract
들이 동작하는지 확인합니다.const Counter = artifacts.require('Counter');
artifacts.require()
를 사용하여 사용할 계약 인스턴스를 가져올 수 있습니다.
Transaction
을 일으키려면Private Key
가 필요한데 프론트에 개인키를 두고 작업하기에는 보안상 문제가 있으니MetaMask
같은 지갑을 사용할 것입니다.
useWeb3 CustomHook
)
npm install web3
/* useWeb3.jsx */
import { useEffect, useState } from 'react';
import Web3 from 'web3/dist/web3.min'; // 프론트에서는 최소한의 라이브러리 내용만 가져옵니다.
const useWeb3 = () => {
const [account, setAccount] = useState('');
const [web3, setWeb3] = useState(null);
useEffect(() => {
(async () => {
if (!window.ethereum) return;
// Front와 MetaMask 지갑 연결하기
const [address] = await window.ethereum.request({
method: 'eth_requestAccounts',
});
// 지갑 주소가져온후 Account 상태 변경
setAccount(address);
// 메타마스크와 Front web3 연결
const web3 = new Web3(window.ethereum);
// 연결한 web3로 상태변경
setWeb3(web3);
})();
}, []);
return [web3, account];
};
export default useWeb3;
Front
에서MetaMask
는 연결은 지난 시간에 해보았기에 자세한 설명은 생략하겠습니다.
/* App.jsx */
import React from 'react';
import useWeb3 from './hooks/useWeb3';
import Counter from './components/Counter';
const App = () => {
const [web3, account] = useWeb3();
if (!account) return <>메타마스크 연결이 필요합니다.</>;
return (
<div>
<span>Account : {account} </span>
<Counter web3={web3} account={account} />
</div>
);
};
export default App;
App
함수형 컴포넌트가 실행될때 아래와 같이MetaMask
와 지갑이 연동이 안되어 있을경우 메타마스크 연결이 필요하다는 메세지가 보입니다.
- 지갑을 연결하면 아래와 같이 해당 지갑의 주소와
Counter
컴포넌트를props
로web3
와account
를 전달해준 상태로 render합니다.
/* Counter.jsx*/
import { useState, useEffect } from 'react';
import CounterContract from '../contracts/Counter.json';
// props로 전달받은 web3와 account를 받아서 사용합니다.
const Counter = ({ web3, account }) => {
const [count, setCounte] = useState(0);
const [deployed, setDeployed] = useState();
useEffect(() => {
(async () => {
if (deployed) return;
const deploy = await new web3.eth.Contract(CounterContract.abi, '0x1F1F1B55CAAD018D1efCd34fedecB165b74b6413'); // 2가지 인자값 abi ,CA
const current = await deploy.methods.current().call();
setDeployed(deploy);
setCounte(current);
})();
}, []);
const increment = async () => {
await deployed.methods.increment().send({ from: account });
const current = await deployed.methods.current().call();
setCounte(current);
};
const decrement = async () => {
await deployed.methods.decrement().send({ from: account });
const current = await deployed.methods.current().call();
setCounte(current);
};
return (
<div>
<h2>Counter : {count} </h2>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
};
export default Counter;
import CounterContract from '../contracts/Counter.json';
위에서Truffle
로compile
하고 배포한contract
내용을 가져옵니다.Counter
컴포넌트의props
로 전달받은web3
와account
를 구조분해 할당으로 받아 사용할 것입니다.ComponentDidMount
가 되면useEffect
안에 즉시 실행 함수가 실행되며 처음에는deployed
가null
값이니Ganache
네트워크와 연결된web3.eth.Contract()
메서드를 사용하여 인자값으로abi
파일과 배포한Contract
의CA
값을 넣어methods
에 배포한contract
들이 추가된deploy
인스턴스를 생성합니다.- 이후
deploy
의methods
에 있는current().call()
을 하여count
값을 가져와current
변수에 할당하고setCounte
로 상태를 변경합니다. 또한deploy
인스턴스도 상태에 넣어 다른 함수에서도 사용할 수 있도록 해주었습니다.- 마지막으로
+
-
버튼에onClick
이벤트를 넣고 각각increment
와decrement
함수를 만들어+
혹은-
버튼을 누르면deployed
메서드안에increment
혹은decrement
contract
가 실행되어Transaction
을 발생시키고 MetaMask에 연결한 지갑에서 가스 수수료를 지불하고 난후Ganache
네트워크에서 블록을 자동으로 생성해주어 해당Transaction
의contract
가 실행되어 상태변수를 바꿔줄것입니다. 값이 바뀌면 다시current().call()
메서드를 실행하여 바뀐값을 가져와setCounte
로 상태를 바꿔주었습니다.
- 버튼을 눌러 상태변수를 바꿀것이기에 가스 수수료를 지불해야 합니다.
가스 수수료를 지불하면
Transaction
이Transaction Pool
에 담기고Ganache
네트워크에서 자동으로 블록을 생성해주어 바로Contract
가 실행되며Count
값이 +1이 됩니다.