개요 ☁️

블록체인을 공부하며 얻은 정보로는,
웹 브라우저 (리액트 등) 에서는 Web3 에 대한 자료가 비교적 풍부하다.
하지만,
모바일 환경 (RN, Native 등) 에서는 상대적으로 적은 편 이라는 것 이다.
해당 포스팅 에서는 내가 생각하는 Dapp 의 기초인 Wallet Connect 를 구현하는 과정을 적어보려한다.


init 🔥

해당 과정을 따라가기에 앞서 React-Native expo 앱을 생성 해 준다.

npx create-expo-app -t expo-template-blank-typescript

생성한 앱의 경로로 이동하여 ls 명령어로 파일구조를 확인 해 보면,
./node_modules 가 존재하지 않기 때문에
npm install 명령으로 필요 패키지들을 설치 해 준다.

Expo 앱을 사용하여 구현하는 이유는,
개발환경은 Window 이지만 디바이스는 ios 를 사용하고 있기 때문이다.
물론, 애뮬레이터를 사용하여 Android 로 빌드 할 수도 있겠지만
개발환경의 성능 이슈로 인해 Expo 앱으로 채택하여 진행한다.

설치가 모두 끝났다면,
npm start 명령으로 생성한 Expo 앱을 실행 해 준다.

디바이스의 카메라로 해당 QR 코드를 인식하여 Expo 앱을 개발환경과 연동 해 준다.


Wellet Connect 🌷

https://walletconnect.com/

해당 사이트에 접속하여 대시보드를 누르면 메타마스크 연결을 통해 로그인 할 수 있다.

대시보드에 접근하여 새로운 프로젝트를 생성 해 준다.

생성한 프로젝트를 보면,
Project ID 가 있는데 이 키는 React-Native 앱과 지갑 연결을 구현할 때 필요하므로 저장 해 놓는다.

https://docs.metamask.io/wallet/how-to/connect/set-up-sdk/javascript/react-native/

실제 메타마스크 공식문서를 살펴보면,
React-Native 앱에서 메타마스크 SDK 를 사용할 수 있는 패키지가 출시 전 이라 위와 같은 과정으로 진행한다.


install 🔮

이벤트를 실행시킬 때 Wallet Connect 모달 창을 표시하기 위해 해당 라이브러리를 설치 해 준다.

npx expo install @walletconnect/modal-react-native

해당 라이브러리를 통해 화면에 보여지는 모달 창은,
구현된 코드에서 사용자에게 지갑 연결 및 연결 해제를 위한 UI 를 그려준다.
옵션으로
explorerRecommendedWalletIds,
explorerExcludedWalletIds,
projectId,
providerMetadata 를 받는다.

  • explorerRecommendedWalletIds : 추천하는 지갑 식별자 배열로 사용자에게 모달을 통해 표현된다.
  • explorerExcludedWalletIds : 모달에서 표시되지 않는 지갑의 식별자를 지정한다.
    ALL 로 지정 시 모든 지갑이 표시되지 않는다.
  • projectId : Wallet Connect 프로젝트 식별자
  • providerMetadata : 지갑에 대한 정보를 포함하는 객체로 이름, 설명, URL 및 아이콘 정보를 담을 수 있다.

다음은 React-Native 환경에서 Wallet Connect 를 구현하기 위해 필요한 부가적인 라이브러리들이다.

npx expo install
@react-native-async-storage/async-storage
react-native-get-random-values
react-native-modal
react-native-svg

각 라이브러리들은,

  • 비동기 스토리지와 데이터를 처리하기 위한 라이브러리로, 앱 데이터를 로컬 스토리지에 저장할 때 사용한다.
  • 보안을 위하여 암호화된 난수를 생성하는 라이브러리이다.
  • 모달 창을 통해 팝업, 경고창 등을 화면에 출력하기 위한 라이브러리다.
  • SVG 이미지를 React-Native 앱에서 렌더링 할 때 사용되는 라이브러리이다.

SVG 형식의 이미지는 화면 크기에 맞게 확대 및 축소에 용이하여 다양한 해상도와 크기에 적합하다.


코드 구현 💻

