[Truffle, React.js] 과일 가게 dApp 만들기 (2/2)

in #ethereum6 years ago (edited)

할 것


  1. React.js 로 프론트엔드를 구성합니다.

  2. Web3.js 를 활용해 dApp 을 완성시킵니다.



완성물 미리보기 (Youtube)



Project_Preview

클릭하면 유뷰트로 이동합니다.

실제 완성물은 조금 더 못생겼습니다. css 를 다루기 귀찮아서...

React.js 불필요 코드 제거



우리가 받아온 react box 에는 미리 작성되어있는 코드가 존재합니다. 이제 그 부분은 필요가 없으므로 제거합니다.

src/App.js 를 아래 소스코드로 바꿔주세요:

import React, { Component } from "react";
import getWeb3 from "./utils/getWeb3";

import "./App.css";

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      web3: null
    };
  }

  componentWillMount() {
    getWeb3
      .then(results => {
        this.setState({
          web3: results.web3
        });

        this.instantiateContract();
      })
      .catch(() => {
        console.log("Error finding web3.");
      });
  }

  instantiateContract() {
    const contract = require("truffle-contract");
  }

  render() {
    return <div className="App">Fruit shop</div>;
  }
}

export default App;


이제 App.js

  • componentWillMount 에서 Web3 를 불러오고 state 에 저장합니다. 앞으로 우리는 this.state.web3 를 통해 Web3.js 를 사용합니다.

  • 또한 instantiateContract 함수를 실행하고 truffle-contract 모듈을 불러옵니다. 이 모듈은 Truffle 에서 작성한 컨트랙트를 Javascript 로 손쉽게 불러오고 사용하도록 만들어줍니다.

Shop 인스턴스 저장하기



우리가 컴파일한 Shop.json 파일을 불러옵니다:

import React, { Component } from "react";
import ShopContract from "../build/contracts/Shop.json";
import getWeb3 from "./utils/getWeb3";



instantiateContract 함수를 수정합니다:

const contract = require("truffle-contract");
const shop = contract(ShopContract);
shop.setProvider(this.state.web3.currentProvider);


  • truffle-contract 를 통해 우리가 불러온 Shop.json 파일을 const shop 에 저장했습니다.

  • Provider 를 우리가 현재 사용하는 MetaMask 로 설정합니다.

stateshopInstance, myAccount 를 추가해주세요. 우리가 배포한 컨트랙트의 인스턴스와 구매, 판매에 사용할 계정을 할당할 state 입니다:

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      shopInstance: null, // shopInstance 추가
      myAccount: null,    // myAccount 추가
      web3: null
    };
  }


이제 instantiateContract 함수를 마저 완성합시다:

instantiateContract() {
  const contract = require("truffle-contract");
  const shop = contract(ShopContract);
  shop.setProvider(this.state.web3.currentProvider);

  /* 이것을 추가하세요. */
  this.state.web3.eth.getAccounts((error, accounts) => {
    if (!error) {
      shop.deployed().then(instance => {
        this.setState({ shopInstance: instance, myAccount: accounts[0] });
      });
    }
  });
}


  • web3 의 getAccounts 를 통해 계정을 받아오면, shop 에 배포된 인스턴스를 shopInstance 에 저장합니다.

  • accounts 에는 Ganache 에서 생성한 10 개의 계정이 들어있습니다. 우리는 그 중 첫번째 계정인 accounts[0] 을 사용합니다.

Shop 컨트랙트를 사용할 준비는 모두 마쳤습니다. 마지막으로 App.js 코드를 확인해주세요:

import React, { Component } from "react";
import ShopContract from "../build/contracts/Shop.json";
import getWeb3 from "./utils/getWeb3";

import "./App.css";

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      shopInstance: null,
      web3: null
    };
  }

  componentWillMount() {
    getWeb3
      .then(results => {
        this.setState({
          web3: results.web3
        });

        this.instantiateContract();
      })
      .catch(() => {
        console.log("Error finding web3.");
      });
  }

  instantiateContract() {
    const contract = require("truffle-contract");
    const shop = contract(ShopContract);
    shop.setProvider(this.state.web3.currentProvider);

    this.state.web3.eth.getAccounts((error, accounts) => {
      if (!error) {
        shop.deployed().then(instance => {
          this.setState({ shopInstance: instance });
        });
      }
    });
  }

  render() {
    return <div className="App">Fruit shop</div>;
  }
}

export default App;




사과 구매 함수 작성하기


buyApple() {
  this.state.shopInstance.buyApple({
    from: this.state.myAccount,
    value: this.state.web3.toWei(10, "ether"),
    gas: 900000
  });
}


  • 우리가 Shop.sol 에 작성한 buyApple 함수를 호출합니다.

  • 구매자(from)는 앞서 만든 myAccount 입니다.

  • 가격(value)은 10 ether 입니다. valueether 보다 작은 단위인 wei 이기 때문에 toWei 함수로 10 etherwei 로 환산합니다.

  • gas900000 로 설정합니다.

사과 판매 함수 작성하기


