[Web3.js]React, Solidity, Web3.js로 dApp 만들어 보기

Jay·2022년 3월 11일
5
post-thumbnail

※ 다음 글은 해당 포스트의 번역본입니다.

1. Tools

이번 블로그 포스트에서 사용하게 될 툴입니다.

  1. Truffle Framework
  2. Ganache
  3. Solidity 0.8.10
  4. Metamask
  5. React 17.0.2
  6. Web3.js 1.6.1

시작하기에 앞서, 위에 언급한 툴에 대해 간략히 설명해 볼게요.

첫 번째로 Truffle Framework는 이더리움 스마트 컨트랙트를 개발하기 위한 툴을 제공합니다. 스마트 컨트랙트 관리, 개발과 마이그레이션, 네트워크 관리, 개발용 콘솔 등의 기능 또한 제공합니다.

Ganache는 개인용 블록체인에서도 퍼블릭 블록체인의 작용을 모방하기 위해 사용되는 이더리움 개발 툴입니다.

Solidity는 스마트 컨트랙트를 실행하기 위한 객체 지향의 고급 프로그래밍 언어입니다. 자세한 내용이 알고 싶으면 이곳을 클릭하세요.

대부분의 브라우저는 현재 블록체인 네트워크에 연결할 수 없도록 하고 있습니다. 그래서 저는 Metamask 크롬 확장프로그램을 사용할 겁니다. 메타마스크는 크롬 브라우저로 하여금 블록체인 네트워크에 연결할 수 있도록 해 주거든요.

UI와 프론트단 개발을 위해, 우리는 프론트엔드 커뮤니티 내에서 가장 많이 쓰이는 자바스크립트 라이브러리 중 하나인 리액트 라이브러리를 사용할 겁니다.

Web3.js는 이더리움 블록체인과 연결할 수 있게 해 주는 자바스크립트 라이브러리입니다. Web3.js로 블록체인을 활용한 리액트 앱을 만들 겁니다.

2. Setting Up Tools

가장 먼저 아래의 명령문으로 Truffle Framework를 설치해 주세요.

npm install -g truffle

다음은 Ganache를 다운로드 받아주세요. 설치 후, 프로그램을 실행하면 아래와 같은 화면이 보일 겁니다.

다음은 Metamask가 필요합니다. 크롬에서 Metamask 라는 확장 프로그램을 추가하면 아래와 같은 화면이 보입니다. Metamask를 세팅하기 위해 이 블로그 포스트를 참고해 주세요.

3. Writing Code

준비가 됐으면, 스마트 컨트랙트를 작성하기 위한 단계로 넘어가 봅시다. 터미널을 열고, 당신의 프로젝트 폴더 안에 아래의 명령어로 폴더를 만들어 주세요.

mkdir blockchain

이제 아래의 명령어로 blockchain 폴더 안에 또다른 폴더를 만들고 해당 폴더 안에 진입합니다.

cd blockchain
mkdir contracts
cd contracts

그리고 스마트 컨트랙트 개발을 위한 truffle 프로젝트를 생성해 봅시다. 아래의 명령어를 사용하세요.

truffle init

위의 명령어를 사용하면, 이와 같은 결과를 볼 수 있습니다.

이제 선호하는 텍스트 에디터로 truffle 프로젝트를 여세요. 아래의 폴더와 파일 구조가 보이죠?

contracts 폴더 안에 우리의 스마트 컨트랙트를 작성할 겁니다.

migrations 폴더 안에는 우리가 새로 생성한 스마트 컨트랙트를 집어 넣을 거예요.

test 폴더 안에는 보통 스마트 컨트랙트를 테스트하기 위한 코드를 작성하지만 이는 해당 포스트에서 배울 내용이 아니기에 여기선 사용하지 않겠습니다. 퍼블릭 블록체인 노드로 배포하기 전에, 당신의 스마트 컨트랙트를 테스트하기 위한 코드를 작성하는 걸 권장합니다.

