본문 바로가기

블록체인

[Ethereum/이더리움] 메타마스크 연결하기

 

이번에는 DApp를 만들기 위한 기초 작업을 진행하려고 한다. 구글 Chrome 확상 프로그램에서 메타마스크를 설치하고 우리가 직접 만든 웹사이트와 메타마스크를 연결하는 작업을 진행해보도록 하자.

 

 

여기서 잠깐,

 

DApp이 무엇인가?

 

DApp

DApp (DApp, Decentrailzed Application) 이란  탈중앙화 애플리케이션의 약자로 탈중앙화된 블록체인 플랫폼을 기반으로 작동하는 압을 말한다. 예를 들어 안드로이드 같은 스마트폰 운영체제가 이더리움과 같은 '플랫폼'이라면 안드로이드용 스마트폰 앱이 디앱이라고 할 수 있다.

 

기존의 '중앙화 어플리케이션'에서는 중앙 서버의 데이터베이스에 정보를 저장하고, 그 운용 역시 중앙 서버를 통해 이루어져 있다. 하지만 디앱에는 중앙 서버 없이 서버가 가졌던 신뢰와 권한을 블록체인 네트워크에 참여하는 수많은 컴퓨터가 나눠 갖고 있다.

 

디앱이란 개념은 스마트 컨트랙트가 도입되면서 본격적으로 사용되기 시작하여 현재는 DAO, DeFI, NFT 마켓플레이스, P2E(Play-to-Earn) 등 다양한 형태로 발전하고 있다.

 

예를 들어 가상자산 시장에서 가장 많이 사용되는 디앱은 탈중앙화 거래소이다. 특정 운영 주체가 거래를 매개하는 중앙화 된 거래소와는 달리 탈중앙화 거래소는 중간 매개자 없이 스마트 컨트랙트를 통해 유저와 유저가 직접 거래하면 된다.

 

 

중앙화와 탈중앙화

 

 

다시 돌아와서 우리가 만든 웹사이트와 메타마스크를 연결하는 작업을 해보자.

 

 

1. 메타마스크 네트워크 추가하기

 

Chrome 브라우저에서 확장 프로그램으로 메타마스크를 추가해주고 회원가입을 진행한다. 회원가입이 완료되고 로그인 작업을 하면 아래의 화면처럼 나오게 될 것이다.

 

 

메타마스크 로그인 후 화면

 

 

 

 

이제 우리가 이용하는 가나쉬를 통해서 만든 로컬 이더리움 네트워크를 메타마스크에 추가하는 작업을 할 것이다.

메타마스크 상단에 있는 이더리움 메인 넷을 클릭하고 밑으로 내려보면 네트워크 추가라는 버튼이 있을 것이다.

 

이더리움 메인넷 클릭

 

네트워크 추가 버튼 클릭

 

 

 

 

 

네트워크 추가 버튼을 누르면 아래와 같은 화면이 나온다. 여기서 내가 추가하고 싶은 네트워크 이름, 새 RPC URL, 체인 ID, 통화 기호를 채워주면 된다.

 

우리는 가나쉬를 통해 만들어진 로컬 이더리움 네트워크를 추가해야 하기 때문에 그에 맞는 정보를 넣어줬다.

 

블록체인 네트워크는 각자의 고유한 체인 ID를 가지고 있다. 우리가 생성한 가나쉬의 고유 체인 ID는 1337이다.

 

체인 ID는 따로 설정하지 않으면 1337이지만 우리가 임의로 ID 값을 바꿀 수 있다.

 

바꾸는 방법 : npx ganache-cli --chainId 1541

 

네트워크 추가 내용

 

 

 

위와 같이 작성하고 저장 버튼을 누르면 네트워크에 우리가 만든 네트워크가 추가가 된 것을 확인할 수 있다.

 

 

 

 

 

2. 웹사이트와 메타마스크 연결하기

 

 

메타마스크와 연결하기에 앞서서 먼저 간단한 방식으로 웹사이트를 만들어보도록 하자.

웹사이트를 만드는 과정으로 우리는 CRA ( create-react-app )이용할 것이다

 

 

터미널에 아래의 명령어를 쓰고 리액트를 까는 작업을 하도록 하자.

