3강 web만들기

YU YU·2021년 10월 27일
1

1. react.js 기본 설정

$ npm -g install create-react-app
을 통해 create-react-app을 설치한다.
$ create-react-app [프로젝트명]
을 입력하여 리액트 프로젝트를 만든다.

$ npm i web3를 통해 web3를 설치한다.
https://medium.com/valist/how-to-connect-web3-js-to-metamask-in-2020-fee2b2edf58a
에 어떻게 메타마스크를 web3에 연결하는지 잘 나와있다. 평상시 많이 바뀌므로 검색해서 쓰는 습관을 들이도록 하자.

이렇게쓰라고 나오는데 아무리 찾아봐도 안나온다.....

터미널 창에 다음과 같이 입력한다.
$ npx truffle console
$ web3.eth.getAccounts()

그러면 아무거나 나오는 값을 복사한다.
그리고 다음과 같이 입력한다.
$ web3.eth.sendTransaction({from:'방금 복사한 값',to:'현재 내 메타마스크',value:10000000000000000000})

  • MetaMask 지갑주소 알아내는 법

    먼저 ganache-cli로 했으니 localhost:8545로 맞추어준다.
    그 다음에 Account주소를 복사한다.
    그러면 다음과 같이

    10 eth가 생긴다.

1-2. App.js

function App() {

  

  const initWeb3 = async () =>{
    if(window.ethereum){

      var web3 = new Web3(window.ethereum);

      console.log('윈도우야 메타 마스크를 연결하니 이게 실행되네')
      console.log(web3)
      
      try{
        await window.ethereum.enable();
      }catch (error){
        console.log(`error ouccur ${error}`)
      }
    }

    //legacy dapp browers..
    else if(window.web3){
      //이게 실행이 됨
      var web3 = new Web3(Web3.curentProvider);
      // console.log(await web3.eth.getAccounts());
      


    }
    else{
      console.log('너 이더리움 없어 메타마스크라도 깔아라....')
    }

    let accounts = await web3.eth.getAccounts();//여기서 왜 안되지?
    console.log(accounts);
    // const Web3 = require("web3");
    // const ethEnabled = () => {
    //   if (window.web3) {
    //     let web3 = new Web3(window.web3.currentProvider);
    //     window.ethereum.enable();
    //     console.log(web3);
    //     return true;
    //   }
    // return false;
    // }
  }

  useEffect(async()=>{
    await initWeb3();
  },[]);

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p>
          Edit <code>src/App.js</code> and save to reload.
        </p>
        <a
          className="App-link"
          href="https://reactjs.org"
          target="_blank"
          rel="noopener noreferrer"
        >
          Learn React
        </a>
      </header>
    </div>
  );
}

export default App;

원래 여기서 최신 버전이 있으니 그걸 적용해서 하라고 했는데 아무리 찾아봐도 없는 것 같아 그냥 따라 적었다. 그러면서 메타마스크가 있다면 window.ethereum이 실행되는 것을 알 수 있었다. 그리고 되는 곳에서 var을 사용해줘야지만 지갑의 주소가 나오는 것을 알 수 있었다...ㅜㅜ

2. web3.js-send & call

2-1. 재배포

스마트 컨트랙트 폴더로 이동하여서 다시한번 스마트 컨트랙트를 다시 배포한다.
$ truffle migrate --reset

그러면 이러한 화면이 뜬다. 여기서 contract address를 복사하자.
0x43C466923FfFFa2d9B8a1Bbba294Fed9a3214b86
그리고 build>contracts>Lottery.json파일에서 abi 를 복사한다. 그리고 다음의 사이트에 접속한다. https://www.textfixer.com/tools/remove-line-breaks.php
그리고 내용을 붙여넣어준다.

그리고 그걸 vs코드에 넣어준다.

스마트 컨트랙트 만들 때 가장 먼저 해야하는 것이 smart contract 객체를 만드는 일이다.

    let lotteryContract = new web3.eth.Contract(lotteryABI, lotteryAddress);

    let pot = await lotteryContract.methods.getPot().call();
    console.log('pot:',pot);

    let owner = await lotteryContract.methods.owner().call();
    console.log('owner:', owner);

그럼 다음과 같이 console창에 내용이 뜸을 알 수 있다.

그리고 그 바로 밑에 이제 복권ㅇ르 사는 행위를 넣어보자.

lotteryContract.methods.betAndDistribute('0xcd').send({from:accounts, value:5000000000000000, gas:300000})

그러고 이제 저장하고 돌아가보면 메타마스크 창이 뜨는 것을 알 수 있다.

let nonce = await web3.eth.getTransactionCount(accounts[0]);
    lotteryContract.methods.betAndDistribute('0xcd').send({from:accounts[0], value:5000000000000000, gas:300000, nonce:nonce});

이것도 추가해준다.

2-2. react에 삽입하기

call

call: 스마트 컨트랙트의 값만 읽어옴. read만 할 수 있다. 변화시키거나 영향을 미치지 못함.

3. web3.js-filter

이벤트는 어떻게 찍을 수 있는가+ 이벤트 로그들을 어떻게 가져오는가에 대해서 알아보도록 하자.

filter:이벤트를 캐치한다.
getPastEvents 와 websocket 부분에서 사용할 수 있다.
해당 이벤트에 해당하는 것만 골라오는 메소드이다.

아까 했던 밑부분에 위의내용을 추가해준다.
블록의 처음부터 끝까지 나온 BET을 가져오라는 내용이다.

지금까지 3번 일어났음을 알 수 있다.

여기서 address는 smartcontract address임을 알 수 있다. 그리고 'event'는 log의 내용이 나옴을 알 수 있다. raw 값을 해석한 것이 returnValues이다.

4. Dapp 데이터 관리

4-1. Read

  • smart contract를 직접 call 해서 가져온다.(EVM을 거치기 때문에 속도가 꽤 느리다-> 여러개일때는 batch read call을 고려를 해봐야 한다.)
  • event Log를 읽는 방법
    - http(polling) : 물어보면서 한다.
    - websocket
  • init 과 동시에 past Event들을 가져온다.
    ws으로 geth또는 infura와 연결한다.
  • subscribe("BET")이런식으로 원하는 이벤트를 구독을 할 수 있다.
    만약 웹소켓(ws)을 사용할 수 없다면 long polling(3분(긴 시간)마다 한번씩 요청을 보내고 응답을 받아서 업테이트를 한다. )
    서버를 들고있는 환경이라면 서버에서 이러한 작업들을 해주고, 웹에서는 서버에서 데이터를 받아와서 showing만 해주는게 가장 좋은 방법이 될 거 같다.
  • 만약 돈이 크게 걸려있는 서비스라면 블록 컨펌을 확인해주자. 이더리움은 최소 20컨펌이상이 되어야지 안정적이다. 그래서 20이상일 때만 프론트에 보여주는 것도 안정적인 방법이다.