truffle-config.js 파일은 truffle 프로젝트의 모든 구성 내용을 포함합니다.

이제 스마트 컨트랙트를 작성해 봅시다. contracts 폴더 안에 contacts.sol 라는 이름의 파일을 생성해 주세요. 그리고 해당 파일 안에 아래의 코드를 넣어 주세요.

pragma solidity >=0.4.22 <0.9.0;

이 코드는 반드시 당신의 스마트 컨트랙트 파일의 첫 번째 줄에 와야합니다. 이 코드로 우리는 solidity의 버전을 명시할 수 있어요.

이제 아래의 코드로 우리의 첫 번째 스마트 컨트랙트를 만들어 봅시다.

pragma solidity >=0.4.22 <0.9.0;

contract Contacts {
  
}

contract 라는 키워드를 사용해 스마트 컨트랙트 코드를 작성할 수 있으며, 이 키워드 뒤에는 컨트랙트의 이름이 따라 붙게 되는데 이 예제에서는 Contracts 라고 하겠습니다.

이제 스마트 컨트랙트 안에 있는 컨트랙트 개수를 카운트하기 위한 변수를 생성합니다.

pragma solidity >=0.4.22 <0.9.0;

contract Contacts {
  uint public count = 0; // state variable
}

이 변수는 특수 변수이며 이 변수에 할당된 값은 블록체인 저장소에 저장될 것입니다. 우리는 public 이라는 접근 제어자를 사용하여 스마트 컨트랙트의 외부에서도 이 변수에 접근할 수 있게끔 했습니다. 일단 이 변수에 0을 할당해 두었습니다.

상태 변수를 생성했고, 거기에 값도 할당했습니다. 이제 프론트단으로 가서 이 퍼블릭 상태 변수에 접근해 보도록 합시다. 그러기 위해선, 리액트 앱과 우리의 스마트 컨트랙트가 통신할 수 있도록 해야 합니다.

리액트 앱을 생성하기 위해 아래의 명령문을 blockchain 폴더 안에서 실행해 주세요.

npx create-react-app contacts
cd contacts
yarn add web3

보시다시피, 이 프로젝트의 dependency에 web3.js 를 추가했어요. 이건 리액트 앱을 스마트 컨트랙트와 상호작용할 수 있게 해 줍니다.

이제 App.js 파일 안에 코드를 작성해 보겠습니다. 에디터에서 해당 파일을 오픈하고 아래의 코드를 넣어 주세요.

import { useEffect, useState } from 'react';
import Web3 from 'web3';

function App() {
  return (
    <div>
    </div>
  );
}

export default App;

React에서 두 개의 훅과 Web3.js 에서 Web3를 import 해 왔어요. 이제 아래와 같이 상태 변수를 생성해 주세요.


import { useEffect, useState } from 'react';
import Web3 from 'web3';

function App() {
  const [account, setAccount] = useState(); // state variable to set account.
  return (
    <div>
    </div>
  );
}

export default App;

이제 useEffect 훅 안에 스마트 컨트랙트 코드를 아래처럼 넣어 주세요.

import { useEffect, useState } from 'react';
import Web3 from 'web3';

function App() {
  const [account, setAccount] = useState(); // state variable to set account.
  
  useEffect(() => {
    async function load() {
      const web3 = new Web3(Web3.givenProvider || 'http://localhost:7545');
      const accounts = await web3.eth.requestAccounts();
      
      setAccount(accounts[0]);
    }
    
    load();
   }, []);
  
   return (
     <div>
       Your account is: {account}
     </div>
   );
}

export default App;

클라이언트 사이드를 성공적으로 작동시키기 위해 백엔드에서 몇 가지 과정을 처리해야 합니다. contrcts 폴더로 돌아가 주세요. truffle-config.js 파일을 열고 아래의 속성을 추가해 주세요.


