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