ws를 이용해 polling을 하겠다.

5. Lottery UI 개발

부트 스트랩을 이용할 것이다.
$ npm i bootstrap

https://en.wikipedia.org/wiki/Standard_52-card_deck 에서 카드덱을 찾아서 넣으면 된다.

import logo from './logo.svg';
import './App.css';
import React,{useEffect,useState} from 'react';
import Web3 from 'web3' 

function App() {

  
  let lotteryAddress = '0x43C466923FfFFa2d9B8a1Bbba294Fed9a3214b86';
  let lotteryABI = [ { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }, { "indexed": true, "internalType": "address", "name": "bettor", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "bytes1", "name": "challenges", "type": "bytes1" }, { "indexed": false, "internalType": "uint256", "name": "answerBlockNumber", "type": "uint256" } ], "name": "BET", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }, { "indexed": false, "internalType": "address", "name": "bettor", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "bytes1", "name": "challenges", "type": "bytes1" }, { "indexed": false, "internalType": "bytes1", "name": "answer", "type": "bytes1" }, { "indexed": false, "internalType": "uint256", "name": "answerBlockNumber", "type": "uint256" } ], "name": "DRAW", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }, { "indexed": false, "internalType": "address", "name": "bettor", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "bytes1", "name": "challenges", "type": "bytes1" }, { "indexed": false, "internalType": "bytes1", "name": "answer", "type": "bytes1" }, { "indexed": false, "internalType": "uint256", "name": "answerBlockNumber", "type": "uint256" } ], "name": "FAIL", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }, { "indexed": false, "internalType": "address", "name": "bettor", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "bytes1", "name": "challenges", "type": "bytes1" }, { "indexed": false, "internalType": "uint256", "name": "answerBlockNumber", "type": "uint256" } ], "name": "REFUND", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }, { "indexed": false, "internalType": "address", "name": "bettor", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "bytes1", "name": "challenges", "type": "bytes1" }, { "indexed": false, "internalType": "bytes1", "name": "answer", "type": "bytes1" }, { "indexed": false, "internalType": "uint256", "name": "answerBlockNumber", "type": "uint256" } ], "name": "WIN", "type": "event" }, { "inputs": [], "name": "answerForTest", "outputs": [ { "internalType": "bytes32", "name": "", "type": "bytes32" } ], "stateMutability": "view", "type": "function", "constant": true }, { "inputs": [], "name": "owner", "outputs": [ { "internalType": "address payable", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function", "constant": true }, { "inputs": [], "name": "getPot", "outputs": [ { "internalType": "uint256", "name": "pot", "type": "uint256" } ], "stateMutability": "view", "type": "function", "constant": true }, { "inputs": [ { "internalType": "bytes1", "name": "challenges", "type": "bytes1" } ], "name": "betAndDistribute", "outputs": [ { "internalType": "bool", "name": "result", "type": "bool" } ], "stateMutability": "payable", "type": "function", "payable": true }, { "inputs": [ { "internalType": "bytes1", "name": "challenges", "type": "bytes1" } ], "name": "bet", "outputs": [ { "internalType": "bool", "name": "result", "type": "bool" } ], "stateMutability": "payable", "type": "function", "payable": true }, { "inputs": [], "name": "distribute", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "answer", "type": "bytes32" } ], "name": "setAnswerForTest", "outputs": [ { "internalType": "bool", "name": "result", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "bytes1", "name": "challenges", "type": "bytes1" }, { "internalType": "bytes32", "name": "answer", "type": "bytes32" } ], "name": "isMatch", "outputs": [ { "internalType": "enum Lottery.BettingResult", "name": "", "type": "uint8" } ], "stateMutability": "pure", "type": "function", "constant": true }, { "inputs": [ { "internalType": "uint256", "name": "index", "type": "uint256" } ], "name": "getBetInfo", "outputs": [ { "internalType": "uint256", "name": "answerBlockNumber", "type": "uint256" }, { "internalType": "address", "name": "bettor", "type": "address" }, { "internalType": "bytes1", "name": "challenges", "type": "bytes1" } ], "stateMutability": "view", "type": "function", "constant": true } ]
  
  const [state,setState]=useState({
    betRecords:[],
    winRecords:[],
    failRecords:[],
    pot:'0',
    challenges:['A','B'],
    finalRecords:[{
      bettor:'어쩌구 저쩌구',
      index:'0',
      challenges:'ab',
      answer:'ab',
      targetBlockNumber:'10',
      pot:'0'
    }]
  })
  
  
  const initWeb3 = async () =>{
    if(window.ethereum){

      var web3 = new Web3(window.ethereum);

      console.log('윈도우야 메타 마스크를 연결하니 이게 실행되네')
      console.log(web3)
      
      try{
        await window.ethereum.enable();
      }catch (error){
        console.log(`error ouccur ${error}`)
      }
    }

    //legacy dapp browers..
    else if(window.web3){
      //이게 실행이 됨
      var web3 = new Web3(Web3.curentProvider);
      // console.log(await web3.eth.getAccounts());
      


    }
    else{
      console.log('너 이더리움 없어 메타마스크라도 깔아라....')
    }

    let accounts = await web3.eth.getAccounts();//여기서 왜 안되지?
    console.log(accounts);

    //스마트 컨트랙트 만들 때 가장 먼저 해야하는 것이 smart contract 객체를 만드는 일이다.
    let lotteryContract = new web3.eth.Contract(lotteryABI, lotteryAddress);

    let pot = await lotteryContract.methods.getPot().call();
    console.log('pot:',pot);

    let owner = await lotteryContract.methods.owner().call();
    console.log('owner:', owner);
    let nonce = await web3.eth.getTransactionCount(accounts[0]);
    lotteryContract.methods.betAndDistribute('0xcd').send({from:accounts[0], value:5000000000000000, gas:300000, nonce:nonce});

    let records = [];
    let events = await lotteryContract.getPastEvents('BET',{fromBlock:0, toBlock:'latest'});
    console.log('events',events);
    //블록의 처음부터 끝까지 나온 BET을 가져와라

    // const Web3 = require("web3");
    // const ethEnabled = () => {
    //   if (window.web3) {
    //     let web3 = new Web3(window.web3.currentProvider);
    //     window.ethereum.enable();
    //     console.log(web3);
    //     return true;
    //   }
    // return false;
    // }
  }

  useEffect(async()=>{
    await initWeb3();
  },[]);

  const getCard = (_Character,_cardStyle)=>{
    let _card='';
    if(_Character === 'A'){
      _card = '🂡'
    }
    if(_Character === 'B'){
      _card = '🂱	'
    }
    if(_Character === 'C'){
      _card = '🃁	'
    }
    if(_Character === 'D'){
      _card = '🃑	'
    }

    return (
      <button className={_cardStyle}>
        <div className = "card-body text-center">
        <p className = "card-text"></p>
        <p className = "card-text text-center" style={{fontSize:300}}>{_card}</p>
        <p className = "card-text"></p>
        </div>
      </button>
    )
  }

  return (
    <div className="App">
      <div className = "container">
        <div className = "jumbotron">
          <h1>Current Pot : {state.pot}</h1>
          <p>Lottery</p>
          <p>Lottery tutorial</p>
          <p>Your Bet</p>
          <p>{state.challenges[0]} {state.challenges[1]}</p>
        </div>
      </div>

      {/*card section */}
      <div className = "container">
        <div className = "card-group">
          {getCard('A','dard bg-primary')}
          {getCard('B','dard bg-warning')}
          {getCard('C','dard bg-danger')}
          {getCard('D','dard bg-success')}
        </div>
      </div>
      <br/>
      <div className = "container">
        <button className = "btn btn-danger btn-lg">BET!</button>
      </div>
      <br/>
      <div className = "container">
        <table className = "table table-dark table-striped">
          <thread>
            <tr>
              <th>Index</th>
              <th>Address</th>
              <th>Challenge</th>
              <th>Answer</th>
              <th>Pot</th>
              <th>Status</th>
              <th>AnswerBlockNumber</th>
            </tr>
          </thread>
          <tbody>
            {
              state.finalRecords.map((record,index)=>{
                return(
                  <tr key = {index}>
                    <td>{record.index}</td>
                    <td>{record.bettor}</td>
                    <td>{record.challenges}</td>
                    <td>{record.answer}</td>
                    <td>{record.pot}</td>
                    <td>{record.win}</td>
                    <td>{record.targetBlockNumber}</td>
                  </tr>
                )
              })
            }
          </tbody>
        </table>
      </div>
    </div>
    
  );
}

export default App;

6. React Lottery 기능 개발

6-1. pot머니 가져오기


DATA부분에 보면 cd가 되어있는 것이 우리가 배팅을 한 것의 내용이다.
0x부터 그 전까지의 내용은 함수(function) 시그니쳐 내용이다. id 를 주는 것이다.

6-2.bet()

import logo from './logo.svg';
import './App.css';
import React,{useEffect,useState,useReducer} from 'react';
import Web3 from 'web3' 


const reducer = (state,action)=>{
  switch(action.type){
    case "INIT":{
      let {web3,Instance,account}=action;
      return {
        ...state,web3,Instance,account
      }
    }
    case "GETPOT": { 
      let {pot}= action;
      return {
        ...state,pot
      }
    }
  }
}



function App() {

  
  let lotteryAddress = '0x43C466923FfFFa2d9B8a1Bbba294Fed9a3214b86';
  let lotteryABI = [ { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }, { "indexed": true, "internalType": "address", "name": "bettor", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "bytes1", "name": "challenges", "type": "bytes1" }, { "indexed": false, "internalType": "uint256", "name": "answerBlockNumber", "type": "uint256" } ], "name": "BET", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }, { "indexed": false, "internalType": "address", "name": "bettor", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "bytes1", "name": "challenges", "type": "bytes1" }, { "indexed": false, "internalType": "bytes1", "name": "answer", "type": "bytes1" }, { "indexed": false, "internalType": "uint256", "name": "answerBlockNumber", "type": "uint256" } ], "name": "DRAW", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }, { "indexed": false, "internalType": "address", "name": "bettor", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "bytes1", "name": "challenges", "type": "bytes1" }, { "indexed": false, "internalType": "bytes1", "name": "answer", "type": "bytes1" }, { "indexed": false, "internalType": "uint256", "name": "answerBlockNumber", "type": "uint256" } ], "name": "FAIL", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }, { "indexed": false, "internalType": "address", "name": "bettor", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "bytes1", "name": "challenges", "type": "bytes1" }, { "indexed": false, "internalType": "uint256", "name": "answerBlockNumber", "type": "uint256" } ], "name": "REFUND", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }, { "indexed": false, "internalType": "address", "name": "bettor", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "bytes1", "name": "challenges", "type": "bytes1" }, { "indexed": false, "internalType": "bytes1", "name": "answer", "type": "bytes1" }, { "indexed": false, "internalType": "uint256", "name": "answerBlockNumber", "type": "uint256" } ], "name": "WIN", "type": "event" }, { "inputs": [], "name": "answerForTest", "outputs": [ { "internalType": "bytes32", "name": "", "type": "bytes32" } ], "stateMutability": "view", "type": "function", "constant": true }, { "inputs": [], "name": "owner", "outputs": [ { "internalType": "address payable", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function", "constant": true }, { "inputs": [], "name": "getPot", "outputs": [ { "internalType": "uint256", "name": "pot", "type": "uint256" } ], "stateMutability": "view", "type": "function", "constant": true }, { "inputs": [ { "internalType": "bytes1", "name": "challenges", "type": "bytes1" } ], "name": "betAndDistribute", "outputs": [ { "internalType": "bool", "name": "result", "type": "bool" } ], "stateMutability": "payable", "type": "function", "payable": true }, { "inputs": [ { "internalType": "bytes1", "name": "challenges", "type": "bytes1" } ], "name": "bet", "outputs": [ { "internalType": "bool", "name": "result", "type": "bool" } ], "stateMutability": "payable", "type": "function", "payable": true }, { "inputs": [], "name": "distribute", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "answer", "type": "bytes32" } ], "name": "setAnswerForTest", "outputs": [ { "internalType": "bool", "name": "result", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "bytes1", "name": "challenges", "type": "bytes1" }, { "internalType": "bytes32", "name": "answer", "type": "bytes32" } ], "name": "isMatch", "outputs": [ { "internalType": "enum Lottery.BettingResult", "name": "", "type": "uint8" } ], "stateMutability": "pure", "type": "function", "constant": true }, { "inputs": [ { "internalType": "uint256", "name": "index", "type": "uint256" } ], "name": "getBetInfo", "outputs": [ { "internalType": "uint256", "name": "answerBlockNumber", "type": "uint256" }, { "internalType": "address", "name": "bettor", "type": "address" }, { "internalType": "bytes1", "name": "challenges", "type": "bytes1" } ], "stateMutability": "view", "type": "function", "constant": true } ]
  let bytecode = "0x60806040526000600560006101000a81548160ff02191690831515021790555034801561002b57600080fd5b5033600360006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506114e48061007c6000396000f3fe6080604052600436106100865760003560e01c80638da5cb5b116100595780638da5cb5b1461020157806399a167d714610258578063e16ea857146102e1578063e4fc6b6d14610349578063f4b46f5b1461036057610086565b8063403c9fa81461008b5780637009fa36146100b657806379141f801461010957806384f7e4f0146101d6575b600080fd5b34801561009757600080fd5b506100a06103c8565b6040518082815260200191505060405180910390f35b3480156100c257600080fd5b506100ef600480360360208110156100d957600080fd5b81019080803590602001909291905050506103d2565b604051808215151515815260200191505060405180910390f35b34801561011557600080fd5b506101426004803603602081101561012c57600080fd5b810190808035906020019092919050505061048a565b604051808481526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001827effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001935050505060405180910390f35b3480156101e257600080fd5b506101eb61058f565b6040518082815260200191505060405180910390f35b34801561020d57600080fd5b50610216610595565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b34801561026457600080fd5b506102bd6004803603604081101561027b57600080fd5b8101908080357effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19169060200190929190803590602001909291905050506105bb565b604051808260028111156102cd57fe5b60ff16815260200191505060405180910390f35b61032f600480360360208110156102f757600080fd5b8101908080357effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19169060200190929190505050610896565b604051808215151515815260200191505060405180910390f35b34801561035557600080fd5b5061035e6108b3565b005b6103ae6004803603602081101561037657600080fd5b8101908080357effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19169060200190929190505050610f8d565b604051808215151515815260200191505060405180910390f35b6000600454905090565b6000600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461047a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602b815260200180611484602b913960400191505060405180910390fd5b8160068190555060019050919050565b600080600061049761142a565b60026000868152602001908152602001600020604051806060016040529081600082015481526020016001820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020016001820160149054906101000a900460f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815250509050806000015193508060200151925080604001519150509193909250565b60065481565b600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008083905060008490506000846000602081106105d557fe5b1a60f81b90506000856000602081106105ea57fe5b1a60f81b90506004847effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916901c93506004847effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916901b93506004827effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916901c91506004827effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916901b91506004837effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916901b92506004837effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916901c92506004817effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916901b90506004817effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916901c9050837effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19161480156107ce5750827effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b156107e0576001945050505050610890565b837effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916827effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191614806108755750827effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916817effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916145b15610887576002945050505050610890565b60009450505050505b92915050565b60006108a182610f8d565b506108aa6108b3565b60019050919050565b6000806108be61142a565b60008060015494505b600054851015610f7f5760026000868152602001908152602001600020604051806060016040529081600082015481526020016001820160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020016001820160149054906101000a900460f81b7effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152505092506109b98360000151611141565b9150600060028111156109c857fe5b8260028111156109d457fe5b1415610e3b5760006109e98460000151611191565b90506109f98460400151826105bb565b915060016002811115610a0857fe5b826002811115610a1457fe5b1415610b6d57610a3384602001516611c37937e08000600454016111b9565b945060006004819055507f8219079e2d6c1192fb0ff7f78e6faaf5528ad6687e69749205d87bd4b156912b86856020015187876040015185600060208110610a7757fe5b1a60f81b8960000151604051808781526020018673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001858152602001847effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001837effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001828152602001965050505050505060405180910390a15b60006002811115610b7a57fe5b826002811115610b8657fe5b1415610cd4576611c37937e080006004600082825401925050819055507f3b19d607433249d2ebc766ae82ca3848e9c064f1febb5147bc6e5b21d0adebc58685602001516000876040015185600060208110610bde57fe5b1a60f81b8960000151604051808781526020018673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001858152602001847effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001837effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001828152602001965050505050505060405180910390a15b600280811115610ce057fe5b826002811115610cec57fe5b1415610e3957610d0784602001516611c37937e080006111b9565b94507f72ec2e949e4fad9380f9d5db3e2ed0e71cf22c51d8d66424508bdc761a3f4b0e86856020015187876040015185600060208110610d4357fe5b1a60f81b8960000151604051808781526020018673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001858152602001847effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001837effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152602001828152602001965050505050505060405180910390a15b505b60016002811115610e4857fe5b826002811115610e5457fe5b1415610e5f57610f7f565b600280811115610e6b57fe5b826002811115610e7757fe5b1415610f6857610e9283602001516611c37937e080006111b9565b93507f59c0185881271a0f53d43e6ab9310091408f9e0ff9ae2512613de800f26b8de48584602001518686604001518760000151604051808681526020018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001848152602001837effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191681526020018281526020019550505050505060405180910390a15b610f7185611282565b5084806001019550506108c7565b846001819055505050505050565b60006611c37937e08000341461100b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600e8152602001807f4e6f7420656e6f7567682045544800000000000000000000000000000000000081525060200191505060405180910390fd5b611014826112e6565b611086576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601a8152602001807f4661696c20746f206164642061206e65772042657420496e666f00000000000081525060200191505060405180910390fd5b3373ffffffffffffffffffffffffffffffffffffffff167f100791de9f40bf2d56ffa6dc5597d2fd0b2703ea70bc7548cd74c04f5d215ab760016000540334856003430160405180858152602001848152602001837effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19167effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815260200182815260200194505050505060405180910390a260019050919050565b600081431180156111555750816101000143105b15611163576000905061118c565b814311611173576001905061118c565b61010082014310611187576002905061118c565b600290505b919050565b6000600560009054906101000a900460ff166111af576006546111b2565b81405b9050919050565b60008060009050600081840390508473ffffffffffffffffffffffffffffffffffffffff166108fc829081150290604051600060405180830381858888f1935050505015801561120d573d6000803e3d6000fd5b50600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f19350505050158015611276573d6000803e3d6000fd5b50809250505092915050565b6000600260008381526020019081526020016000206000808201600090556001820160006101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556001820160146101000a81549060ff0219169055505060019050919050565b60006112f061142a565b33816020019073ffffffffffffffffffffffffffffffffffffffff16908173ffffffffffffffffffffffffffffffffffffffff1681525050600343018160000181815250508281604001907effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff191690817effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916815250508060026000805481526020019081526020016000206000820151816000015560208201518160010160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060408201518160010160146101000a81548160ff021916908360f81c021790555090505060008081548092919060010191905055506001915050919050565b604051806060016040528060008152602001600073ffffffffffffffffffffffffffffffffffffffff16815260200160007effffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff19168152509056fe4f6e6c79206f776e65722063616e207365742074686520616e7377657220666f722074657374206d6f6465a2646970667358221220efb6c16501351c0526935debc12ba06f64e0240bb8bf03c20a1cb7338c956f7064736f6c63430006000033"
  const initialState={
    betRecords:[],
    winRecords:[],
    failRecords:[],
    pot:'0',
    challenges:['A','B'],
    finalRecords:[{
      bettor:'어쩌구 저쩌구',
      index:'0',
      challenges:'ab',
      answer:'ab',
      targetBlockNumber:'10',
      pot:'0'
    }],
    Instance:null,
    account:null,
    web3:null}
  const [state,dispatch]=useReducer(reducer,initialState);

  const bet = async()=>{
    let {challenges,web3,Instance,account}=state;
    let challenge = '0x'+challenges[0].toLowerCase()+challenges[1].toLowerCase();
    let nonce = await web3.eth.getTransactionCount(account[0]);
    Instance.methods.betAndDistribute('0xcd').send({from:account[0], value:5000000000000000, gas:300000, nonce:nonce});
  }
  
  
  const initWeb3 = async () =>{
    // const contract = require('@truffle/contract');

    if(window.ethereum){

      var web3 = new Web3(window.ethereum);

      console.log('윈도우야 메타 마스크를 연결하니 이게 실행되네')
      console.log(web3)
      
      try{
        await window.ethereum.enable();



        
      }catch (error){
        console.log(`error ouccur ${error}`)
      }
    }

    //legacy dapp browers..
    else if(window.web3){
      //이게 실행이 됨
      var web3 = new Web3(Web3.curentProvider);
      // console.log(await web3.eth.getAccounts());
      


    }
    else{
      console.log('너 이더리움 없어 메타마스크라도 깔아라....')
    }

    let accounts = await web3.eth.getAccounts();//여기서 왜 안되지?
    console.log(accounts);

    //스마트 컨트랙트 만들 때 가장 먼저 해야하는 것이 smart contract 객체를 만드는 일이다.
    let lotteryContract = new web3.eth.Contract(lotteryABI, lotteryAddress);
    console.log('로테리컨트랙트',lotteryContract)
    // let pot = await lotteryContract.methods.getPot().call();
    // console.log('pot:',pot);

    let owner = await lotteryContract.methods.owner().call();
    console.log('owner:', owner);
   

    let records = [];
    let events = await lotteryContract.getPastEvents('BET',{fromBlock:0, toBlock:'latest'});
    console.log('events',events);
    //블록의 처음부터 끝까지 나온 BET을 가져와라

    // const Web3 = require("web3");
    // const ethEnabled = () => {
    //   if (window.web3) {
    //     let web3 = new Web3(window.web3.currentProvider);
    //     window.ethereum.enable();
    //     console.log(web3);
    //     return true; 
    //   }
    // return false;
    // }
    dispatch({type:'INIT',web3,account:accounts,Instance:lotteryContract})
  }

 

  const getCard = (_Character,_cardStyle)=>{
    let _card='';
    if(_Character === 'A'){
      _card = '🂡'
    }
    if(_Character === 'B'){
      _card = '🂱'
    }
    if(_Character === 'C'){
      _card = '🃁'
    }
    if(_Character === 'D'){
      _card = '🃑'
    }

    return (
      <button className={_cardStyle}>
        <div className = "card-body text-center">
        <p className = "card-text"></p>
        <p className = "card-text text-center" style={{fontSize:300}}>{_card}</p>
        <p className = "card-text"></p>
        </div>
      </button>
    )
  }

  const getPot = async()=>{
    let {Instance,web3}=state;
    console.log('Instance',Instance);
    let pot = await Instance.methods.getPot().call();
    let potString = web3.utils.fromWei(pot.toString(),'ether');
    //eth단위로 변환시켜줌
    console.log('pot',potString);
    dispatch({type:"GETPOT",pot:potString});
  }

  const pollData = async()=>{
    await getPot();
    
  }
  useEffect(async()=>{
    await initWeb3();
    await pollData();
    // await getPot();
  },[]);
  return (
    <div className="App">
      <div className = "container">
        <div className = "jumbotron">
          <h1>Current Pot : {state.pot}</h1>
          <p>Lottery</p>
          <p>Lottery tutorial</p>
          <p>Your Bet</p>
          <p>{state.challenges[0]} {state.challenges[1]}</p>
        </div>
      </div>

      {/*card section */}
      <div className = "container">
        <div className = "card-group">
          {getCard('A','dard bg-primary')}
          {getCard('B','dard bg-warning')}
          {getCard('C','dard bg-danger')}
          {getCard('D','dard bg-success')}
        </div>
      </div>
      <br/>
      <div className = "container">
        <button className = "btn btn-danger btn-lg" onClick={bet}>BET!</button>
      </div>
      <br/>
      <div className = "container">
        <table className = "table table-dark table-striped">
          <thead>
            <tr>
              <th>Index</th>
              <th>Address</th>
              <th>Challenge</th>
              <th>Answer</th>
              <th>Pot</th>
              <th>Status</th>
              <th>AnswerBlockNumber</th>
            </tr>
          </thead>
          <tbody>
            {
              state.finalRecords.map((record,index)=>{
                return(
                  <tr key = {index}>
                    <td>{record.index}</td>
                    <td>{record.bettor}</td>
                    <td>{record.challenges}</td>
                    <td>{record.answer}</td>
                    <td>{record.pot}</td>
                    <td>{record.win}</td>
                    <td>{record.targetBlockNumber}</td>
                  </tr>
                )
              })
            }
          </tbody>
        </table>
      </div>
    </div>
    
  );
}

export default App;

6-3. UI변경

카드를 클릭했을 때 배팅하는 것이 달라지는 UI를 만드는 것이다.

6-4. Hash값 찾기

const bet = async()=>{
    let {challenges,web3,Instance,account}=state;
    let challenge = '0x'+challenges[0].toLowerCase()+challenges[1].toLowerCase();
    let nonce = await web3.eth.getTransactionCount(account[0]);
    Instance.methods.betAndDistribute(challenge).send({from:account[0], value:5000000000000000, gas:300000, nonce:nonce})
    .on('transactionHash',(hash)=>{
      console.log('HASH값 :',hash);//그냥 해시값 찾아봤다.
    });
  }

테이블 만들기

getBetEvents()

이벤트 찍기

const getBetEvents = async(Instance)=>{
    let records = [];
    let events = await Instance.getPastEvents('BET',{fromBlock:0, toBlock:'latest'});
    for(let i=0; i<events.length;i+=1){
      const record = {};
      record.index = parseInt(events[i].returnValues.index,10).toString();//hex값으로 오는 경우도 있기에 바꿔준다.
      record.bettor = events[i].returnValues.bettor;
      record.betBlockNumber = events[i].blockNumber;
      record.targetBlockNumber = events[i].returnValues.answerBlockNumber.toString();
      record.challenges = events[i].returnValues.challenges;
      record.win = 'Not Revealed';
      record.answer = '0x00';
      records.unshift(record);
    }
    console.log(records);
    dispatch({type:"GETBETEVENT",records})
    //블록의 처음부터 끝까지 나온 BET을 가져와라
  }

getWinEvents

const getWinEvents = async(Instance)=>{
    let records = [];
    let events = await Instance.getPastEvents('WIN',{fromBlock:0, toBlock:'latest'});
    for(let i=0; i<events.length;i+=1){
      const record = {};
      record.index = parseInt(events[i].returnValues.index,10).toString();//hex값으로 오는 경우도 있기에 바꿔준다.
      record.amount = parseInt(events[i].returnValues.amount,10).toString();
      records.unshift(record);
    }
    console.log('winEvent',records);
    dispatch({type:"WINEVENT",records})
    //블록의 처음부터 끝까지 나온 BET을 가져와라
  }
  

정답은 0x00이므로 00을 선택한 뒤 4개의 다른 bet을 누른다. 그 다음에 새로고침을 해서 보면 다음과 같이 나온다.

그리고 potmoney는 다시 0이 되었다.

getWinEvents

const getFailEvents = async(Instance)=>{
    let records = [];
    let events = await Instance.getPastEvents('FAIL',{fromBlock:0, toBlock:'latest'});
    for(let i=0; i<events.length;i+=1){
      const record = {};
      record.index = parseInt(events[i].returnValues.index,10).toString();//hex값으로 오는 경우도 있기에 바꿔준다.
      record.answer = events[i].returnValues.answer;
      records.unshift(record);
    }
    console.log('FailEvent',records);
    dispatch({type:"FAILEVENT",records})
    //블록의 처음부터 끝까지 나온 BET을 가져와라
  }

정답이 중요하기에 정답 레코드를 받는다.


여기서 4개가 Not Revealed처리가 되었다. 그 이유는 필터로 이벤트를 처리함에 있다. 필터로 이벤트를 처리할 때에는 1컴펌이 되어야 한다.

함수형 web3 완성본


import 'bootstrap/dist/css/bootstrap.css';
import React,{useEffect,useReducer} from 'react';
import Web3 from 'web3' 


const reducer = (state,action)=>{
  switch(action.type){
    case "INIT":{
      let {web3,Instance,account}=action;
      return {
        ...state,web3,Instance,account,flag:true
      }
    }
    case "GETPOT": { 
      let {pot}= action;
      return {
        ...state,pot,
      }
    }
    case "CLICKCARD":{
      let _Character = action.data;
      let {challenges} = action;
      console.log('clickcard',_Character);
      return {
        ...state,challenges:[challenges,_Character]
      }
    }
    case "GETBETEVENT":{
      let {records}=action;
      return{
        ...state,betRecords:records
      }
    }
    case "WINEVENT":{
      let {records} = action;
      return {
        ...state,winRecords:records,flag1:!state.flag1
      }
    }
    case "FAILEVENT":{
      let {records}=action;
      return{
        ...state,failRecords:records,flag2:!state.flag2
      }
    }
    case "FINALRECORD":{
      let {records}=action;
      return{
        ...state,finalRecords:records
      }
    }
  }
}



function App() {

  
  let lotteryAddress = '0x5eE960B277574C090212fA92fa7b19BBDc8B913A';
  let lotteryABI =[ { "inputs": [], "stateMutability": "nonpayable", "type": "constructor" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }, { "indexed": true, "internalType": "address", "name": "bettor", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "bytes1", "name": "challenges", "type": "bytes1" }, { "indexed": false, "internalType": "uint256", "name": "answerBlockNumber", "type": "uint256" } ], "name": "BET", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }, { "indexed": false, "internalType": "address", "name": "bettor", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "bytes1", "name": "challenges", "type": "bytes1" }, { "indexed": false, "internalType": "bytes1", "name": "answer", "type": "bytes1" }, { "indexed": false, "internalType": "uint256", "name": "answerBlockNumber", "type": "uint256" } ], "name": "DRAW", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }, { "indexed": false, "internalType": "address", "name": "bettor", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "bytes1", "name": "challenges", "type": "bytes1" }, { "indexed": false, "internalType": "bytes1", "name": "answer", "type": "bytes1" }, { "indexed": false, "internalType": "uint256", "name": "answerBlockNumber", "type": "uint256" } ], "name": "FAIL", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }, { "indexed": false, "internalType": "address", "name": "bettor", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "bytes1", "name": "challenges", "type": "bytes1" }, { "indexed": false, "internalType": "uint256", "name": "answerBlockNumber", "type": "uint256" } ], "name": "REFUND", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": false, "internalType": "uint256", "name": "index", "type": "uint256" }, { "indexed": false, "internalType": "address", "name": "bettor", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "amount", "type": "uint256" }, { "indexed": false, "internalType": "bytes1", "name": "challenges", "type": "bytes1" }, { "indexed": false, "internalType": "bytes1", "name": "answer", "type": "bytes1" }, { "indexed": false, "internalType": "uint256", "name": "answerBlockNumber", "type": "uint256" } ], "name": "WIN", "type": "event" }, { "inputs": [], "name": "answerForTest", "outputs": [ { "internalType": "bytes32", "name": "", "type": "bytes32" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "owner", "outputs": [ { "internalType": "address payable", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getPot", "outputs": [ { "internalType": "uint256", "name": "pot", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "bytes1", "name": "challenges", "type": "bytes1" } ], "name": "betAndDistribute", "outputs": [ { "internalType": "bool", "name": "result", "type": "bool" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "internalType": "bytes1", "name": "challenges", "type": "bytes1" } ], "name": "bet", "outputs": [ { "internalType": "bool", "name": "result", "type": "bool" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [], "name": "distribute", "outputs": [], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "bytes32", "name": "answer", "type": "bytes32" } ], "name": "setAnswerForTest", "outputs": [ { "internalType": "bool", "name": "result", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "bytes1", "name": "challenges", "type": "bytes1" }, { "internalType": "bytes32", "name": "answer", "type": "bytes32" } ], "name": "isMatch", "outputs": [ { "internalType": "enum Lottery.BettingResult", "name": "", "type": "uint8" } ], "stateMutability": "pure", "type": "function" }, { "inputs": [ { "internalType": "uint256", "name": "index", "type": "uint256" } ], "name": "getBetInfo", "outputs": [ { "internalType": "uint256", "name": "answerBlockNumber", "type": "uint256" }, { "internalType": "address", "name": "bettor", "type": "address" }, { "internalType": "bytes1", "name": "challenges", "type": "bytes1" } ], "stateMutability": "view", "type": "function" } ];



  const initialState={
    betRecords:[],
    winRecords:[],
    failRecords:[],
    pot:'0',
    challenges:['A','B'],
    finalRecords:[{
      bettor:'어쩌구 저쩌구',
      index:'0',
      challenges:'ab',
      answer:'ab',
      targetBlockNumber:'10',
      pot:'0'
    }],
    Instance:null,
    account:null,
    web3:null,
    flag1:false,
    flag2:false,
  }
  const [state,dispatch]=useReducer(reducer,initialState);

  const bet = async()=>{
    let {challenges,web3,Instance,account}=state;
    let challenge = '0x'+challenges[0].toLowerCase()+challenges[1].toLowerCase();
    let nonce = await web3.eth.getTransactionCount(account[0]);
    Instance.methods.betAndDistribute(challenge).send({from:account[0], value:5000000000000000, gas:300000, nonce:nonce})
    .on('transactionHash',(hash)=>{
      console.log('HASH값 :',hash);//그냥 해시값 찾아봤다.
    });
  }

  const makeFinalRecords=()=>{
    let {web3}=state;
    console.log('makeFinal들어옴')
    //win이랑 Fail인 애들을 전부다 매칭시키는 것임
    let f=0, w=0;
    const records = [...state.betRecords];
    console.log('records다 ',records)
    for(let i=0; i<state.betRecords.length;i++){
      console.log("원래 인덱스");
      if(state.winRecords.length>0 && state.betRecords[i].index === state.winRecords[w].index){
        //이겼을때
        console.log('이긴거 들어옴')
        records[i].win = 'WIN';
        records[i].answer = records[i].challenges;
        records[i].pot = web3.utils.fromWei(state.winRecords[w].amount,'ether');
        if(state.winRecords.length-1>w){w++;}
      } else if(state.failRecords.length>0 && state.betRecords[i].index === state.failRecords[f].index){
        console.log("진짜 얜 왜 안되?")
        records[i].win = 'FAIL';
        records[i].answer = state.failRecords[f].answer;
        records[i].pot = 0;
        if(state.failRecords.length-1>f)f++;
      } else {
        records[i].answer = 'Not Revealed';
      }
    }
    console.log("FINAL",records);
    clearInterval();
    dispatch({type:"FINALRECORD",records})
  }


  const getBetEvents = async(Instance)=>{
    let records = [];
    let events = await Instance.getPastEvents('BET',{fromBlock:0, toBlock:'latest'});
    for(let i=0; i<events.length;i+=1){
      const record = {};
      record.index = parseInt(events[i].returnValues.index,10).toString();//hex값으로 오는 경우도 있기에 바꿔준다.
      record.bettor = events[i].returnValues.bettor.slice(0,4) + '...' + events[i].returnValues.bettor.slice(40,42);
      record.betBlockNumber = events[i].blockNumber;
      record.targetBlockNumber = events[i].returnValues.answerBlockNumber.toString();
      record.challenges = events[i].returnValues.challenges;
      record.win = 'Not Revealed';
      record.answer = '0x00';
      records.unshift(record);
    }
    console.log('getbetEvent',records);
    clearInterval();
    dispatch({type:"GETBETEVENT",records})
    //블록의 처음부터 끝까지 나온 BET을 가져와라
  }

  const getWinEvents = async(Instance)=>{
    let records = [];
    let events = await Instance.getPastEvents('WIN',{fromBlock:0, toBlock:'latest'});
    for(let i=0; i<events.length;i+=1){
      const record = {};
      record.index = parseInt(events[i].returnValues.index,10).toString();//hex값으로 오는 경우도 있기에 바꿔준다.
      record.amount = parseInt(events[i].returnValues.amount,10).toString();
      records.unshift(record);
    }
    console.log('winEvent',records);
    clearInterval();
    dispatch({type:"WINEVENT",records})
    //블록의 처음부터 끝까지 나온 BET을 가져와라
  }

  const getFailEvents = async(Instance)=>{
    let records = [];
    let events = await Instance.getPastEvents('FAIL',{fromBlock:0, toBlock:'latest'});
    for(let i=0; i<events.length;i+=1){
      const record = {};
      record.index = parseInt(events[i].returnValues.index,10).toString();//hex값으로 오는 경우도 있기에 바꿔준다.
      record.answer = events[i].returnValues.answer;
      records.unshift(record);
    }
    console.log('FailEvent',records);
    dispatch({type:"FAILEVENT",records})
    //블록의 처음부터 끝까지 나온 BET을 가져와라
  }
  

  async function dataPoll(web3,Instance){
    await getPot(web3,Instance);
    await getBetEvents(Instance);
    await getWinEvents(Instance);
    await getFailEvents(Instance);
  }
  const initWeb3 = async () =>{
    // const contract = require('@truffle/contract');
    let web3
    if(window.ethereum){

      web3 = new Web3(window.ethereum);
      console.log(web3)
      try{
        await window.ethereum.enable();        
      }catch (error){
        console.log(`error ouccur ${error}`)
      }
    } else if(window.web3) { //legacy dapp browers..
      //이게 실행이 됨
      web3 = new Web3(Web3.curentProvider);
      // console.log(await web3.eth.getAccounts());
    } else {
      console.log('너 이더리움 없어 메타마스크라도 깔아라....')
    }
    let accounts = await web3.eth.getAccounts();//여기서 왜 안되지?
    
    //스마트 컨트랙트 만들 때 가장 먼저 해야하는 것이 smart contract 객체를 만드는 일이다.
    let lotteryContract = new web3.eth.Contract(lotteryABI, lotteryAddress); // console.log('로테리컨트랙트',lotteryContract)
    
    // let pot = await lotteryContract.methods.getPot().call();
    // console.log('pot:',pot);

    let owner = await lotteryContract.methods.owner().call();
    console.log('owner:', owner);
   
    dispatch({ type: 'INIT', web3, account: accounts, Instance: lotteryContract })
    // await getPot();
    // await getPot();
    // await getPot();
    // await getBetEvents();
    let interval = setInterval(() => {
      dataPoll(web3,lotteryContract);
    }, 1000);
   



    // let interval = setInterval(() => {
    //   getPot(web3,lotteryContract);
    // }, 1000);
    // await getBetEvents(lotteryContract);
    // await getWinEvents(lotteryContract);
    // await getFailEvents(lotteryContract);
   
  }

  const onClickCard=(Character)=>{
    dispatch({type:"CLICKCARD",data:Character,challenges:state.challenges[1]})
  }
  

  const getCard = (_Character,_cardStyle)=>{
    let _card='';
    if(_Character === 'A'){
      _card = '🂡'
    }
    if(_Character === 'B'){
      _card = '🂱'
    }
    if(_Character === 'C'){
      _card = '🃁'
    }
    if(_Character === '0'){
      _card = '🃑'
    }

    return (
      <button className={_cardStyle} onClick = {()=>{onClickCard(_Character)}}>
        <div className = "card-body text-center">
        <p className = "card-text"></p>
        <p className = "card-text text-center" style={{fontSize:300}}>{_card}</p>
        <p className = "card-text"></p>
        </div>
      </button>
    )
  }

  const getPot = async(web3,Instance)=>{


    let pot = await Instance.methods.getPot().call();
    let potString = web3.utils.fromWei(pot.toString(),'ether');
    //eth단위로 변환시켜줌
    clearInterval()
    dispatch({type:"GETPOT",pot:potString});
  }

  const pollData = async()=>{
    await initWeb3(); 
    
  }

  useEffect(()=>{
    pollData(); 
  },[]);

  useEffect(()=>{
    makeFinalRecords();
  },[state.flag2])

  return (
    <div className="App">
      <div className = "container">
        <div className = "jumbotron">
          <h1>Current Pot : {state.pot}</h1>
          <p>Lottery</p>
          <p>Lottery tutorial</p>
          <p>Your Bet</p>
          <p>{state.challenges[0]} {state.challenges[1]}</p>
        </div>
      </div>

      {/*card section */}
      <div className = "container">
        <div className = "card-group">
          {getCard('A','dard bg-primary')}
          {getCard('B','dard bg-warning')}
          {getCard('C','dard bg-danger')}
          {getCard('0','dard bg-success')}
        </div>
      </div>
      <br/>
      <div className = "container">
        <button className = "btn btn-danger btn-lg" onClick={bet}>BET!</button>
      </div>
      <br/>
      <div className = "container">
        <table className = "table table-dark table-striped">
          <thead>
            <tr>
              <th>Index</th>
              <th>Address</th>
              <th>Challenge</th>
              <th>Answer</th>
              <th>Pot</th>
              <th>Status</th>
              <th>AnswerBlockNumber</th>
            </tr>
          </thead>
          <tbody>
            {
              state.finalRecords.map((record,index)=>{
                return(
                  <tr key = {index}>
                    <td>{record.index}</td>
                    <td>{record.bettor}</td>
                    <td>{record.challenges}</td>
                    <td>{record.answer}</td>
                    <td>{record.pot}</td>
                    <td>{record.win}</td>
                    <td>{record.targetBlockNumber}</td>
                  </tr>
                )
              })
            }
          </tbody>
        </table>
      </div>
    </div>
    
  );
}