module.exports = {
  networks: {
    development: {
      host: "127.0.0.1",
      port: 7545,
      network_id: "*"
    }
  },
  compilers: {
    solc: {
      optimizer: {
        enabled: true,
        runs: 200
      }
    }
  }
}

migrations 폴더로 와서, 2_deploy_contracts.js 파일을 생성하고 아래의 코드를 붙여 넣어 주세요.

const Contacts = artifacts.require("./Contacts.sol");

module.exports = function(deployer) {
  deployer.deploy(Contacts);
};

터미널을 열고, 컨트랙트를 migrate 시키기 위한 명령문을 실행하세요.

truffle migrate

아래와 같은 결과물을 볼 수 있을 거예요.

프론트단 폴더로 돌아와서 아래의 명령문을 실행해 주세요.

yarn start

당신의 브라우저에서 리액트 앱이 열리면서, 당신의 블록체인 네트워크와 소통할 수 있게 해 줄 Metamask. 아래와 같은 화면이 보여야 합니다.

위 화면에서 Next 버튼을 누르게 되면, 아래의 화면이 보여야 합니다.

Connect 버튼을 누르세요. 그럼 당신의 계정 번호가 아래와 같이 보여야 합니다.

성공적으로 스마트 컨트랙트와 연결하여 계정 id를 가져왔네요. 이제 우리의 스마트 컨트랙트 안에 컨트랙트 목록을 가져오고 프론트단으로 보내서 화면에 렌더링하는 기능을 만들어 봅시다.

contracts 폴더로 돌아가서 Contracts.sol 파일을 열고 아래의 함수를 추가해 주세요.

pragma solidity >=0.4.22 <0.9.0;

contract Contacts {
  uint public count = 0; // state variable
  
  struct Contact {
    uint id;
    string name;
    string phone;
  }
  
  constructor() public {
    createContact('Zafar Saleem', '123123123');
  }
  
  mapping(uint => Contact) public contacts;
  
  function createContact(string memory _name, string memory _phone) public {
    count++;
    contacts[count] = Contact(count, _name, _phone);
  }
}

※ 혹시 Warning: Visibility for constructor is ignored. If you want the contract to be non-deployable, makiing it "abstract" is sufficient. 라는 에러가 발생한다면, contructor() public {...}에서 public을 제거해주면 됩니다.

변경사항이 생겼으니 contract를 다시 migrate 해 주어야 합니다. 왜냐하면 스마트 컨트랙트는 변하지 않기 때문이죠.

truffle migrate

프론트단의 contacts 폴더로 가 주세요. /src 폴더 안에 config.js라는 파일을 생성하고 아래의 코드를 넣어 주세요.

export const CONTACT_ADDRESS = '0xfAd567EBdCb36f49F3a509FEDF9e72E3ad75ca59';

export const CONTACT_ABI = [
  {
    "constant": true,
    "inputs": [],
    "name": "count",
    "outputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function",
    "signature": "0x06661abd"
  },
  {
    "constant": true,
    "inputs": [
      {
        "name": "",
        "type": "uint256"
      }
    ],
    "name": "contacts",
    "outputs": [
      {
        "name": "id",
        "type": "uint256"
      },
      {
        "name": "name",
        "type": "string"
      },
      {
        "name": "phone",
        "type": "string"
      }
    ],
    "payable": false,
    "stateMutability": "view",
    "type": "function",
    "signature": "0xe0f478cb"
  },
  {
    "inputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "constructor",
    "signature": "constructor"
  },
  {
    "constant": false,
    "inputs": [
      {
        "name": "_name",
        "type": "string"
      },
      {
        "name": "_phone",
        "type": "string"
      }
    ],
    "name": "createContact",
    "outputs": [],
    "payable": false,
    "stateMutability": "nonpayable",
    "type": "function",
    "signature": "0x3dce4920"
  }
];

ABI(Application Binary Interface)란, 컨트랙트 함수와 매개변수들을 JSON 형식으로 나타낸 리스트다.
참조: ABI

App.js 안에 스마트 컨트랙트와 ABI를 import 해 옵니다. 아래의 코드처럼 작성해 주세요.