npx create-react-app front    // front [디렉토리명]

 

 

 

그러면 우리가 설정해준 디렉토리명인 front가 생기는 것을 확인할 수 있다. front 디렉토리 안에 src 디렉토리에 hooks 디렉토리를 만들고 그 안에 useWeb3.js 파일을 만들어두자.

import { useEffect, useState } from 'react'
import Web3 from 'web3/dist/web3.min.js'

const useWeb3 = () => {
    const [account, setAccount] = useState(null)
    const [web3, setWeb3] = useState(null)

    const getChainId = async () => {
        const chainId = await window.ethereum.request({
            method: 'eth_chainId',
        })
        console.log(chainId)
        return chainId
    }

    const getRequestAccounts = async () => {
        const account = await window.ethereum.request({
            method: 'eth_requestAccounts',
        })
        console.log(account)
        return account
    }

    const addNetwork = async (chainId) => {
        const network = {
            chainId: chainId,
            chainName: 'chanGanache',
            rpcUrls: ['http://127.0.0.1:8545'],
            nativeCurrency: {
                name: 'Ethereum',
                symbol: 'ETH',
                decimals: 18,
            },
        }

        await window.ethereum.request({
            method: 'wallet_addEthereumChain',
            params: [network],
        })
    }

    useEffect(() => {
        const init = async () => {
            try {
                const targetChainId = '0x1e2a'
                const chainId = await getChainId() // 7722
                // 현재 너의 메타마스크 체인 아이디가 7722니?
                console.log('너의 체인 아이디', chainId)
                // console.log(Web3.utils.toHex('7722'))
                if (targetChainId !== chainId) {
                    // network를 추가하는 코드 작성
                    addNetwork(targetChainId)
                }
                const [account] = await getRequestAccounts()

                const web3 = new Web3(window.ethereum) // 요청을 메타마스크에 바로 보내줌
                setAccount(account)
                setWeb3(web3)
            } catch (e) {
                console.error(e.message)
            }
        }

        if (window.ethereum) {
            init()
        }
    }, [])

    return [account, web3]
}

export default useWeb3

 

useWeb3.js 파일에는 프론트와 메타마스크를 연결해 메타마스크와 서로 통신이 될 수 있는 커스텀 훅을 만들어 주었다.

 

코드를 찬찬히 살펴보도록 하자. 

 

 

getChainId( )

const getChainId = async () => {
        const chainId = await window.ethereum.request({
            method: 'eth_chainId',
        })
        console.log(chainId)
        return chainId
    }
    
// console.log() === 0x539

getChainId 함수는 현재 메타마스크 네트워크에 연결되어 있는 체인 ID를 반환하게 해주는 함수이다. 반환하는 작업에서 window.ethereum.request라는 것을 볼 수 있는데, 브라우저에는 애초에 window 객체 안에 ethereum이 존재하지 않는다.

 

하지만 우리가 Chrome 확장 프로그램에서 메타마스크를 추가함으로써 window 객체 안에 ehtereum 속성이 새로 생긴 것이다. window.ehtereum.request를 이용하면 메타마스크에 요청을 보낼 수 있다. 그렇기에 우리는 메타마스크에게 이 메소드의 인자 값으로 method : 'eth_ChainId' 라는 속성 값을 객체로 전달해주었다.

 

 

 

getRequestAccounts( )

 const getRequestAccounts = async () => {
        const account = await window.ethereum.request({
            method: 'eth_requestAccounts',
        })
        console.log(account)
        return account
    }

 

getRequestAccounts 함수는 현재 메타마스크에 연결되어있는 네트워크에 존재하고 있는 계정들을 가져와주는 함수이다.

 

 

 

addNetwork ( )

const addNetwork = async (chainId) => {
        const network = {
            chainId: chainId,
            chainName: 'chanGanache',
            rpcUrls: ['http://127.0.0.1:8545'],
            nativeCurrency: {
                name: 'Ethereum',
                symbol: 'ETH',
                decimals: 18,
            },
        }

        await window.ethereum.request({
            method: 'wallet_addEthereumChain',
            params: [network],
        })
    }

 