https://walletconnect.com/

앞서 복사 해 둔 Project ID 를 변수에 담아 불러온다.

const projectId = "[Project ID]";

WalletConnect 라이브러리를 초기화할 때 사용되는 데이터를 포함하는 객체로,
아래 정의된 객체는 템플릿 데이터이다.

const providerMetadata = {
  name: "YOUR_PROJECT_NAME",
  description: "YOUR_PROJECT_DESCRIPTION",
  url: "https://your-project-website.com/",
  icons: ["https://your-project-logo.com/"],
  redirect: {
    native: "YOUR_APP_SCHEME://",
    universal: "YOUR_APP_UNIVERSAL_LINK.com",
  },
};

useWalletConnectModal 훅에서 현재 Wallet Connect 와 관련된 정보를 가져온다.

const { open, isConnected, address, provider } = useWalletConnectModal();

onClick 이벤트를 실행할 함수를 생성한다.
이 함수는 비동기적으로 버튼이 눌렸을 경우 실행되며,
Wallet 에 연결하려고 할 때 호출된다.
이미 연결되어 있는 경우 provider?.disconnect() 를 호출하여 현재 연결을 끊는다.

그렇지 않은 경우, (false)
open() 함수를 호출하여 Wallet Connect 모달을 실행한다.

const handleButtonPress = async () => {
  if (isConnected) {
    return provider?.disconnect();
  }
  return open();
};

화면에 보여지는 부분을 간단하게 나타내보도록 하자.

{isConnected ? address : "No Wallet Connected"}
지갑 연결 상태에 따른 텍스트를 출력하는 부분으로,
isConnected 가 true 일 경우 연결된 지갑의 주소를 출력하고,
그렇지 않을 경우 No Wallet Connected 를 출력한다.

return (
  <View style={styles.container}>
    <Text style={styles.heading}>WalletConnect</Text>
    <Text>{isConnected ? address : "No Wallet Connected"}</Text>
    <Pressable onPress={handleButtonPress} style={styles.pressableMargin}>
      <Text>{isConnected ? "Disconnect" : "Connect"}</Text>
    </Pressable>
    <WalletConnectModal
      explorerRecommendedWalletIds={[
        "c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96",
      ]}
      explorerExcludedWalletIds={"ALL"}
      projectId={projectId}
      providerMetadata={providerMetadata}
    />
  </View>
);

전체 코드 🔐

import { StyleSheet, Text, View, Pressable } from "react-native";
import {
  WalletConnectModal,
  useWalletConnectModal,
} from "@walletconnect/modal-react-native";

const projectId = "[Project ID]";

const providerMetadata = {
  name: "",
  description: "",
  url: "",
  icons: [""],
  redirect: {
    native: "",
    universal: "",
  },
};

export default function App() {
  const { open, isConnected, address, provider } = useWalletConnectModal();

  const handleButtonPress = async () => {
    if (isConnected) {
      return provider?.disconnect();
    }
    return open();
  };

  return (
    <View style={styles.container}>
      <Text style={styles.heading}>WalletConnect</Text>
      <Text>{isConnected ? address : "No Wallet Connected"}</Text>
      <Pressable onPress={handleButtonPress} style={styles.pressableMargin}>
        <Text>{isConnected ? "Disconnect" : "Connect"}</Text>
      </Pressable>
      <WalletConnectModal
        explorerRecommendedWalletIds={[
          "c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96",
        ]}
        explorerExcludedWalletIds={"ALL"}
        projectId={projectId}
        providerMetadata={providerMetadata}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center",
  },
  heading: {
    fontSize: 18,
    fontWeight: "bold",
    marginBottom: 16,
  },
  pressableMargin: {
    marginTop: 16,
  },
});


Error ❌

만약,
모든 코드를 작성하고 앱을 실행시킨 경우 아래 에러가 발생한다면,
추가적으로 해당 라이브러리를 설치 해 주면 에러가 없이 정상적으로 실행이 될 것이다.


Reference 🌊

https://cloud.walletconnect.com/
https://docs.metamask.io/wallet/how-to/connect/set-up-sdk/javascript/react-native/