export default App;

기능

어려웠던 점

클래스형을 함수형으로 고치다보니까 dispatch로 인한 문제가 생겼다. 화면을 그리지 못해서 난감했던 시간들이 꽤 길었다. 그래도 포기하지 않고 마저 했고, 또한 교수님께서 같이 1시간동안 붙잡고 해결하는데 도움을 주셨다.... @truffle/contract로는 도저히 bet함수가 구현이 안되어서 다시 돌아왔지만... 그래도 비동기통신에 대해 더 확실하게 알 수 있었다. 그리고 지난번 프로젝트때 websocket으로 인한 통신을 함으로써 useEffect를 더 잘 사용할 수 있었다.

지금 새벽 2시 30분.... 그래도 해결하니까 기분은 좋다!!😁

profile
코딩 재밌어요!

1개의 댓글

comment-user-thumbnail
2022년 6월 8일

안녕하세요 너무 좋은 내용의 글 잘 봤습니다 :)

최근 web3js를 배우면서 HDWalletProvider를 사용하여 혼자서 실습을 하다가, 리액트와 함께 배포하는 과정을 실습하려고 하는데요. 궁금한 부분이 생겨 구글링을 하다가 여쭤보고싶은 게 있어서 댓글을 달게 되었습니다! 답글 달아주시면 감사하겠습니다 :)

  1. 지금까지는 제 스스로의 계정만을 넣어서 web3 = new Web3(provider)로 해서 저의 계정을 가져왔다면, 배포하여 사용자의 메타마스크 계정과 연결하기 위해서는 web3 = new Web3(window.ethereum) => web3.eth.getAccount() 과정을 거쳐 실제 사용자의 메타마스크 계정과 연동할 수 있는 것인가요?

  2. 단순하게, 원래 인퓨라로 제공되었던 provider를 위 과정을 통해 metamask가 provider가 되고, 인퓨라 노드를 통해서 배포하고, 컨트랙트 내 함수를 호출하고 했던 일련의 과정이 metamask를 통해 실행하게 되는건가요?

  3. 위 과정을 통해 metamask를 provider로 설정한 후에는, contract.methods.call() / send() 등 이전에 사용했던 web3 함수들을 그대로 적용해서 사용해도 똑같이 작동하는것인가요?

답글 달기