addNetwork 함수는 우리가 위에서 메타마스크에 네트워크 추가하는 작업을 코드로 만들어주는 함수이다. 웹사이트에 접속했을 때 자동으로 메타마스크에 해당하는 네트워크를 추가시켜준다.

 

addNetwork 함수에는 네트워크를 추가할 때 필요한 속성값들을 넣어주었다. 그리고 window.ethereum.request를 통해서 인자 값으로 { method: 'wallet_addEthereumChain', params: [network] } 를 넣어주었다.

 

 

 

useEffect( )

    useEffect(() => {
        const init = async () => {
            try {
                const targetChainId = '0x1e2a'
                const chainId = await getChainId() // 7722
                // 현재 너의 메타마스크 체인 아이디가 7722니?
                console.log('너의 체인 아이디', chainId)
                // console.log(Web3.utils.toHex('7722'))
                if (targetChainId !== chainId) {
                    // network를 추가하는 코드 작성
                    addNetwork(targetChainId)
                }
                const [account] = await getRequestAccounts()

                const web3 = new Web3(window.ethereum) // 요청을 메타마스크에 바로 보내줌
                setAccount(account)
                setWeb3(web3)
            } catch (e) {
                console.error(e.message)
            }
        }

        if (window.ethereum) {
            init()
        }
    }, [])

    return [account, web3]
}

 

useEffect를 이용해서 모든 컴포넌트들이 렌더 되었을 때 우리가 정의해줬던 함수들이 실행될 수 있게 해 줬다. if( window.ethereum ) 조건문을 통해서 window 객체 안에 ethereum이 있을 경우에 init() 을 실행시킴으로써 init 함수 안에 있는 함수들이 실행될 수 있도록 했다.

 

 

 

App.js

import useWeb3 from './hooks/useWeb3'
import { useState, useEffect } from 'react'

function App() {
    const [account, web3] = useWeb3()
    const [isLogin, setIsLogin] = useState(false)
    const [balance, setBalance] = useState(0)

    const handleSubmit = async (e) => {
        e.preventDefault()

        await web3.eth.sendTransaction({
            from: account,
            to: e.target.recived.value,
            value: web3.utils.toWei(e.target.amount.value, 'ether'),
        })
    }

    useEffect(() => {
        const init = async () => {
            const balance = await web3?.eth.getBalance(account)
            setBalance(balance / 10 ** 18)
        }
        if (account) setIsLogin(true)
        init()
    }, [account])

    if (!isLogin) return <div>메타마스크 로그인 이후 사용해주세요.</div>
    return (
        <div>
            <div>
                <h2>{account}님 환영합니다.</h2>
                <div>Balance : {balance} ETH</div>
            </div>
            <div>
                <form onSubmit={handleSubmit}>
                    <input type="text" id="recived" placeholder="받을 계정" />
                    <input type="number" id="amount" placeholder="보낼 금액" />
                    <input type="submit" value="전송" />
                </form>
            </div>
        </div>
    )
}

export default App

 

우리가 작성했던 useWeb3를 import해와서 내용을 가져온 것을 web3.eth.sendTransaction 메소드를 통해서 트랜잭션을 하는 과정을 볼 수 있다.

 

App.js 의 코드를 해석하자면

 

1. 먼저 메타마스크에 로그인이 되어있는지 확인하는 작업을 가져서 로그인이 안되어있다면 "메타마스크 로그인 이후 사용해주세요' 라는 문구가 뜨도록 해놨다.

 

2. 메타마스크 로그인 이후 메타마스크와 연결되어있는 가나쉬의 로컬 이더리움 네트워크와 연결될 수 있도록 지정해놨다.

 

3. 계정 연결이 완료되면, 웹페이지에서 해당 계정의 잔액이 얼마나 남았는지 확인할 수 있다.

 

4. 웹 페이지에 있는 input 박스를 통해서 트랜잭션을 생성하기 위한 정보로써 "받을 계정" 과 "보낼 금액"을 적고 보낼 수 있다.

 

5. 전송 버튼을 누르면 메타마스크로 요청을 보내게 되고 메타마스크에서 서명을 만들어 트랜잭션을 발생시킨다.