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
설치가 끝나면
> 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 (
...
)
}
(2) Provider 설정
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);
(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", []);
(4) Signer 저장
블록체인 네트워크에 메시지와 트랜잭션을 보낼 때, 사용자는 지갑의 개인 키를 통해 서명을 하여 같이 보냅니다. 그리고 그 서명을 통해 네트워크 내부의 사용자 지갑 상태를 변경합니다. 서명은 사용자의 개인 키로 진행됩니다.
메타마스크는 사용자의 개인 키 보관과 호출을 지원하지만, 메타마스크는 개인 키에 대한 어떠한 정보를 얻을 수 없습니다.
그리고 개인 키로 서명을 할 때, 개인 키는 JsonRpcProvider에 연결되어 getSigner 함수로 서명된 정보만 얻을 수 있고, 직접적인 개인 키는 얻을 수 없습니다.
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()
위 함수는 모두 Promise Type입니다. 자바스크립트 기본 Promise.all 함수로 한 번에 같이 호출해주었습니다.
result의 결과 값을 보면 index 1, 잔액(balance)은 BigNumber로 결과가 넘어왔습니다.
EVM의 가장 큰 수는 2^256-1로 표현할 수 있습니다. 하지만 프로그래밍 언어에 따라 최대로 표현할 수 있는 숫자의 길이는 전부 다릅니다.
블록체인은 표현하는 숫자의 크기가 크기 때문에 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)
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
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
리액트의 구조와 CSS는 Github에 공유해두었습니다.
원하시는 분은 참고해주시면 될 것 같습니다.
'Block Chain > Web3 Library' 카테고리의 다른 글
Ganache 가나슈 설치 및 연동 방법 || Canache-Cli | Metamask | Remix || (1) | 2022.10.29 |
---|---|
IPFS Node.js 이미지 올리기 || IPFS | ipfs-api | Node.js || back-end .ver (0) | 2022.10.25 |
메타마스크 리액트 토큰(ERC20) 추가 구현 || React | Metamask | ehters.js | EIP-747 || (0) | 2022.10.07 |
메타마스크 리액트 네트워크 추가 & 전환 구현 || React | Metamask | ehters.js || (0) | 2022.10.02 |
블록체인 네트워크 ID별 데이터 리스트(List) || JavaScript || KR (0) | 2022.09.29 |
댓글