[Ethereum] 프론트엔드 연결하기

0xDave·2022년 8월 21일
0

Ethereum

목록 보기
3/112

이전에 npx hardhat node 명령어로 블록을 생성했던 것은 로컬 네트워크에서 이더리움 노드를 돌리는 방법이었다. 이제 실제 이더리움 테스트넷에서 컨트랙트를 배포하고 프론트엔드 연결까지 해보자.

QuickNode


https://www.quicknode.com/ 사이트에 들어가서 계정을 만들고(광고 아님) Rinkeby 테스트넷으로 endpoint를 하나 만든다.

faucet 받기


https://rinkebyfaucet.com/ 에서 faucet 받을 메타마스크 주소를 입력하고 Send Me ETH 버튼을 누른다. 시간이 조금 지나면 테스트넷 이더가 들어와 있을 것이다.


Rinkbey 테스트넷에 디플로이 하기


hardhat.config.js 파일을 다음과 같이 수정한다. QuickNode api와 메타마스크 프라이빗키를 입력한다.(반드시 버너지갑으로 할 것!!) 이 파일은 프라이빗 키가 들어있으니 깃헙에 커밋하지 않도록 조심하자.

require("@nomiclabs/hardhat-waffle");

module.exports = {
  solidity: "0.8.4",
  networks: {
    rinkeby: {
      url: "YOUR_QUICKNODE_API_URL",
      accounts: ["YOUR_PRIVATE_RINKEBY_ACCOUNT_KEY"]
    },
  },
};

아래 명령어를 입력해서 rinkeby 테스트넷에 디플로이 한다.

npx hardhat run scripts/deploy.js --network rinkeby

가장 마지막에 출력되는 WavePortal 주소(디플로이된 컨트랙 주소)는 나중에 프론트엔드와 연결할 때 필요하니 따로 보관하도록 하자.


window.ethereum()


만들어 놓은 프론트의 App.jsx 파일에(여기에선 replit 샘플을 사용)
window.ethereum()을 이용해 지갑이 연결됐는지 확인해야 한다.

import React, { useEffect } from "react";
import "./App.css";

const App = () => {
  
  //나중에 채울 부분
  const wave = () => {
    
  }
  
  //지갑 연결 확인
  const checkIfWalletIsConnected = () => {
  
    const { ethereum } = window;

    if (!ethereum) {
      console.log("Make sure you have metamask!");
    } else {
      console.log("We have the ethereum object", ethereum);
    }
  }

  //페이지 로드 되면 지갑 연결 확인 함수 실행
  useEffect(() => {
    checkIfWalletIsConnected();
  }, [])

  return (
    <div className="mainContainer">
      <div className="dataContainer">
        <div className="header">
        👋 Hey there!
        </div>

        <div className="bio">
          I am farza and I worked on self-driving cars so that's pretty cool right? Connect your Ethereum wallet and wave at me!
        </div>

        <button className="waveButton" onClick={wave}>
          Wave at Me
        </button>
      </div>
    </div>
  );
}

export default App

저장하고 새로고침하면 콘솔에 이더리움 오브젝트가 출력된다!!


사용자 지갑 권한 확인


다음으로, 우리의 프론트엔드가 웹사이트에 접속한 유저의 메타마스크 지갑에 접근할 수 있는 권한이 있는지 확인해야 한다. 이 때 eth_accounts 라는 메소드를 이용한다. 이 때 사용자가 지갑을 연결하면 여러 계정이 있을 수 있는데 여기서는 첫 번째 계정만 가져온다.

import React, {useEffect, useState} from "react";
import './App.css';

export default function App() {

  const wave = () => {
    
  }
	
  //접근 권한 있는 지갑
  const [currentAccount, setCurrentAccount] = useState("");

  const checkIfWalletIsConnected = async () => {
    try {
      const { ethereum } = window;

      if (!ethereum) {
        console.log("Make sure you have metamask!");
        return;
      } else {
        console.log("We have the ethereum object", ethereum);
      }

      
      const accounts = await ethereum.request({ method: "eth_accounts" });

      if (accounts.length !== 0) {
        const account = accounts[0];
        console.log("Found an authorized account:", account);
        setCurrentAccount(account)
      } else {
        console.log("No authorized account found")
      }
      
    } catch (error) {
      console.log(error);
    }
  }
  
  return (
    <div className="mainContainer">
	//...
    </div>
  );
}

지갑 연결 버튼 추가


eth_requestAccounts 메소드를 이용해서 메타마스크가 웹사이트에 연결되도록 한다.

//지갑 연결 함수
const connectWallet = async () => {
    try {
      const { ethereum } = window;

      if (!ethereum) {
        alert("Get MetaMask!");
        return;
      }

      const accounts = await ethereum.request({ method: "eth_requestAccounts" });

      console.log("Connected", accounts[0]);
      setCurrentAccount(accounts[0]);
    } catch (error) {
      console.log(error)
    }
  }

return (
    <div className="mainContainer">
      <div className="dataContainer">
        <div className="header">
        👋 Hey there!
        </div>

        <div className="bio">
          I am farza and I worked on self-driving cars so that's pretty cool right? Connect your Ethereum wallet and wave at me!
        </div>

        <button className="waveButton" onClick={wave}>
          Wave at Me
        </button>

        {/*
        * 연결된 지갑이 없을 때만 지갑 연결 버튼 보이기
        */}
        {!currentAccount && (
          <button className="waveButton" onClick={connectWallet}>
            Connect Wallet
          </button>
        )}
      </div>
    </div>
  );

