본문 바로가기
Block Chain/Web3 Library

메타마스크 리액트 지갑 연동 || React | Metamask | ethers.js || KR

by 개발이 체질인 나그네 2022. 9. 30.
반응형

Metamask는 이더리움에서 만든 CA 관리 애플리케이션입니다. CA 생성뿐만 아니라, Coin 및 Token 관리, 트랜잭션 , 계좌 생성, private key 관리까지 블록체인 네트워크를 이용하기 위한 다양한 서비스를 제공합니다.

 

 

때문에 web3 dapp을 개발하는 사람은 필수적으로 프론트에 Metamask를 연결할 줄 알아야 합니다.

 

오늘은 가장 많이 사용하는 프론트 프레임워크 리액트와 Metamask를 연동시키는 방법을 ethers 모듈을 이용해 만들어 보겠습니다.

 

※ 본 게시글은 프론트엔드 개발 분야가 아닙니다. CSS와 디자인 요소는 다루지 않습니다. 원하시는 분은 Github에 올린 코드를 클론 하시면 됩니다.

※ 본 게시글은 리액트 강의 게시글이 아닙니다. 리액트에 대한 기본지식이 있는 개발자를 위한 교육 게시글이니 참고해주시기 바랍니다.

 

(1) 준비 단계

- CRA

npx create-react-app front

cd ./front
npm i ethers

원하시는 폴더에서 위 코드를 통해 리액트 패키지를 설치하고 ethers 모듈을 설치해주세요.

 

- import ethers

App.jsx

설치가 끝나면

> import { ethers } from"ethers";

 

위 코드를 App.jsx 상단에 import 해주세요. 해당 게시글은 ethers를 통해 메타마스크에서 제공되는 코드(RPC)를 사용합니다.

 

- connectWallet 함수 만들기

import { useState, useEffect, useCallback } from "react";
import { ethers } from "ethers";

  function App() {

  		...
        
    const connectWallet = useCallback(async () => {
        try {
        // 메타마스크 설치 된 경우
          if(typeof window.ethereum !== 'undefined') {
          // todo
          
          // 메타마스크 설치 안된 경우
          } else {
            alert("please install MetaMask")
          }
        } catch (error) {
          console.log(error);
        }
      },[])
      
     	...
      
    return (
    ...
    )
  }

 

메타마스크는 브라우저에 설치되어 window.ethereum 명령어를 통해 데이터를 전달합니다.
 
만약 window.ethereum가 undefined라면 사용자의 브라우저에 메타 마스크가 설치되어있지 않다는 걸 의미합니다.
 
 

(2) Provider 설정

 

블록체인 네트워크와 상호작용하기 위해선 RPC 표준을 사용해야합니다. 이는 이더리움 네트워크에서 정한 표준이며,

Provider는 이더리움 네트워크와 상호작용하기 위한 RPC 표준을 사용하기 편리하게 만들어 인터페이스를 제공합니다.

import { useState, useEffect, useCallback } from "react";
import { ethers } from "ethers";

  function App() {
  	const [provider, setProvider] = useState(undefined);
    
    	...

        const getProvider = async () => {
            // 메타마스크에서 제공하는 provider를 ethers 모듈에 저장
            const provider = await new ethers.providers.Web3Provider(
            	window.ethereum
               );
               
            // 상태변수 저장
            setProvider(provider);
            return provider;
      }

    	...
      
    return (
    ...
    )
  }

ethers 모듈에서 제공하는 Web3Provider 를 통해 메타마스크에서 제공하는 인터페이스를 저장할 수 있습니다.

> await new ethers.providers.Web3Provider(window.ethereum);

이후 블록체인에 트랜잭션을 보내기 위해, 지원해준 Provider통해 이더리움 네트워크가 이해할 수 있는 양식으로 변환하게 됩니다.
 
 
 

(3) Metamask 연동 승인

메타마스크를 만든 Dapp에서 이용하기 위해선 먼저 연결을 승인해야 합니다. 불법 사이트나 피싱사이트에서 계정 정보를 사용하는 걸 방지하고, 신뢰하는 사이트에 연결을 하기 위함입니다.

import { useState, useEffect, useCallback } from "react";
import { ethers } from "ethers";

  function App() {
    
    	...

        const getSigner = async provider => {
            // 메타마스크에 홈페이지 연동 승인 요청
            await provider.send("eth_requestAccounts", []);
  	}

    	...
      
    return (
   	...
    )
  }