우리가 Solidity 로 작성한 sellMyApple 함수에서 uint 형 파라미터를 받던거 기억 하시죠?:

// Shop.sol 의 sellMyApple 함수
function sellMyApple(uint _applePrice) payable external


Web3.js 에서 파라미터를 넘기면서 호출해주면 됩니다:

sellApple() {
  this.state.shopInstance.sellMyApple(this.state.web3.toWei(10, "ether"), {
    from: this.state.myAccount,
    gas: 900000
  });
}


  • sellMyApple 은 트랜잭션을 실행해야 하므로 이더리움을 누구에서 전송해야 할지 알려줘야 합니다. 따라서 from 정보도 함께 넘겨줍니다.

  • gas900000 로 설정합니다.

컨트랙트에서 내 정보 받아오기

먼저 내 사과 개수를 보관할 myApplesstate 에 새롭게 추가해야겠죠?:

constructor(props) {
  super(props);

  this.state = {
    shopInstance: null,
    myAccount: null,
    myApples: 0,  // myApples 추가
    web3: null
  };
}


구매와 판매 기능은 작성했지만 현재 내가 몇 개의 사과를 가지고 있는지 모릅니다. 다행히 우리는 Shop.solgetMyApples 함수를 작성했습니다:

// Shop.sol 의 getMyApples 함수
function getMyApples() view external returns(uint16)  {
      return myApple[msg.sender];
}


Web3.js 로 이 함수를 호출하고 state 에 내가 가진 사과의 개수를 저장합시다. 이때 객채로 전달되는 result 에서 우리는 사과의 개수만 필요하므로 toNumber 를 사용합니다:

updateMyApples() {
  this.state.shopInstance.getMyApples().then(result => {
    this.setState({ myApples: result.toNumber() });
  });
}


내가 가진 사과 개수를 업데이트 하기 위해 updateMyApples 함수를 적절한 시점에 호출해야합니다.

instantiateContract 에 코드 한 줄을 추가해주세요:

this.state.web3.eth.getAccounts((error, accounts) => {
  if (!error) {
    shop.deployed().then(instance => {
      this.setState({ shopInstance: instance, myAccount: accounts[0] });
      this.updateMyApples();  // 여기서 updateMyApples 호출하기
    });
  }
});


  • 이제 페이지에 접속하면 내가 가진 사과의 개수를 업데이트합니다.

이제 프론트엔드를 작성합시다!



프론트엔드 만들기



사실 저도 React.js 를 이제 막 사용했습니다. 그래서 최대한 간단히 만들겠습니다.

우리가 필요한 컴포넌트는 다음과 같습니다:

  1. 사과의 가격 (텍스트)
  2. 내가 가진 사과 개수 (텍스트)
  3. 내가 가진 사과의 판매 가격 (텍스트)
  4. 구매 (버튼)
  5. 판매 (버튼)

render 함수를 수정해주세요:

render() {
  return (
    <div className="App">
      <h1>사과의 가격: 10 ETH</h1>
      <button onClick={() => this.buyApple()}>구매하기</button>
      <p>내가 가진 사과: {this.state.myApples}</p>
      <button onClick={() => this.sellApple()}>
        판매하기 (판매 가격: {10 * this.state.myApples})
      </button>
    </div>
  );
}



Fruit_Shop_React

실습하기



크롬 브라우져를 실행하고 MetaMask 로 우리가 띄워놓은 Ganache 에 접속합시다.

MetaMask 를 설치 및 실행하고 크롬 익스텐션에서 여우 모양 아이콘을 눌러주세요:

MetaMask_1

  • 드랍 다운 메뉴를 눌러주세요

MetaMask_2

  • Custom RPC 를 눌러주세요.

MetaMask_3

  • http://127.0.0.1:7545 를 입력하고 Save 를 눌러주세요.

Ganache 의 첫번째 계정 Private Key 를 받아와 MetaMask 계정에 추가합시다:

MetaMask_4

  • Ganache 를 열어 첫번째 계정의 열쇠 모양 버튼을 눌러주세요.

  • Private Key 를 복사해주세요.

MetaMask_5

  • Import Account 버튼을 눌러 복사한 Private Key 를 입력해주세요.

  • 성공적으로 계정을 불러오면 IMPORTED 라는 빨간 태그가 있는 계정이 생성됩니다.

MetaMask_6

  • Ganache 의 첫번째 계정과 같은량의 이더리움이 들어있겠죠?

이제 사과를 직접 구매하고 판매해봅시다!

서버를 실행시켜주세요:

> npm run start


http://localhost:3000 에 접속합니다.

Result_1

  • 구매하기 버튼을 눌러 트랜잭션을 진행합니다.

Result_2

  • 사과의 개수와 이더리움이 차감된게 보이시나요?

Result_3

  • 판매하기 버튼으로 우리가 가진 사과를 과일 가게에 팔아봅시다.

Result_4

  • 사과가 모두 팔리고 10 ether 가 다시 들어왔습니다!


Sort:  

짱짱맨 호출에 출동했습니다!!

잘봤습니다

예제를 따라하던 중 web.js의 파일이 어디에 존재해야하는지 궁금합니다.