Blockchain

#18 metamask 활용

Sila 2022. 6. 29. 21:04

이번 글에서는 지금까지 배운 툴들을 이용해 내 지갑에서 블록체인과 상호작용하는 방식에 대해 알아보자.

 

web3, ganache-cli, metamask를 이용해 블록체인 네트워크에 요청을 보내 tx를 일으키는

 

기본적인 dApp을 만드는 것이 목표이다.

 

이번에는 프론트는 리액트로, 블록체인 네트워크는 전 글에서 만든 ganache-cli 제공 네트워크를 사용해볼 것이다.

 

 

우리가 #16까지 실제로 작동하는 블록체인을 만들었을 때 wallet 폴더에 server가 따로 있었고,

 

프론트에서 요청을 보내면 wallet/server.ts에서 이걸 받아 블록체인 네트워크로 요청을 전달했다.

 

wallet 폴더의 server.ts를 편하게 지갑서버라고 불렀는데, 메타마스크가 이 지갑 서버라고 보면 된다.

 

우리가 프론트에서 전송한 정보를 가지고 서명을 만들고 추가적인 작업을 직접 코드를 작성해 구현했는데,

 

메타마스크는 tx 발생시 이런 작업들을 자동으로 실행해 블록체인 네트워크로 요청을 전달해준다.

 

1. set up

 

이번에도 구동할 서버는 2개가 필요한데, 하나는 프론트서버를 구동할 서버,

 

나머지 하나는 ganache-cli가 사용할 서버이다.

 

npx create-react-app practice

 

리액트 앱을 설치해 잘 구동되는지 확인한 후 (npm run start), ganache-cli를 우분투에서 실행한다.

 

이번에는 chainId를 직접 정해줄 것인데, 다음과 같이 --chainId를 적고 원하는 숫자를 쓰면 된다.

 

ganache-cli --chainId 3476

 

ganache를 실행하면 저번과 마찬가지로 100ETH가 있는 지갑 10개를 만들어줄 것이다.

 

브라우저에서 콘솔을 열고 window.ethereum을 입력해보면 객체를 출력해줄 것이다.

 

이는 메타마스크를 설치할 때 같이 들어오는 데이터인데, 이 안에 있는 함수나 method들을 사용해

 

블록체인 네트워크와 상호작용 할 수 있게 된다.

 

2. 지갑 account 가져오기

/*  src/hooks/useWeb3.js  */

import { useEffect, useState } from 'react'

const useWeb3 = () => {
    const [ account, setAccount ] = useStare(null)
    
    // 지갑 account를 가져오는 함수
    const getRequestAccounts = async () => {
        const account = await window.ethereum.request({
            method : 'eth_requestAccounts'
        })
        // ethereum 객체를 이용해 메타마스크로부터 지갑 account를 가져온다
        return account        
    }
    
    useEffect(() => {
        const init = async () => {
            try {
                const [account] = await getRequestAccounts()
                setAccount(account)
                // account의 상태를 바꿔준다
            }
            catch (e) {
                console.error(e.message)
            }
        }
        if(window.ethereum) {
            init()
        }
    },[])
    
    return [account]
}

export default useWeb3

 

이를 app.js에서 가져와 account의 유무에 따라 다른 화면을 렌더링한다.

 

/*  src/App.js  */

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

function App() {
    const [account] = useWeb3()
    // useWeb3 컴포넌트에서 account 변수를 가져온다
    const [isLogin, setIsLogin] = useState(false)
    
    useEffect(() => {
        if(account) setIsLogin(true)
        // account가 있다면 ( = 로그인 되어있다면) isLogin 값을 바꾼다
    }, [account])
    
    if (!isLogin) return <div> 메타마스크 로그인 후 사용해주세요 </div>
    
    return (
        <div> {account} </div>
    )
}

export default App;

 

이제 메타마스크에서 계정 가져오기를 클릭해 ganache-cli에서 생성해준 지갑들 중 하나를 골라 개인키를 입력하면

 

그 지갑의 지갑 주소를 출력할 수 있을 것이다.

 

3. chainId 가져오기

비슷한 방식으로 현재 네트워크의 chainId값을 확인해보고 필요하다면 바꿔보자.

 

보통 메타마스크를 설치한 직후 디폴트로 사용하는 네트워크는 이더리움의 메인넷인데,

 

따로 바꿔주지 않았다면 아마 chainId 값으로 0x1 을 출력해줄 것이다. 앞에 0x를 제외한 1이 이더리움의 체인 아이디이다.

 

이제 메타마스크에 연결된 네트워크를 다른 네트워크 (우리가 구동하고 있는 ganache 네트워크)로 바꿔준다.

 

 

다음과 같이 네트워크 정보를 입력하고 네트워크를 저장하면 해당 네트워크를 사용할 수 있다.

 

다시 돌아와서 useWeb3 컴포넌트에 다음과 같이 chainId를 출력해주는 함수를 추가한 후, init() 함수에서 호출한다.

 

/*  src/hooks/useWeb3.js  */

const useWeb3 = () => {
    // ...중략
    const [ chainId, setChainId ] = useState(null)
    
    // ...중략

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

    useEffect (() => {
        const init = async() => {
            const chainId = await getChainId() //추가
            // ...중략
            
            setChainId(chainId)
        }
    },[])
    //...중략
    
    return [ account, web3, chainId ]
}

 

/*  src/App.js  */

// ...중략

function App () {
    const [ account, chainId ] = useWeb3()
    
    // ...중력
    
    return (
        <div>
            // ...중략
            <div> chainId : { chainId } </div>
            // ...중략
        </div>
    )
}

 