> await provider.send("eth_requestAccounts", []);

위 함수를 통해 메타마스크에 연동 승인을 요청할 수 있습니다.
 
※ 기존에는 window.ethereum.enable() 사용했지만, 메타마스크 공식문서에서 곧있으면 해당 함수는 더이상 지원되지 않는다고 합니다.   

 

(4) Signer 저장

블록체인 네트워크에 메시지와 트랜잭션을 보낼 때, 사용자는 지갑의 개인 키를 통해 서명을 하여 같이 보냅니다. 그리고 그 서명을 통해 네트워크 내부의 사용자 지갑 상태를 변경합니다. 서명은 사용자의 개인 키로 진행됩니다.

메타마스크는 사용자의 개인 키 보관과 호출을 지원하지만, 메타마스크는 개인 키에 대한 어떠한 정보를 얻을 수 없습니다.

그리고 개인 키로 서명을 할 때, 개인 키는 JsonRpcProvider에 연결되어 getSigner 함수로 서명된 정보만 얻을 수 있고, 직접적인 개인 키는 얻을 수 없습니다.

> ethers Signer 공식문서 링크

 

import { useState, useEffect, useCallback } from "react";
import { ethers } from "ethers";

  function App() {
  
    	const [signer, setSigner] = useState(undefined)
    	...

        const getSigner = async (provider) => {
            await provider.send("eth_requestAccounts", []);
            
            // 메타마스크로 서명 요청
            const signer = provider.getSigner();
            // 서명 저장
    	    setSigner(signer)
            return signer;
  	}

    	...
      
    return (
   	...
    )
  }

 

(5) 지갑 정보 요청(주소/네트워크/잔액)

메타마스크로 요청을 보내, 현재 사용자의 지갑 주소와 연결된 네트워크 ID, 현재 보유 중인 코인 잔액 데이터를 얻어보겠습니다.

import { useState, useEffect, useCallback } from "react";
import { ethers } from "ethers";

  function App() {
  
          const [walletAddress, setWalletAddress] = useState(undefined)
          const [currentBalance, setCurrentBalance] = useState(undefined)
          const [chainId, setChainId] = useState(undefined)
  
    	...

          const getWalletData = async(signer) => {
            const result = await Promise.all([
            	signer.getAddress(),
                signer.getBalance(),
                signer.getChainId()
              ])

            setWalletAddress(result[0])
            setCurrentBalance(Number(result[1]))
            setChainId(result[2])

          }

    	...
      
    return (
   	...
    )
  }

 

지갑 주소(address) : signer.getAddress()

현재 잔액(balance) : signer.getBalance()

네트워크 Id : signer.getChainId()

> ethers signer 함수 문서 공식문서 링크

 

위 함수는 모두 Promise Type입니다. 자바스크립트 기본 Promise.all 함수로 한 번에 같이 호출해주었습니다.

result의 결과 값을 보면 index 1, 잔액(balance)은 BigNumber로 결과가 넘어왔습니다.

EVM의 가장 큰 수는 2^256-1로 표현할 수 있습니다. 하지만 프로그래밍 언어에 따라 최대로 표현할 수 있는 숫자의 길이는 전부 다릅니다.

자바스크립트 int 최대 표현

블록체인은 표현하는 숫자의 크기가 크기 때문에 BigNumber를 사용합니다. 

때문에 우리가 쉽게 볼 수 있는 10진수로 변환을 해야 합니다.

 

> setCurrentBalance(Number(result[1]))

 

잔액 데이터를 상태 변수에 저장할 때, Number()를 통해 10진수로 변환해 주었습니다.

 

(6) connectWallet 함수 정리

import { useState, useEffect, useCallback } from "react";
import { ethers } from "ethers";

  function App() {

        const [provider, setProvider] = useState(undefined);
        const [signer, setSigner] = useState(undefined)
        const [walletAddress, setWalletAddress] = useState(undefined)
        const [currentBalance, setCurrentBalance] = useState(undefined)
        const [chainId, setChainId] = useState(undefined)

      const connectWallet = useCallback(async () => {
        try {
          if(typeof window.ethereum !== 'undefined') {
            const _provider = await getProvider();
            const _signer = await getSigner(_provider);
            await getWalletData(_signer);

          } else {
            alert("please install MetaMask")
          }
        } catch (error) {
          console.log(error);
        }
      },[])
      
     	...
      
    return (
    ...
    )
  }

 

