
이 포스트는 web3-react 와 디센트 지갑 연동 기능 개발기,
web3-react, ethers.js 개념 정리,
web3-react, 지갑 연동을 참조하였습니다.
아직 web3와 web3.js 포스트를 읽고오지 않았다면, 반드시 먼저 보고 오자.
길지 않으니 금방 끝날 것이다!
이전 web3.js 포스트에서 그랬듯이,
간단한 NFT 민팅 프로젝트를 진행하면서 개념을 잡고 적용해보자.
자바스크립트 기반으로, 이더리움 DApp이나 서비스를 구현할 때 사용하는 라이브러리.
페이스북에서 만든 UI 라이브러리. 쉬운 상태관리, 컴포넌트 기반 개발, 가상돔 랜더링 등을 특징으로 가지고 있다.
React 앱에서 Context를 이용해 Web3의 DApp과 관련된 특정 주요 데이터 을 최신상태로 유지해주는 state machine이다.
지갑 연동 및 스마트컨트랙트 ABI와 상호작용을 쉽고 간단하게 할 수 있도록 도와준다.
web3.js와 web3-react는 사용방법은 거의 동일하다. 겁먹지 말고 따라해보자!
이 때의 특정 주요 데이터란?
사용자의 현재 계정, chain Id, web3 Provider 등
web3-react를 사용하려면 리액트 앱의 루트에 Web3ReactProvider 컴포넌트가 있어야한다.

출처 : web3.js 마이크로소프즈웨어 393호 발췌 (web3.js 구조도)
이 그림이 아주 잘 설명하고 있다!
이전 Web3.js 예제(1) 포스트에서도 설명했듯이,
web3Provider는 우리의 web3가 이더리움 네트워크의 어떤 노드와 소통해야하는지 지정하는 역할을 한다.
web3를 인스턴스화할 때 provider, 즉 공급자를 필요로 하기 때문.
메타마스크가 설치되어있으면 메타마스크를 web3 공급자로 사용한다.
- blockChain network
우리DApp에서 읽기,변경하기를 하고싶어? 그럼 이web3Provider로 저 노드와 소통해- client-server network
우리의App에서 읽기,변경하기를 하고싶어? 그럼 이API URL로 저 API 와 소통해
yarn add @web3-react/core
yarn add @ethersproject/providers
web3-react를 사용하기 위해서는 먼저 @web3-react/core와 @ethersproject/providers를 설치한다.
import { Web3ReactProvider } from "@web3-react/core";
import { Web3Provider } from "@ethersproject/providers";
React 앱 루트 파일 상단에 추가한다.
(왜 비슷해보이는 두개를 가져오는건지는 아래에 나온다)
인스턴스화란 ?
: 추상적인 개념인 클래스에서 실제 객체인 인스턴스를 생성하는 일
지금 web3 객체는 클래스와 같이 아직 실제 데이터에 접근할 수 있는 데이터가 없는 상태.
그 객체로부터 하나의 인스턴스, 그러니까 실제 객체를 만들어야 스마트컨트랙트와 소통할 수 있다.
📌 인스턴스에 대한 자세한 내용은 [JavaScript] - 객체와 인스턴스, 붕어빵 틀과 슈크림붕어빵를 참조하자.
import를 완료했다면, 아래와 같이 Web3ReactProvider로 App을 감싸주자.
import { Web3ReactProvider } from "@web3-react/core";
import { Web3Provider } from "@ethersproject/providers";
ReactDOM.render(
<React.StrictMode>
<Web3ReactProvider>
<RecoilRoot>
<ThemeProvider theme={{}}>
<BrowserRouter>
<App />
</BrowserRouter>
</ThemeProvider>
</RecoilRoot>
</Web3ReactProvider>
</React.StrictMode>,
document.getElementById("root")
);
reportWebVitals();
(나는 미리 이렇게 구조가 잡혀있던 프로젝트에 추가하는 것이기 때문에, 구조는 동일하지 않아도 된다)
이렇게만 하면 오류가 뜰 것이다.
web3react에 getLibrary 함수를 props 로 전달해줘야 하는데,
이 함수는 web3-react가 사용할 web3 provider 를 제공하는 역할을 한다.
import { Web3ReactProvider } from "@web3-react/core";
import { Web3Provider } from "@ethersproject/providers";
const getLibrary = (provider: any) => {
console.log("[getLibrary] provider", provider);
return new Web3Provider(provider);
};
ReactDOM.render(
<React.StrictMode>
<Web3ReactProvider getLibrary={getLibrary}>
<RecoilRoot>
<ThemeProvider theme={{}}>
<BrowserRouter>
<App />
</BrowserRouter>
</ThemeProvider>
</RecoilRoot>
</Web3ReactProvider>
</React.StrictMode>,
document.getElementById("root")
);
reportWebVitals();
이렇게 getLibrary는
Web3Provider라는 객체로부터, 실질적인 객체인 인스턴스를 만들어서 Web3ReactProvider에게 제공한다.
그래서 Web3Provider는 @ethersproject/providers에서 가져오는 것이고,
Web3ReactProvider는 @web3-react/core에서 가져오는 것이다. ✨
우리가 React프로젝트에서 쓰고 싶어서 Web3ReactProvider로 사용하기로 했으니,
Web3ReactProvider에게 하는 말 ::
"자! ethersprojects를 만들 때 쓰는 라이브러리 안에,
providers 폴더에서 가져온 Web3Provider 객체를,
너가 쓸 수 있도록 인스턴스화 해서 getLibrary prop으로 건네줄게!"
Provider로 감쌌기 때문에 이제 하위 컴포넌트에서 값들을 받아올 수 있다.
우리가 root에서 설정한 Provider가 Web3ReactProvider가 되었기 때문에,
Web3ReactProvider에서는 아래 subtree에서 context 값에 접근 할 수 있도록 useWeb3React 라는 hook을 제공한다.
이러니까 쓰는 거다. Context를 쏙 꺼내올 수 있어 세상 편하기 때문에.
import { useWeb3React } from "@web3-react/core";
const Page = () => {
const theme = useTheme();
const {
connector,
library,
chainId,
account,
active,
error,
activate,
deactivate,
} = useWeb3React();
return (
<Container>
<StyledSampleDiv theme={theme}>
<Typography variant="h4" color={theme.colors.gray[1000]}>
메인 홈 화면
</Typography>
</StyledSampleDiv>
</Container>
);
};
export default Page;
useWeb3React로 꺼내올 수 있는 값들을 살펴보자.
참고로 지금 값들을 찍어보면 아래와 같이 나온다.

