truffle unbox react
ganache 실행 후 metamask 연결
truffle-config.js 내용 수정 // 이전과 같이
/contracts/SimpleStorage.sol 코드 수정
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.21 <0.7.0;
contract SimpleStorage {
uint storedData;
event Change(string message, uint newVal); // 추가
function set(uint x) public {
storedData = x;
emit Change('set', x); // 추가
}
function get() public view returns (uint) {
return storedData;
}
}
truffle compile
truffle migrate
/client/src/App.js 코드 수정
import React, { useState } from "react";
import SimpleStorageContract from "./contracts/SimpleStorage.json";
import getWeb3 from "./getWeb3";
import "./App.css";
const App = () => {
const [value, setValue] = useState(0);
const [storage, setStorage] = useState(0);
const [loading, setLoading] = useState(false);
// 직접 서명
const send = () => {
}
// DB(Back-end) 거치고 서명
const sendAPI = () => {
}
// DB 서명
const sendTx = () => {
}
const handleChange = e => {
const val = e.target.value;
setValue(val);
}
return (
<div style={{padding:"20px"}}>
<input type="text" value={value} onChange={handleChange} />
<div>
<button onClick={send}>직접 서명</button>
<button onClick={sendAPI}>DB 거치고 서명</button>
<button onClick={sendTx}>DB 서명</button>
</div>
<div>
{loading ? '로딩 중' : storage}
</div>
</div>
);
}
export default App;
화면 구성
npm install @truffle/contract // 서버 종료 후 패키지 설치
로딩 상태 변수로 선언하여 처리, 설치 한 패키지 가져오기, Web3와 contract 연결 (이전 포스트 참조, 링크 : https://hwan91.tistory.com/12 )
import React, { useEffect, useState } from "react";
import SimpleStorageContract from "./contracts/SimpleStorage.json";
import getWeb3 from "./getWeb3";
import "./App.css";
const App = () => {
const [value, setValue] = useState(0);
const [storage, setStorage] = useState(0);
const [loading, setLoading] = useState(false);
const handleResult = () => {
setStorage(value);
setLoading(prev => !prev);
}
// 직접 서명
const send = () => {
if (value > 0) {
setLoading(prev => !prev);
handleResult(); // 비동기적 처리
}
}
// DB(Back-end) 거치고 서명
const sendAPI = () => {
if (value > 0) {
setLoading(prev => !prev);
handleResult(); // 비동기적 처리
}
}
// DB 서명
const sendTx = () => {
if (value > 0) {
setLoading(prev => !prev);
handleResult(); // 비동기적 처리
}
}
const handleChange = e => {
const val = e.target.value;
setValue(val);
}
const init = async () => {
const contract = require('@truffle/contract');
const web3 = await getWeb3();
const [account] = await web3.eth.getAccounts();
// const networkId = await web3.eth.net.getId(); // network id값 가져오기, 참고
let simpleStorage = contract(SimpleStorageContract);
simpleStorage.setProvider(web3.currentProvider);
const instance = await simpleStorage.deployed();
console.log(web3);
console.log(account);
console.log(instance);
}
useEffect(() => {
init();
}, []);
return (
<div style={{padding:"20px"}}>
<input type="text" value={value} onChange={handleChange} />
<div>
<button onClick={send}>직접 서명</button>
<button onClick={sendAPI}>DB 거치고 서명</button>
<button onClick={sendTx}>DB 서명</button>
</div>
<div>
{loading ? '로딩 중' : storage}
</div>
</div>
);
}
export default App;
차례대로 console.log(web3, account, instance);
reducer와 dispatch를 사용하여 상태 관리
import React, { useEffect, useReducer, useState } from "react";
import SimpleStorageContract from "./contracts/SimpleStorage.json";
import getWeb3 from "./getWeb3";
import "./App.css";
const reducer = (state, action) => {
switch (action.type) {
case "INIT":
let { web3, instance, account } = action;
return {
...state,
web3,
instance,
account,
}
}
}
const INIT_ACTIONS = (web3, instance, account) => {
return {
type: 'INIT',
web3,
instance,
account,
}
}
const App = () => {
const initialState = {
web3: null,
instance: null,
account: null,
}
const [value, setValue] = useState(0);
const [storage, setStorage] = useState(0);
const [loading, setLoading] = useState(false);
const [state, dispatch] = useReducer(reducer, initialState);
const handleResult = () => {
setStorage(value);
setLoading(prev => !prev);
}
// 직접 서명
const send = () => {
if (value > 0) {
setLoading(prev => !prev);
handleResult(); // 비동기적 처리
}
}
// DB(Back-end) 거치고 서명
const sendAPI = () => {
if (value > 0) {
setLoading(prev => !prev);
handleResult(); // 비동기적 처리
}
}
// DB 서명
const sendTx = () => {
if (value > 0) {
setLoading(prev => !prev);
handleResult(); // 비동기적 처리
}
}
const handleChange = e => {
const val = e.target.value;
setValue(val);
}
const init = async () => {
const contract = require('@truffle/contract');
const web3 = await getWeb3();
const [account] = await web3.eth.getAccounts();
// const networkId = await web3.eth.net.getId(); // network id값 가져오기, 참고
let simpleStorage = contract(SimpleStorageContract);
simpleStorage.setProvider(web3.currentProvider);
const instance = await simpleStorage.deployed();
// console.log(web3);
// console.log(account);
// console.log(instance);
dispatch(INIT_ACTIONS(web3, instance, account));
}
useEffect(() => {
init();
}, []);
return (
<div style={{padding:"20px"}}>
<input type="text" value={value} onChange={handleChange} />
<div>
<button onClick={send}>직접 서명</button>
<button onClick={sendAPI}>DB 거치고 서명</button>
<button onClick={sendTx}>DB 서명</button>
</div>
<div>
{loading ? '로딩 중' : storage}
</div>
</div>
);
}
export default App;
상태값 확인
직접 서명에 대한 코드 작성 콘솔 출력 테스트 // 5000 입력, 직접 서명 버튼 클릭 후 MetaMaskNontification에서 확인
import React, { useEffect, useReducer, useState } from "react";
import SimpleStorageContract from "./contracts/SimpleStorage.json";
import getWeb3 from "./getWeb3";
import "./App.css";
const reducer = (state, action) => {
switch (action.type) {
case "INIT":
let { web3, instance, account } = action;
return {
...state,
web3,
instance,
account,
}
}
}
const INIT_ACTIONS = (web3, instance, account) => {
return {
type: 'INIT',
web3,
instance,
account,
}
}
const App = () => {
const initialState = {
web3: null,
instance: null,
account: null,
}
const [value, setValue] = useState(0);
const [storage, setStorage] = useState(0);
const [loading, setLoading] = useState(false);
const [state, dispatch] = useReducer(reducer, initialState);
const handleResult = (log, web3) => {
// data : 0x0000000000000000000
// decodeLog의 인자값 >> 1: 데이터 형식(params), 2: log.data
const params = [ // .sol 파일에 작성했던 event Change의 인자값 내용과 동일
{
type: 'string',
name: 'message',
},
{
type: 'uint256',
name: 'newVal',
},
];
const returnValues = web3.eth.abi.decodeLog(params, log.data);
console.log(returnValues);
// case가 많을 경우 switch문으로 작성
if (returnValues.message == 'set') {
setStorage(value);
setLoading(prev => !prev);
}
}
// 직접 서명
const send = async () => {
const { account, instance } = state;
if (value > 0) {
setLoading(prev => !prev);
await instance.set(value, { from: account });
// handleResult(); // 비동기적 처리
}
}
// DB(Back-end) 거치고 서명
const sendAPI = () => {
if (value > 0) {
setLoading(prev => !prev);
// handleResult(); // 비동기적 처리
}
}
// DB 서명
const sendTx = () => {
if (value > 0) {
setLoading(prev => !prev);
// handleResult(); // 비동기적 처리
}
}
const handleChange = e => {
const val = e.target.value;
setValue(val);
}
const init = async () => {
const contract = require('@truffle/contract');
const web3 = await getWeb3();
const [account] = await web3.eth.getAccounts();
// const networkId = await web3.eth.net.getId(); // network id값 가져오기, 참고
let simpleStorage = contract(SimpleStorageContract);
simpleStorage.setProvider(web3.currentProvider);
const instance = await simpleStorage.deployed();
// console.log(web3);
// console.log(account);
// console.log(instance);
dispatch(INIT_ACTIONS(web3, instance, account));
// websocket 사용과 비슷
web3.eth.subscribe('logs', { address: instance.address }) // 배포가 된, contract 계정의 address
.on('data', log => {
console.log(log);
handleResult(log, web3);
})
.on('error', err => {
console.log(err);
})
}
useEffect(() => {
init();
}, []);
return (
<div style={{padding:"20px"}}>
<input type="text" value={value} onChange={handleChange} />
<div>
<button onClick={send}>직접 서명</button>
<button onClick={sendAPI}>DB 거치고 서명</button>
<button onClick={sendTx}>DB 서명</button>
</div>
<div>
{loading ? '로딩 중' : storage}
</div>
</div>
);
}
export default App;
console.log(log);
console.log(returnValues);
npm install axios // 다음 작업에 필요한 패키지 설치 후 import로 가져오기
root 디렉토리에서 npm init root 디렉토리에서 server 폴더 생성, 그 안에 아래와 같은 구조로 폴더 및 파일 생성
/server/server.js 코드 작성
const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const cors = require('cors');
const router = require('./routes');
app.use(cors());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
app.use('/', router);
app.listen(3001, () => {
console.log(`server on port 3001`);
});
/server/routes/index.js 코드 작성
const express = require('express');
const router = express.Router();
const rpc = require('./rpc');
router.use('/rpc', rpc);
module.exports = router;
/server/routes/rpc/index.js 코드 작성
const express = require('express');
const router = express.Router();
const controller = require('./rpc_controller');
router.post('/set', controller.set);
router.post('/setTx', controller.setTx);
module.exports = router;
/server/routes/rpc/rpc_controller.js 코드 작성
const Web3 = require('web3');
const web3 = new Web3('http://localhost:8545');
const { abi } = require('../../../client/src/contracts/SimpleStorage.json');
const { address } = require('../../../client/src/contracts/SimpleStorage.json').networks['5777'];
const ethTx = require('ethereumjs-tx').Transaction;
// Back-end 거치고 서명
const set = async (req, res) => {
const { from, val } = req.body;
try {
const contract = await new web3.eth.Contract(abi, address);
const data = await contract.methods.set(val).encodeABI();
console.log('data : ', data);
let txObject = { // transaction == tx
from,
to: address, // contract address >> /client/src/contracts/SimpleStorage.json의 networks 객체안에 5777 안에 address.. 길어서 전역 변수로 뺌
data, // 배포한 set 메서드를 인자값을 넣은 set(10)으로 바꿔줘야함 >> set 메서드 사용을 위해 web3 사용
gasLimit: web3.utils.toHex(30000), // hex(16진수)로 변환
gasPrice: web3.utils.toHex(web3.utils.toWei('20', 'gwei')), // wei 단위로 작성 후 hex로 변환
}
res.json({
// msg: 'hello set',
success: true,
raxTx: txObject,
});
} catch (e) {
console.log(e);
res.json({
success: false,
});
}
}
// Back-end 서명
const setTx = async (req, res) => {
const { from, val } = req.body;
const privateKey = Buffer.from('fae7534c013c508e384b4d912bf258ea0ebdd18a636eef5a736f975b55c41d90', 'hex'); // '가나슈 0번 인덱스의 privatekey, + hex로 반환'
try {
const instance = await new web3.eth.Contract(abi, address);
const data = await instance.methods.set(val).encodeABI();
const txCount = await web3.eth.getTransactionCount(from);
console.log('data : ', data);
let txObject = {
nonce: web3.utils.toHex(txCount),
from,
to: address,
data,
gasLimit: web3.utils.toHex(30000),
gasPrice: web3.utils.toHex(web3.utils.toWei('20', 'gwei')),
}
// 서명 시 개인키가 필요, 따라서 비밀키(privateKey)가 필요
const tx = new ethTx(txObject);
console.log(tx);
tx.sign(privateKey);
const serializedTx = tx.serialize();
console.log(serializedTx.toString('hex'));
const txhash = await web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex'));
res.json({
// msg: 'hello setTx',
success: true,
txhash,
});
} catch (e) {
console.log(e);
res.json({
success: false,
});
}
}
module.exports = {
set,
setTx,
}
Postman 사용, POST /rpc/set, 빨간 네모 안에 JSON 설정을 하지 않아 console.log(data)에 대한 터미널 에러 발생 (아래)
에러 문구
/rpc/set에 대한 console.log(data); // 정상 출력
/rpc/set에 대한 Postman 결과값
POST /rpc/setTx
/rpc/setTx에 대한 console.log(tx);
rpc/setTx에 대한 console.log(serializedTx
/rpc/setTx에 대한 Post맨 결과값