지금 까지 만든 함수를 connectWallet 함수에 넣어 마지막으로 정리하였습니다.

 

<div className="btn connectButton" 
    onClick={() => props.connectWallet()}>Connect Wallet</div>

이제 ConnectWallet 버튼을 누를 때, connectWallet()을 onClick 메서드로 실행시키면 됩니다.

 

(7) 데이터 정리 하기

[connect Wallet] 버튼을 누르면 위 사진과 같은 데이터를 UI에서 보여줍니다. 하지만 사용자 입장에서 보기 불편하고, 정보도 한눈에 들어오지 않습니다.

마지막으로 데이터를 정리해주겠습니다.

 

- 잔액(balance)

블록체인 네트워크는 소수점을 표현할 수 없습니다. 때문에 Decimal 개념을 도입하여 소수점만큼 자릿수를 늘려 표현해줍니다.

 

ex)
- USDT
Decimal : 6
1234567 => 1.234567

-ETH
Decimal : 18
10569506558633116000 => 10.569506558633116000

 

ethers 모듈에는 Decimal 18 이더를 표현하기 좋게 해주는 기능이 있습니다.

 

> ethers.utils.formatEther(bigNum)

> ethers 공식문서 링크

 

import { useState, useEffect, useCallback } from "react";
import { ethers } from "ethers";

  function App() {
  
          const [walletAddress, setWalletAddress] = useState(undefined)
          const [currentBalance, setCurrentBalance] = useState(undefined)
          const [chainId, setChainId] = useState(undefined)
  
    	...

          const getWalletData = async(signer) => {
            const result = await Promise.all([
            	signer.getAddress(),
                signer.getBalance(),
                signer.getChainId()
              ])

            setWalletAddress(result[0])
            // Decimal 18 포멧
            setCurrentBalance(Number(ethers.utils.formatEther(result[1])))
            setChainId(result[2])

          }

    	...
      
    return (
   	...
    )
  }

 

- 주소(address)

개인 지갑 주소(CA)는 0x + 64개 문자열로 이뤄져 있습니다. 사용자는 전체 길이를 외우지 않고 주로 앞 5~10글자를 보고 본인의 지갑 주소가 맞는지를 판단합니다.

const displayWalletAddress = `${walletAddress?.substring(0,10)}...`;

자바스크립트 기본문법 substring()를 통해 10자리까지만 표현하도록 했습니다.

 

- 네트워크

블록체인은 각 네트워크마다 네트워크 ID를 갖고 있습니다. 메타마스크에선 사용자가 연결된 네트워크 상태를 ID 값으로 알려줍니다.

 

https://borntodevelop.tistory.com/53

 

블록체인 네트워크 ID별 데이터 리스트(List) || JavaScript || KR

블록체인 네트워크는 각자 자기만의 ID을 갖고 있습니다. 메타마스크나 지갑 어플에서 사용자가 연결된 네트워크를 ID로 데이터를 보내줍니다. 비개발자 및 Dapp 사용자들은 UI상으로 ID 값을 보여

borntodevelop.tistory.com

 

 

import chainIds from "../chainList/chainIds";


    <span>{displayCurrentBalance} {chainIds[chainId].symbol}</span>
    <span>{chainIds[chainId].name}</span>

위 게시글에 네트워크 ID 별로 블록체인 이름과 심벌을 리턴해주는 코드를 만들어놨습니다.

저 코드를 이용하여 메타마스크에서 보내는 ID값을 네트워크 이름과 심볼을 얻어서 UI에 표현해보겠습니다.

 

- 최종

 

정리 전 :

정리 후:

 


지금까지 메타마스크와 리액트를 통해 UI상으로 사용자의 지갑 정보를 보여주는 작업을 실습해봤습니다.

CSS나 React 강의가 아닌 ethers 모듈과 메타마스크 API를 보여주는 게시글이었습니다.

 

https://github.com/imelon2/BornToDev_React_web3/tree/master/connectWallet

 

GitHub - imelon2/BornToDev_React_web3

Contribute to imelon2/BornToDev_React_web3 development by creating an account on GitHub.

github.com

리액트의 구조와 CSS는 Github에 공유해두었습니다.

원하시는 분은 참고해주시면 될 것 같습니다.

 


 

 

 

반응형

댓글