내가 ganache를 실행할때 입력한 chainId는 10진수, 출력되는 chainId는 16진수임에 주의하면서 동일한지 확인한다.

 

4. balance 가져오기

잔고를 가져오는건 web3 라이브러리를 활용해보자.

 

npm i web3

 

useWeb3.js 에서 web3 라이브러리를 가져오고 이를 App.js에 넘겨줘 App.js에서 이를 사용할 것이다.

 

그런데 useWeb3.js에서 web3를 import해오면 에러가 난다.

 

이는 webpack이 전체 web3 라이브러리를 번들해줄 수 없기 때문인데,

 

여기서는 webpack으로 번들할 수 있는 web3의 일부 기능만을 가져와 사용하면 된다.

 

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

const useWeb3 = () => {
    // ...중략
    const [ web3, setWeb3 ] = useState(null)
    
    // ...중략
    
    useEffect (() => {
        const init = async() => {
            try {
                const web3 = new Web3(window.ethereum)
                setWeb3(web3)
                
                const [account] = await getRequestAccounts()
                setAccount(account)
          
                const chainId = await getChainId()
                setChainId(chainId)
            }
            catch (e) {
                console.error(e.message)
            }
        }
        
        if(window.ethereum) {
            init()
        }
        
    },[])
    
    return [account, web3, chainId]
}

 

/*  src/App.js  */

function App() {
    const [ account, web3, chainId ] = useWeb3()
    // ...중략
    const [ balance, setBalance ] = useState(0)
    
    useEffect(() => {
        const init = async() => {
            const balance = await web3?.eth.getBalance(account)
            // web3의 지정한 지갑의 잔고를 가져오는 기능 사용
            setBalance(balance / 10 ** 18 )
            // 단위를 맞춰 주기 위해 10^18로 나눈 값으로 Balance state를 변경
        }
        
        if(account) setIsLogin(true)
        init()
    }, [account])
    // ...중략
    
     return (
         <div>
             <h2> {account} </h2>
             <div> chainId : { chainId } </div>
             <div> Balance : { balance } ETH </div>
         </div>
  );
}

 

5. tx

ganache 실행시 만들어진 지갑들간에 코인을 주고 받아보자.

 

우선 프론트서버에 송금을 할 수 있도록 폼을 만든다.

 

/* src/App.js  */

// ...중략

  return (
    <div>

      <h2> {account} </h2>

      <div> Balance : { balance } ETH </div>
      <div> chainId : { chainId } </div>

      <div>
        <form onSubmit={handleSubmit}>
          <input type='text' id='receiver' placeholder='받을 주소'/>
          <input type='number' id='amount' placeholder='보낼 금액'/>
          <input type='submit' value='전송'/>
        </form>
      </div>

    </div>
  );

 

handleSubmit 함수도 App 컴포넌트 안에 다음과 같이 추가하면 된다.

 

/*  src/App.js  */

const handleSubmit = async (e) => {
    e.preventDefault()
    
    await web3.eth.sendTransaction({
        from : account,
        to : e.target.receiver.value,
        value : web3.utils.toWei(e.target.amount.value, 'ether')
    })
}

 

이제 input 박스에 다른 지갑의 주소와 보낼 코인의 양을 적어서 전송해보자.

 

6. 네트워크 변경

마지막으로 사용자가 웹 페이지에 접속했을 때 사용자 브라우저의 메타마스크의 현재 접속중인 네트워크가

 

웹 페이지가 지정한 네트워크와 다르다면 메타마스크가 알아서 네트워크의 chainId와 맞춰주는 기능을 사용해보자.

 

/*  src/hooks/useWeb3.js  */

// ...중략

const useWeb3 = () => {
    // ...중략
    
    useEffect (() => {
        const init = async () => {
            try {
                // ...중략
                const targetChainId = '0xd94' // 처음 ganache 실행시 정한 chainId
                const chainId = await getChainId()
                
                if( targetChainId !== chainId ) {
                    addNetWork(targetChainId)
                }
                setChainId(chainId)
            }
            // ...중략
        }
    }
}

 

웹 사이트 (dApp)에서 지정한 targetChainid와 사용자의 메타마스크가 사용중인 chainId가 다르다면

 

네트워크 추가/변경 함수 addNetWork()를 실행한다.

 

/*  src/hooks/useWeb3.js  */

const useWeb3 = () => {
    // ...중략
    
    const addNetWork = async (chainId) => {
        const network = {
            chainId : chainId,
            chainName : 'practice', // 아까 추가한 네트워크 정보를 적으면 된다.
            rpcUrls : ['http://127.0.0.1:8545'],
            nativeCurrency : {
                name : Ethereum,
                symbol : 'ETH',
                decimals : 18 // 단위 설정
            }
        }
        
        await window.ethereum.request({
            method : 'wallet_addEthereumChain',
            params : [network]
        })
    }
    
    // ...생략
}

 

잔부 작성한 후, 메타마스크에서 네트워크를 이더리움 메인넷으로 바꾸고 react app에 접속해보면

 

메타마스크에서 네트워크 전환에 대한 알림을 줄 것이다.

'Blockchain' 카테고리의 다른 글

#20 개인 네트워크 만들기 (geth)  (0) 2022.07.03
#19 geth  (0) 2022.06.30
#17 ethereum  (0) 2022.06.29
#16 transaction ch.6 - tx pool  (0) 2022.06.27
#15 transaction ch.5 - utxo update  (0) 2022.06.27