현재까지 진행 상황

버튼을 누르면 지갑을 연결할 수 있고 Connect Wallet 버튼이 사라진다!


컨트랙트 호출하기


connectWallet 함수 아래에 다음 코드를 작성한다.

import { ethers } from "ethers";

//connectWallet() 부분...

const wave = async () => {
    try {
      const { ethereum } = window;

      if (ethereum) {
        
        const provider = new ethers.providers.Web3Provider(ethereum);
        const signer = provider.getSigner();
        const wavePortalContract = new ethers.Contract(contractAddress, contractABI, signer);

        let count = await wavePortalContract.getTotalWaves();
        console.log("Retrieved total wave count...", count.toNumber());
      } else {
        console.log("Ethereum object doesn't exist!");
      }
    } catch (error) {
      console.log(error);
    }
}
  1. 우리가 임포트한 ethers는 컨트랙트와 상호작용할 수 있도록 도와주는 라이브러리다.
  2. Provider는 이더리움 노드와 상호작용할 수 있게 한다. 우리가 컨트랙트를 이더리움 노드를 이용해서 디플로이 했기 때문에 노드와의 상호작용이 필요하다.

기타 api에 대한 설명은 ethers 공식문서를 참고하자. 주요 정보들은 아래 사진으로 가져왔다.


const wavePortalContract = new ethers.Contract(contractAddress, contractABI, signer);

Contract 메소드 인자에 우리가 디플로이했던 컨트랙트 주소가 필요하다.


const contractAddress = "컨트랙주소";

App 컴포넌트에 이전에 저장해놨던 컨트랙트 주소를 변수에 담아주자.


✨ ABI

Application Binary Interface의 약자로 컨트랙트의 함수와 매개변수들을 JSON 형식으로 나타낸 리스트이다. ABI는 스마트 컨트랙트의 바이트코드를 호출하고 실행시킬 수 있는 정보와 인터페이스를 제공한다. 리믹스 또는 IDE에서 솔리디티 파일을 컴파일 하면 EVM 바이트 코드와 함께 ABI가 만들어진다. 바이트 코드가 트랜잭션에 담겨서 이더리움 블록 안에 추가가 되면 비로소 우리의 스마트 컨트랙트가 디플로이 됐다고 볼 수 있다. 그러면 우리는 디플로이 된 컨트랙트 주소와 ABI를 가지고 컨트랙트 안의 함수를 호출할 수 있게 된다.

컴파일 하고 나면 artifacts/contracts/WavePortal.sol 폴더 안에 WavePortal.json 파일이 생기는데 이게 바로 ABI 파일이다. replit(프론트엔드 부분)에 가서 src 폴더 안에 utils 폴더를 하나 만들고 WavePortal.json 파일을 만들어서 원래 WavePortal.json 파일 내용을 붙여넣는다.

그리고 App.jsx 파일에 임포트 해주고 변수를 만들어주면 끝

import React, { useEffect, useState } from "react";
import { ethers } from "ethers";
import "./App.css";
import abi from "./utils/WavePortal.json";

const App = () => {
  const [currentAccount, setCurrentAccount] = useState("");

  const contractABI = abi.abi;

wave 함수 호출

버튼을 눌러보면 wave 함수가 실행된 것을 볼 수 있다! 현재 wave 함수는 값을 읽어오기만 해서 따로 가스비가 들진 않는다.

이제 wave 함수를 조금 변형해서 값을 기록하도록 해보자.

const wave = async () => {
    try {
      const { ethereum } = window;

      if (ethereum) {
        const provider = new ethers.providers.Web3Provider(ethereum);
        const signer = provider.getSigner();
        const wavePortalContract = new ethers.Contract(contractAddress, contractABI, signer);

        let count = await wavePortalContract.getTotalWaves();
        console.log("Retrieved total wave count...", count.toNumber());

        //추가된 부분
        const waveTxn = await wavePortalContract.wave();
        console.log("Mining...", waveTxn.hash);

        await waveTxn.wait();
        console.log("Mined -- ", waveTxn.hash);

        count = await wavePortalContract.getTotalWaves();
        console.log("Retrieved total wave count...", count.toNumber());
      } else {
        console.log("Ethereum object doesn't exist!");
      }
    } catch (error) {
      console.log(error);
    }
  }

이제 다시 Wave at Me 버튼을 누르면 메타마스크 팝업이 뜨고, 컨펌을 누르면 콘솔창에 웨이브가 1 증가한 것을 볼 수 있다!


출처 및 참고자료


  1. Ethereum:: ABI와 관련된 Q&A 정리
  2. [이더리움] ABI (Application Binary Interface)
  3. 이더리움 스마트 컨트랙트 동작방식의 이해
  4. [학부연구생 - 블록체인] 컨트랙트와 트랜잭션의 이해
  5. [학부연구생 - 블록체인] ABI와 바이트코드에 대한 이해
  6. https://buildspace.so/p/build-solidity-web3-app
profile
Just BUIDL :)

0개의 댓글