서론
web3.js란?
web3.js 는 내부적으로 HTTP 나 IPC를 통해 JSON RPC API를 호출하도록 되어있습니다.
만약, 스마트 컨트렉트의 함수를 실행하고자 한다면 1.스마트 컨트렉트의 주소 2. 실행할 함수 3. 함수에 전달할 변수들을 전달해야 합니다. 이더리움 네트워크는 노드로 구성되어 있고, 각 노드는 블록체인의 복사본을 가지고 있으며 이더리움 노드들은 JSON-RPC로만 소통할 수 있는데 web3.js는 쉽고 편리하게 자바스크립트 인터페이스로 상호작용할 수 있게 해주는 라이브러리입니다.
web3-react란?
react 앱에서 Context를 이용해 web3의 Dapp과 관련된 특정 주요 데이터(사용자의 현재 계정, chain id, web3 provider 등)를 최신상태로 유지해 주는 state machine입니다.
지갑 연동 및 스마트컨트랙트 ABI와 상호작용을 쉽고 간단하게 할 수 있도록 도와줍니다.
라이브러리 설치
npm i @web3-react/core @web3-react/injected-connector web3
npm i react-device-detect
Web3ReactProvider 설정하기
web3Provider는 우리의 web3가 이더리움 네트워크의 어떤 노드와 소통해야 하는지 지정하는 역할을 하며 web3 객체를 인스턴스화하는 getLibrary 함수를 정의하여 prop로 전달합니다.
// src > index.tsx
import React, { Suspense } from 'react';
import ReactDOM from 'react-dom/client';
import { Web3ReactProvider } from '@web3-react/core';
import Web3 from 'web3';
export const getLibrary = (provider: any): Web3 => {
return provider;
};
const root = ReactDOM.createRoot(document.getElementById('root')!);
root.render(
<React.StrictMode>
<Web3ReactProvider getLibrary={getLibrary}>
...
<Suspense>
<Router />
</Suspense>
...
</Web3ReactProvider>
</React.StrictMode>
);
메타마스크 지갑 app & extension 설치 확인
// modalConnectWallet.tsx
import React from "react";
import { isMobile } from "react-device-detect";
import { useRecoilValue } from "recoil";
import styled from "styled-components";
import useAuth from "../hooks/useAuth";
import { walletAddressAtom } from "../states/atom";
export default function ModalConnectWallet() {
const dapplink = `https://metamask.app.link/dapp/${window.location.host}`;
const { login, logout } = useAuth();
// 전역 state
const walletAddress = useRecoilValue(walletAddressAtom);
const connectMeta = () => {
const checkEthereum = window.ethereum; // 메타마스크 지갑 설치 확인
if (checkEthereum) {
// 설치가 되어있다면, 로그인 로직 진행
login();
} else {
// 설치가 안되어있을 때, 설치 app & extension url로 이동.
if (isMobile) {
window.open(dapplink, '_blank');
} else {
if (
window.confirm('Do you want to install Metamask to use the service?')
) {
window.open(
'https://chrome.google.com/webstore/detail/metamask/nkbihfbeogaeaoehlefnkodbefgpgknn'
);
}
}
}
};
return (
<Wrap>
<Logo>🍋</Logo>
{walletAddress ? (
<WalletWrap>
<Wallet>{walletAddress}</Wallet>
<Logout onClick={() => logout()}>Logout</Logout>
</WalletWrap>
) : (
<WalletMeta type="button" onClick={() => connectMeta()}>
METAMASK
</WalletMeta>
)}
</Wrap>
);
}
1. 메타마스크 지갑이 설치되어 있는지 확인
window.ethereum 명령어로 확인할 수 있습니다.
typescript를 사용하는 경우, Property 'ethereum' does not exist on type 'Window & typeof globalThis'. 타입 지정에 대한 에러를 만날 수 있는데 이때는 react-app-env.d.ts 파일을 만들어 타입을 지정해 주는 방법으로 해결할 수 있습니다.
✅ window.ethereum 타입에러 해결하기
// src > react-app-env.d.ts (경로는 꼭 src 안에)
interface Window {
ethereum: any;
}
2. 설치가 안되어있을 때 모바일은 app 설치 url로, PC는 chrome extension url로 이동시키기.
이 부분은 필수는 아니며, 사용자 경험을 더 좋게 하기 위한 작업입니다 😀
메타마스크 지갑 연결하기
// src > utils > web3 > web3Connector.ts
import { InjectedConnector } from '@web3-react/injected-connector';
const chainId = 1; // ethereum network
const injected = new InjectedConnector({ supportedChainIds: [chainId] });
export const connectors = {
injected: injected, // metamask용 connector
};
// src > hooks > useAuth.ts
import { useCallback } from 'react';
import { useSetRecoilState } from 'recoil';
import { UnsupportedChainIdError, useWeb3React } from '@web3-react/core';
import {
NoEthereumProviderError,
UserRejectedRequestError as UserRejectedRequestErrorInjected,
} from '@web3-react/injected-connector';
import { connectors } from '../utils/web3/web3Connector';
import { walletAddressAtom } from '../states/atom';
const useAuth = () => {
const { activate, deactivate } = useWeb3React();
const setWalletAddress = useSetRecoilState(walletAddressAtom);
const login = useCallback(() => {
if (connectors.injected) {
activate(connectors.injected, async (error: Error) => {
if (error instanceof NoEthereumProviderError) {
// 지갑이 설치되어있지 않은 경우
} else if (error instanceof UnsupportedChainIdError) {
// 지원하지 않는 네트워크일 경우
} else if (error instanceof UserRejectedRequestErrorInjected) {
// 유저가 지갑 연결을 거부한 경우
} else {
console.log('otherError', error);
}
});
}
}, []);
const logout = useCallback(() => {
deactivate();
// logout api 연결 및 로직처리
setWalletAddress('');
window.localStorage.removeItem('account');
}, []);
return { login, logout };
};
export default useAuth;
1. 메타마스크 전용 connector, injected 설정.
wallet을 dapp에 연결하기 위해서는 해당 wallet에 맞는 connector를 activate 함수에 전달해야 합니다. (metamask의 경우 injectedConnector, coinbase의 경우 walletlinkConnector를 portis의 경우 portisConnector를 설치 필요)
이때, chain id를 사용하여 원하는 네트워크로 연결할 수 있으며 지갑에 네트워크가 추가되어있지 않다면, UnsupportedChainIdError 로 에러가 떨어집니다.
2. useWeb3React 활용 context 값 접근하기
activate가 잘 연결되었다면 Web3ReactProvider 에서는 아래 subtree에서 context 값에 접근할 수 있도록 useWeb3React 라는 hook을 제공합니다.
✅ useWeb3React context값 정보
const {
connector, // 현재 dapp에 연결된 월렛의 connector 값
library, // web3 provider 제공
chainId, // dapp에 연결된 account의 chainId
account, // dapp에 연결된 account address
active, // dapp 유저가 로그인 된 상태인지 체크
error,
activate, // dapp 월렛 연결 기능 수행 함수
deactivate // dapp 월렛 연결 해제 수행 함수
} = useWeb3React();
3. 지갑연결 시 발생하는 에러별 처리하기
@web3-react/injected-connector 에서 제공하는 에러 클래스를 사용하면, 각 에러 상황에 맞는 로직을 실행할 수 있습니다.
4. deactivate()를 사용하여 로그아웃 처리 하기.
실제 지갑과 사이트의 연결은 deactivate()를 사용해야 끊기며, 이 함수를 사용하지 않은 상태로 다시 지갑연결을 하는 경우에는 메타마스크 내에서 이미 지갑이 연결되어 있다 판단하여 새로운 연결 창을 띄우지 않습니다.
💡 메타마스크 지갑연결 창 안 뜰 때
메타 마스크 지갑 열기 -> 지갑주소 옆 세로 땡땡땡 누르기 -> 테스트하는 연결된 사이트 연결 해제 누르기
지갑 연결 후 로그인 처리하기
activate 메서드는 해당 요청의 지갑 연결이 되었는지를 결괏값으로 반환하지 않기 때문에, router상에 이벤트를 tracker 할 수 있는 tsx파일을 만들어 useWeb3React의 account 값으로 지갑 연결을 체크합니다.
저는 새로고침시에도 지갑 연결이 끊기지 않게 로컬스토리지에도 로그인여부를 확인할 수 있는 값을 넣어 작업하였습니다.
// src > Router.tsx
import React from 'react';
import { Route, Routes, BrowserRouter } from 'react-router-dom';
import MetaTracker from './components/MetaTracker';
import Header from './components/Header';
const Main = React.lazy(() => import('./pages/Main/Index'));
export default function Router() {
return (
<BrowserRouter>
<MetaTracker /> {/* 해당 컴포넌트에서 지갑 연결 상태 확인 */}
<Header />
<Routes>
<Route path="/" element={<Main />} />
</Routes>
</BrowserRouter>
);
}
// src > components > MetamaskTracker.tsx
import React, { useEffect } from 'react';
import { useWeb3React } from '@web3-react/core';
import { useSetRecoilState } from 'recoil';
import { walletAddressAtom } from '../states/atom';
export default function MetaTracker() {
const { account } = useWeb3React();
const setWalletAddress = useSetRecoilState(walletAddressAtom);
const { login } = useAuth();
useEffect(() => {
if (account && account !== '') {
// 로그인 api 연결 및 로그인 상태값 설정
setWalletAddress(account);
window.localStorage.setItem('account', account);
}
}, [account]);
useEffect(() => {
// 새로고침시 로그인 풀림 방지
if (localAccount !== null) {
// Web3 account 현재계정 업데이트
login();
}
}, []);
return <div />;
}
useEffect를 사용하여 useWeb3React의 context account값에 따라 내부 로직이 실행될 수 있게 dependency에 account를 넣고 login 로직(api 연결, 로그인 상태값 변경 등)을 처리합니다.
useWeb3React()에서 제공하는 account를 사용하여 지갑주소를 사용해도 되지만, 조금 더 보안을 강화하기 위해 서명값을 받아와 로그인을 처리하려 하여 클라이언트 쪽에서 로그인 상태 여부를 확인할 수 있는 지갑주소는 전역 state로 따로 설정하였습니다.
(지갑 연결 후 서명을 하지 않았을 때는, 로그인 처리가 되지 않아야 하기 때문)
메타마스크 서명은 다음 포스팅에서 다루도록 하겠습니다.
'Web > React & Next.js' 카테고리의 다른 글
React | axios interceptors header에 token값 설정&추가하기, 삭제하기 (0) | 2023.01.17 |
---|---|
React | web3-react를 사용하여 메타마스크(Metamask) 서명 요청하기 (0) | 2023.01.17 |
TypeScript 환경에서 React Query 사용하기 (0) | 2022.11.28 |
React | Swiper.js를 사용하여 카드 슬라이드 구현하기 (Slides per view) (0) | 2022.11.25 |
React | Swiper.js를 사용하여 이미지 슬라이드 구현하기 (0) | 2022.11.25 |