지갑을 연결하기 전에 필수사항을 짚고 넘어가자.
어떤 상태를 Context에 저장해야하는지 지정하기 위해서 connector를 연결한다.
그럼 지갑을 사용하기 위해서는? wallet의 connector를 지정해야 함.
metamask의 경우 injectedConnector,
coinbase의 경우 walletlinkConnector,
portis의 경우 portisConnector를 설치한다.
위에서 봤듯이 activate는 dapp 월렛 연결 기능 수행 함수이다.
wallet을 dapp에 연결하기 위해서는,
해당 wallet에 맞는 connector를 activate 함수에 전달해야 한다.
아래 web3-react의 예제 코드로 먼저 훑어보자! connector와 activate의 흐름이 쉽게 이해될 것이다.
// Modal.js
<Button
onClick={() => {
activate(connectors.coinbaseWallet);
// coinbaseWallet를 선택하면 activate 함수에 해당 connector를 전달
setProvider("coinbaseWallet");
closeModal();
}}
>
<Text>Coinbase Wallet</Text>
</Button>
<Button
onClick={() => {
activate(connectors.walletConnect);
// walletConnect를 선택하면 activate 함수에 해당 connector를 전달
setProvider("walletConnect");
closeModal();
}}
>
<Text>Wallet Connect</Text>
</Button>
<Button
onClick={() => {
activate(connectors.injected);
// 메타마스크를 선택하면 activate 함수에 해당 connector를 전달
setProvider("injected");
closeModal();
}}
>
<Text>Metamask</Text>
</Button>
useWeb3React에서 받아온 activate 함수에 connector를 전달한다.
정상적으로 실행될 경우, useWeb3React에서 제공하는 context 값들이 갱신된다.
// 참고로 Web3ReactManagerFunctions에 정의된 activate는 이렇게 생겼다
activate: (connector: AbstractConnector, onError?: (error: Error) => void, throwErrors?: boolean) => Promise<void>;
import { InjectedConnector } from "@web3-react/injected-connector";
import { WalletConnectConnector } from "@web3-react/walletconnect-connector";
import { WalletLinkConnector } from "@web3-react/walletlink-connector";
const injected = new InjectedConnector({
supportedChainIds: [1, 3, 4, 5, 42]
});
const walletconnect = new WalletConnectConnector({
rpcUrl: `https://mainnet.infura.io/v3/${process.env.INFURA_KEY}`,
bridge: "https://bridge.walletconnect.org",
qrcode: true
});
const walletlink = new WalletLinkConnector({
url: `https://mainnet.infura.io/v3/${process.env.INFURA_KEY}`,
appName: "web3-react-demo"
});
export const connectors = {
injected: injected, // metamask용 connector
walletConnect: walletconnect, // walletconnect용 connector
coinbaseWallet: walletlink // coinbase용 connector
};
우선 제일 흔한 메타마스크만 해보자.
메타마스크 지갑을 사용하려면 우선 메타마스크 지갑의 connector인 injected-connector를 설치한다.
yarn add @web3-react/injected-connector
설치했다면 connector 인스턴스를 만들러가자 🏃♀️💨💨
( 나는 그냥 App.tsx에서 만들고 export 시켰다 )
//App.tsx
export const Injected = new InjectedConnector({});
위에서 설명했듯이, wallet에 맞는 connector를 active 함수에 전달해야한다.
InjectedConnector로 connect 인스턴스인 Injected를 생성했다.
챕터 3에서 나온 설명과 같이,
위에서 설치한 Web3ReactProvider는 useWeb3React 라는 hook을 제공한다!
이 useWeb3React는, 하위 컴포넌트에서 context 값에 접근할 수 있게 해준다.
근데 왜 접근할 수 있는 거야?
우리는 현재 루트를 Web3ReactProvider로 감쌌다.
따라서 하위 컴포넌트에서 Context 상태값을 사용 가능한데, 이 Context로는 우리가 Provider로 지정한 값들이 들어있을 것이다.
이 값들은useWeb3React라는 훅으로 뽑아올 수 있는 거고,
그 안에는 챕터 3에서 나열했던 connector, library, chainId, account, active, activate, deactivate 등등이 들어있는 것!
// home.tsx
import { useWeb3React } from "@web3-react/core";
import { Injected } from "App";
const Page = () => {
const theme = useTheme();
const { activate, deactivate, account, chainId } = useWeb3React();
const handleConnect = () => {
if ((window as any).ethereum === undefined) {
// 지갑이 설치 안되어있으면 설치 페이지를 오픈한다. 일단 메타마스크만.
window.open(
`https://metamask.app.link/dapp/${window.location.host}`,
"_blank"
);
return;
}
if (active && account) {
deactivate();
// 이미 연결되어있는 상태면 연결해제 함수 호출
}
activate(Injected);
// activate 함수로, App에서 만든 Injected란 이름의 connector 인스턴스를 넘겨준다
}
return (
<Container>
<StyledSampleDiv theme={theme}>
<Typography variant="h4" color={theme.colors.gray[1000]}>
Account: {account}
</Typography>
<Typography variant="h4" color={theme.colors.gray[1000]}>
chainId: {chainId}
</Typography>
<Button onClick={handleConnect}>
{active ? "연결 해제" : "지갑 연결하기"}
</Button>
</StyledSampleDiv>
</Container>
);
};
export default Page;

그럼 이제 버튼을 누르면 메타마스크가 뜨고 지갑이 연결된다.
다음 포스트부터는 대망의 민팅하기를 만들어보자!