import { useEffect, useState } from 'react';
import Web3 from 'web3';
import { CONTACT_ABI, CONTACT_ADDRESS } from './config';

function App() {
  const [account, setAccount] = useState();
  const [contactList, setContactList] = useState();
  const [contacts, setContacts] = useState([]);

  useEffect(() => {
    async function load() {
      const web3 = new Web3(Web3.givenProvider || 'http://localhost:7545');
      const accounts = await web3.eth.requestAccounts();
      setAccount(accounts[0]);
      // Instantiate smart contract using ABI and address.
      const contactList = new web3.eth.Contract(CONTACT_ABI, CONTACT_ADDRESS);
      // set contact list to state variable.
      setContactList(contactList);
      // Then we get total number of contacts for iteration
      const counter = await contactList.methods.count().call();
      // iterate through the amount of time of counter
      for (var i = 1; i <= counter; i++) {
        // call the contacts method to get that particular contact from smart contract
        const contact = await contactList.methods.contacts(i).call();
        // add recently fetched contact to state variable.
        setContacts((contacts) => [...contacts, contact]);
      }
    }
    
    load();
  }, []);
  
  return (
    <div>
      Your account is: {account}
    </div>
  );
}

export default App;

위의 코드는 스마트 컨트랙트로부터 모든 컨트랙트를 가져온 다음, 상태변수 contacts안에 저장시켜 줍니다.

이제 모든 컨트랙트를 렌더링해 봅시다.

import { useEffect, useState } from 'react';
import Web3 from 'web3';
import { CONTACT_ABI, CONTACT_ADDRESS } from './config';

function App() {
  const [account, setAccount] = useState();
  const [contactList, setContactList] = useState();
  const [contacts, setContacts] = useState([]);
  
  useEffect(() => {
    async function load() {
      const web3 = new Web3(Web3.givenProvider || 'http://localhost:7545');
      const accounts = await web3.eth.requestAccounts();
      setAccount(accounts[0]);
      // Instantiate smart contract using ABI and address.
      const contactList = new web3.eth.Contract(CONTACT_ABI, CONTACT_ADDRESS);
      // set contact list to state variable.
      setContactList(contactList);
      // Then we get total number of contacts for iteration
      const counter = await contactList.methods.count().call();
      // iterate through the amount of time of counter
      for (var i = 1; i <= counter; i++) {
        // call the contacts method to get that particular contact from smart contract
        const contact = await contactList.methods.contacts(i).call();
        // add recently fetched contact to state variable.
        setContacts((contacts) => [...contacts, contact]);
      }
    }
    
    load();
    
  }, []);
  
  return (
    <div>
      Your account is: {account}
      <h1>Contacts</h1>
      <ul>
      {
        Object.keys(contacts).map((contact, index) => (
          <li key={`${contacts[index].name}-${index}`}>
            <h4>{contacts[index].name}</h4>
            <span><b>Phone: </b>{contacts[index].phone}</span>
          </li>
        ))
      }
      </ul>
    </div>
  );
}

export default App;

브라우저에서 리액트 앱을 실행시키면, 업데이트 된 코드를 화면 안에서 볼 수 있습니다.

이 화면이 보인다면, 축하합니다. 당신은 블록체인의 스마트 컨트랙트와 상호작용하는 웹 앱으로 방금 첫 dAPP을 만들었어요.

이제 당신은 스마트 컨트랙트로부터 정보를 가져오는 법을 알게 됐으니, 새로운 컨트랙트 추가, 컨트랙트 업데이트 및 제거 기능을 개발하여 전체적인 CRUD를 구현해 보세요.

글 마치겠습니다. 프로젝트의 전체 코드는 아래의 GitHub 리포지토리에서 확인해 보세요.

🔗GitHub

🔗원문 출처 : Build a Real-World dApp With React, Solidity, and Web3.js

profile
개발할래요💻

0개의 댓글