dukDukz
[블록체인] truffle React 사과 가게 연습 본문
# Dapp 을 만들거임
# Dapp -> truffle -> React
React 에서 deploy (배포)한 Smart Contract 내용을 가져오는 행위를 어떻게 작업했었는지 이미지 그려봐야함
배포한 컨트랙트 내용을 가져올 때 web3를 사용했다는 점
web3 에서 메타마스크에게 연결하는 방법
: 코드로 구현할 필요가 없음
truffle 이 알아서 해줌.
react unbox 하면
src > getWeb3.js 가 있는데 이걸 나중에 호출해서 사용할거임
Promise 객체를 반환해줌
window.ethereum 이 어케 되는걸까?? - 메타마스크 설치되어있는 크롬에서만 나오는..
즉 if(window.ethereum) 는 브라우져에 메타마스크가 있는지를 확인하는 것이다.
# Truffle
작업 공간까지 디렉토리 이동하고
$ npm install -g truffle
1.
$ truffle unbox react
2.
$ ganache
실행 후 메타마스크에 계정 연결 (100ETH)
3. truffle-config.js 파일 설정하기
development : {
host : "127.0.0.1",
port : 7545,
network_id : "*",
},
추가 (메타마스크 바라보게)
4. 내가 배포할 contract 코드를 작성하기
(아직은 생략함)
5. contract 를 작성했다고 가정하고 contract code를 compile 한다
$ truffle compile
client > src > contracts 안에 .json 파일들이 생성됨
json 파일안에 abi bytecode 들이 다 들어가있다.
6. migration 작업
contract 내용을 배포하기
$ truffle migrate
gas 가 발생하여 100ETH 에서 빠져나갔다. 99.99ETH 가 됨
transactions 탭에도 내용을 확인 할 수 있음
7. react 실행하기
$ cd client && npm run start
[인젝티드 web3 / injected web3]
user -> client 가 web3 통해서 접근 -> 메타마스크 중 어떤 계정과 연결
우리는 가나쉬 / 100ETH 갖고 있는 계정과 연결함
그럼 이 메타마스크는 수많은 블체가 연결되어있는 하나의 노드와 rpc 통신을 할 수 있음
네트워크와 통신이 되어야 지갑의 기능을 할 수 있음..
src > App.js
컴파일을 진행하면 생기는 .json 파일을 import 해온다.
-> 두가지 값을 가져옴
state 상태...
componentDidMount
= useEffect [] 와 같은거임
rpc 통신
const accounts = await web3.eth.getAccounts();
=> 10개의 계정들이 나옴
const networkId = await web3.eth.net.getId();
=> 우리는 networkId "*" 로 함.. 5777 임
const deployedNetwork = SimpleStorageContract.networks[networkId];
SimpleStorageContract.json 파일에서
"networks": {
"5777": {
"events": {},
"links": {},
"address": "0xF3833bB61AF21BFEC9324Ea7a62Ef8d5622DB4D9",
"transactionHash": "0xfee2c7d25811e46a5488d5cef188300564d6daf8ad83c4ed5244e9fbd6f9a840"
}
},
이 부분을 넣은건데..
const instance = new web3.eth.Contract(
SimpleStorageContract.abi, // abi 값을 넣어준거임
deployedNetwork && deployedNetwork.address, // 위에 주소값을 넣는다.
);
생략했던 4번
원하는 contract code 작성하기를 해보자
clinet > src > contracts 안에 있는 두 json 파일 다 지우기
contracts > SimpleStorage.sol 지우기
migrations > 2_ 어쩌구 지우기
$ truffle create contract Fruitshop
-> contracts 폴더 안에 .sol 파일 생김
$ truffle create migration Fruitshop
-> migrations 폴더 안에 js 파일 생김
[숫자_fruitshop.js]
var Fruitshop = artifacts.require("./Fruitshop.sol");
module.exports = function(_deployer) {
// Use deployer to state migration tasks.
_deployer.deploy(Fruitshop);
};
이렇게 작성해줌
그 다음에 Fruitshop.sol 로 와서
1. 보낸 사람의 계정에서 사과를 총 몇 개 갖고 있는가
주소별로 사과를 저장할 수 있도록 mapping으로 선언
mapping(address=>uint) myApple;
2. 사과를 구매했을 시, 해당 계정(주소)에 사과를 추가해주는 코드 작성
msg.sender : contract를 요청한 사람의 주소를 담고 있는 내장 객체
function buyApple() public{
myApple[msg.sender]++; // 초기화 값이 0 인데 이거를 1로 만들어줌
}
3. 사과를 판매시 내가 갖고 있는 사과 * 사과 구매 가격 만큼 토큰을 반환 해주고 사과를 0개로 바꿔준다.
function sellApple(uint _applePrice) payable external{
uint totalPrice = (myApple[msg.sender] * _applePrice);
// 내가 갖고 있는 사과 * 가격
myApple[msg.sender] = 0; // 사과 0으로 초기화
msg.sender.transfer(totalPrice); // 환볼 느낌
}
payable : 토큰 거래가 가능한 함수라는 뜻
4. 내 사과를 반환해주는 함수
function getMyApple() public view returns(uint){
return myApple[msg.sender];
}
이제 compile / migrate 하면 됨
$ truffle compile
client > public >src > Fruitshop.json 이 생성됨
$ truffle migrate
migrations > 숫자_fruitshop.js 생성됨
App.js 에 와서
import 부분 중에 SimpleStorage 가 존재하지 않으니 npm run start 가 되지 않을 것이다.
이 부분을 고쳐보자
App 컴포넌트 다 지우고
import FruitshopContract from "./contracts/Fruitshop.json";
이렇게 바꿔준다.
함수형 컴포넌트를 만든다
const [myApple,setMyApple] = useState(0)
const buyApple = () =>{
setMyApple(prev => prev+1)
}
const sellApple = () =>{
setMyApple(0)
}
return(
<div>
<h1>사과 가격 : 10 ETH</h1>
<button onClick={()=>buyApple()}>BUY</button>
<p>내가 가지고 있는 사과 : {myApple}</p>
<button onClick={()=>sellApple()}>SELL (판매 가격은 : {myApple * 10} ETH)</button>
</div>
)
이게 잘 되는 지 확인
clinet 안에서
$ npm run start
잘 됨
추가로 componentDidMount
-> Web3 가져와서 메타마스크 연결 할거임
useEffect(()=>{
},[])
이 부분 추가함
getWeb3 가져오는거 해보자
import getWeb3 from "./getWeb3";
얘의 return 값은 Promise 객체임 그래서 받을때
then 아니면 async await 로 받아야 함
const getweb = async ()=>{
let web3 = await getWeb3()
console.log(web3);
}
// componentDidMount Web3 가져와서 메타마스크 연결 할거임
useEffect(()=>{
getweb()
},[])
8. @truffle/contract 설치 및 사용
$ npm install @truffle/contract
디렉토리가 client 안에 들어가 있는거 확인하고 npm install 해야 한다.
@truffle/contract 검색해보면
얘를 쓰면서 코드가 깔끔하게 나오기 때문에
@truffle/contract 를 사용한다고 보면 된다.
이렇게 사용하면 json 파일 안에 해당 내용들이 다 들어가게 된다.
그리고 거기에 대한 결과 값을
const getweb = async ()=>{
const contract = require("@truffle/contract")
let web3 = await getWeb3()
let fruitshop = contract(FruitshopContract)
fruitshop.setProvider(web3.currentProvider)
let instance = await fruitshop.deployed()
console.log(instance);
}
# 주소 가져오기
// 계정(address) 가져오기
let accounts = await web3.eth.getAccounts()
# instance
let instance = await fruitshop.deployed()
얘는 지역변수라서 상태저장을 해야함
reducer 를 사용해서 위에 함수에서도 얘를 사용할 수 있게 하자
# instance, address, web3 상태에 저장하기
let initalState = {web3:null, instance:null, account:null}
const [state, dispatch] = useReducer(reducer,initalState)
function reducer(state,action){
switch(action.type){
case "INIT":
let {web3,instance,account} = action
return{
...state,
web3,
instance,
account
}
}
}
let InitActions = {
type : 'INIT',
web3,
instance,
accounts:accounts[0]
}
dispatch(InitActions)
상태값 들어가있나 확인하고
# 사과 사기
const buyApple 부분 작업
function buyApple() payable public{
// msg.sender : contract를 요청한 사람의 주소를 담고 있는 내장 객체
myApple[msg.sender]++; // 초기화 값이 0 인데 이거를 1로 만들어줌
}
payable 붙여주고
json 파일 날리고
$ truffle compile
$ truffle migrate
$ npm run start
하고
구매 누르면 거래 발생하고 확인 누르면 이더 빠져나감
그럼 이 빠진 이더는 어디로 가는가?
0x3f16e8389b9fA7d51dE9470d96B17a7CA51d87b3
이 주소로 간다...
이게 무슨 주소일까??
배포하고 나온 결과물의 주소
코드가 올라간 곳의 주소 (코드가 담긴 위치)
근데 새로고침하면 없어짐ㅜㅜ
근데 실제로 내가 이더를 주고 산건데...
이 부분을 고쳐주자
# 새로고침해도 상태값 유지되도록
App.js
getweb 부분. useEffect 부분을 보자
현재 내가 갖고 있는 사과를 리턴해주는 함수를 만든다.
const getApple = async ()=>{
if(instance == null) return
let result = await instance.getMyApple()
setMyApple(result.toNumber())
}
getweb 안에서
getApple(instance)
buyApple 안에서
let {instance, account, web3} = state;
await instance.buyApple({
from : account,
value : web3.utils.toWei("10","ether"), //wei
gas : 90000,
})
sellApple 하기
파는게 안됐는데 Fruitshop.sol
function sellApple(uint _applePrice) payable external{
이렇게 external로 바꿔주고 다시 빌드하니까 됨
Fruitshop.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
/*
1. 보낸 사람의 계정에서 사과를 총 몇개 갖고 있는가
2. 사과를 구매했을 시, 해당 계정(주소)에 사과를 추가함
3. 사과를 판매시 내가 갖고 있는 사과 * 사과 구매 가격 만큼 토큰을 반환해주고
사과를 0개로 바꿔준다.
4. 내 사과를 반환해주는 함수
*/
contract Fruitshop {
mapping(address=>uint) myApple; // 주소별로 사과를 저장할 수 있도록 mapping으로 선언
constructor() public {
}
function buyApple() payable public{
// msg.sender : contract를 요청한 사람의 주소를 담고 있는 내장 객체
myApple[msg.sender]++; // 초기화 값이 0 인데 이거를 1로 만들어줌
}
function getMyApple() public view returns(uint){
return myApple[msg.sender];
}
function sellApple(uint _applePrice) payable external{
uint totalPrice = (myApple[msg.sender] * _applePrice);
// 내가 갖고 있는 사과 * 가격
myApple[msg.sender] = 0; // 사과 0으로 초기화
msg.sender.transfer(totalPrice); // 환볼 느낌
}
}
/*
truffle version 해보면 해석기는 ver 5 이다.
근데 vscode 는 ver 8 이라서 여기서 나는 오류가 해설할때는 안날 수 있다.
solcjs --version
이건 vscode ver
*/
App.js
import React, { Component, useReducer } from "react";
import FruitshopContract from "./contracts/Fruitshop.json";
import getWeb3 from "./getWeb3";
import { useState, useEffect } from "react";
import "./App.css";
const App = () => {
const [myApple,setMyApple] = useState(0)
let initalState = {web3:null, instance:null, account:null}
const [state, dispatch] = useReducer(reducer,initalState)
function reducer(state,action){
switch(action.type){
case "INIT":
let {web3,instance,account} = action
return{
...state,
web3,
instance,
account
}
}
}
const buyApple = async () =>{
//instance 값 가져와야 함
let {instance, account, web3} = state;
await instance.buyApple({
from : account,
value : web3.utils.toWei("10","ether"), //wei
gas : 90000,
})
setMyApple(prev => prev+1) // 블록에 있는 내용을 가져와서 뿌리는걸로 수정해보기 + 숫자 조정해보기
}
const sellApple = async () =>{
let {instance, account, web3} = state
await instance.sellApple(web3.utils.toWei("10","ether"),{
from : account,
gas : 90000,
})
setMyApple(0)
}
// 현재 내가 갖고 있는 사과를 리턴해주는 함수를 만든다.
const getApple = async (instance)=>{
if(instance == null) return
let result = await instance.getMyApple()
setMyApple(result.toNumber())
}
const getweb = async ()=>{
const contract = require("@truffle/contract")
let web3 = await getWeb3()
let fruitshop = contract(FruitshopContract)
fruitshop.setProvider(web3.currentProvider)
let instance = await fruitshop.deployed()
// 계정(address) 가져오기
let accounts = await web3.eth.getAccounts()
let InitActions = {
type : 'INIT',
web3,
instance,
account:accounts[0]
}
dispatch(InitActions)
getApple(instance)
// 내 계정 : 0xE6BE9186bC5f7Dbc5862F3bdC87AD9c3B042acBD
// account : ['0xE6BE9186bC5f7Dbc5862F3bdC87AD9c3B042acBD']
}
// componentDidMount Web3 가져와서 메타마스크 연결 할거임
useEffect(()=>{
getweb()
},[])
return(
<div>
<h1>사과 가격 : 10 ETH</h1>
<button onClick={()=>buyApple()}>BUY</button>
<p>내가 가지고 있는 사과 : {myApple}</p>
<button onClick={()=>sellApple()}>SELL (판매 가격은 : {myApple * 10} ETH)</button>
</div>
)
}
export default App;
'웹 개발 > 블록체인' 카테고리의 다른 글
[블록체인] 서명하는 방법 3가지 - 수업 ver (0) | 2021.10.18 |
---|---|
[블록체인] Dapp 사용의 전반적인 흐름 (0) | 2021.10.18 |
[블록체인] 메타마스크 사용 연습 (0) | 2021.10.13 |
[블록체인] 가나쉬 사용 -1 (0) | 2021.10.13 |
[블록체인] truffle 에서 react 사용하기 - 1 (0) | 2021.10.13 |