React와 web3의 만남 - Web3-react로 NFT 민팅하기 (1) 🚀

Jeenie·2022년 8월 18일
3

세상을 바꿀 web 3.0

목록 보기
5/7
post-thumbnail

이 포스트는 web3-react 와 디센트 지갑 연동 기능 개발기,
web3-react, ethers.js 개념 정리,
web3-react, 지갑 연동을 참조하였습니다.

서론

아직 web3web3.js 포스트를 읽고오지 않았다면, 반드시 먼저 보고 오자.
길지 않으니 금방 끝날 것이다!

이전 web3.js 포스트에서 그랬듯이,
간단한 NFT 민팅 프로젝트를 진행하면서 개념을 잡고 적용해보자.

Web3-react란 무엇인가?

web3.js는?

자바스크립트 기반으로, 이더리움 DApp이나 서비스를 구현할 때 사용하는 라이브러리.

react.js는?

페이스북에서 만든 UI 라이브러리. 쉬운 상태관리, 컴포넌트 기반 개발, 가상돔 랜더링 등을 특징으로 가지고 있다.

그러니까 web3-react.js는?

React 앱에서 Context를 이용해 Web3의 DApp과 관련된 특정 주요 데이터최신상태로 유지해주는 state machine이다.
지갑 연동 및 스마트컨트랙트 ABI와 상호작용을 쉽고 간단하게 할 수 있도록 도와준다.

web3.jsweb3-react는 사용방법은 거의 동일하다. 겁먹지 말고 따라해보자!

이 때의 특정 주요 데이터란?
사용자의 현재 계정, chain Id, web3 Provider 등

1. Web3ReactProvider 설치

web3-react를 사용하려면 리액트 앱의 루트에 Web3ReactProvider 컴포넌트가 있어야한다.

1-1. Provider가 왜 필요할까?


출처 : web3.js 마이크로소프즈웨어 393호 발췌 (web3.js 구조도)

이 그림이 아주 잘 설명하고 있다!

이전 Web3.js 예제(1) 포스트에서도 설명했듯이,
web3Provider는 우리의 web3가 이더리움 네트워크의 어떤 노드와 소통해야하는지 지정하는 역할을 한다.

web3를 인스턴스화할 때 provider, 즉 공급자를 필요로 하기 때문.
메타마스크가 설치되어있으면 메타마스크를 web3 공급자로 사용한다.

  • blockChain network
    우리 DApp에서 읽기,변경하기를 하고싶어? 그럼 이 web3Provider로 저 노드와 소통해
  • client-server network
    우리의 App에서 읽기,변경하기를 하고싶어? 그럼 이 API URL로 저 API 와 소통해

1-2. 적용하기

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 앱 루트 파일 상단에 추가한다.
(왜 비슷해보이는 두개를 가져오는건지는 아래에 나온다)

2. web3 Instance화 하기

2-1. instance화를 왜 하는걸까?

인스턴스화란 ?
: 추상적인 개념인 클래스에서 실제 객체인 인스턴스를 생성하는 일

지금 web3 객체는 클래스와 같이 아직 실제 데이터에 접근할 수 있는 데이터가 없는 상태.
그 객체로부터 하나의 인스턴스, 그러니까 실제 객체를 만들어야 스마트컨트랙트와 소통할 수 있다.

📌 인스턴스에 대한 자세한 내용은 [JavaScript] - 객체와 인스턴스, 붕어빵 틀과 슈크림붕어빵를 참조하자.

2-2. 적용하기

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으로 건네줄게!"

3. 하위 컴포넌트에서 값 접근하기

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로 꺼내올 수 있는 값들을 살펴보자.

  • connector
    : 현재 dapp에 연결된 월렛의 connector 값
  • library
    : web3 provider 제공
  • chainId
    : dapp에 연결된 account의 chainId
  • account
    : dapp에 연결된 account address
  • active
    : dapp 유저가 로그인 된 상태인지 체크
  • activate
    : dapp 월렛 연결 기능 수행 함수
  • deactivate
    : dapp 월렛 연결 해제 수행 함수

참고로 지금 값들을 찍어보면 아래와 같이 나온다.

4. 지갑 연결하기

지갑을 연결하기 전에 필수사항을 짚고 넘어가자.

4-1. connector

어떤 상태를 Context에 저장해야하는지 지정하기 위해서 connector를 연결한다.
그럼 지갑을 사용하기 위해서는? wallet의 connector를 지정해야 함.

metamask의 경우 injectedConnector,
coinbase의 경우 walletlinkConnector,
portis의 경우 portisConnector를 설치한다.

4-2. activate

위에서 봤듯이 activate는 dapp 월렛 연결 기능 수행 함수이다.

wallet을 dapp에 연결하기 위해서는,
해당 wallet에 맞는 connectoractivate 함수에 전달해야 한다.

4-3. 예제로 흐름 이해하기

아래 web3-react의 예제 코드로 먼저 훑어보자! connectoractivate의 흐름이 쉽게 이해될 것이다.

https://codesandbox.io/s/mqlydu?file=/src/Modal.js의 코드 참조

Modal.js

// 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>;

connectors.js

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
};

4-4. 이제 진짜 따라하기

4-4-1. 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를 생성했다.

4-4-2. activate에 connector 넘겨주기

챕터 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;

그럼 이제 버튼을 누르면 메타마스크가 뜨고 지갑이 연결된다.

다음 포스트부터는 대망의 민팅하기를 만들어보자!

profile
Web Front-end developer

0개의 댓글