본문 바로가기
프로그래밍/solidity

[220531] 사이트에 접속하자마자 메타마스크 연동하기 / 갤러리 보이지 않음 이슈해결

by 한코코 2022. 5. 26.

해당 url에 접속하면 메타마스크와 연동하게 해주는 로직 구성

redux로 데이터를 넘겨주려고하다가 넘겨주려는 데이터가 메타마스크 지갑주소 하나인걸 생각하면 볼륨이 너무 과해서 고민.

교수님이 redux말고 contextAPI로 연결해보라고해서 사용해보았다.

import Web3 from 'web3'
import React, { FC, useEffect, useState, createContext, useContext } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import App from './_app'

export const AccountContext = createContext()

const AccountProvider = ({children}) => { 
  const [account, setAccount] = useState("");
  const getAccount = async () => {
    try{
      if (window.ethereum) {
        console.log('try 실행중')
        const accounts = await window.ethereum.request({
          method: "eth_requestAccounts",
        });
        setAccount(accounts[0]); // 객체를 불러움
        console.log('지갑주소 : ',account)
        // 지갑주소 대소문자 상관없음
      }
    }catch(e){
      console.log('에러발생')
    }
  }

  useEffect(() => {
    getAccount();
  }, [account]);

  return(
    <AccountContext.Provider value={account}>
      {children}
    </AccountContext.Provider> 
  )
}

export default AccountProvider
export const useAccountContext = () => useContext(AccountContext)

 

 

 

 

코드 분석

메타마스크 지갑주소를 account로 불러올것이다.

그리고 그 account가 변화할때마다 갱신할것이므로 useEffect를 불러온다.

갱신할 함수에는 getAccount()와, 갱신여부를 체크할 변수는 account로 넣는다.

 

useEffect(() => {
	getAccount();
}, [account]);

 

useEffect를 사용하기 위해 useState를 선언한다.

useState에서 초기화된 account가 useEffect에서 사용될 것이다.

useEffect에서 사용될 getAccount 함수를 선언한다.

윈도우에 메타마스크를 설치하면 window.ethereum 오브젝트가 생기는데, 이를 통해서 메타마스크의 설치유무를 파악할 것이다.

만약 설치되어있다면 지갑주소(account)를 받아서 반환할것이다.

const [account, setAccount] = useState("");

const getAccount = async () => {
    try{
        if (window.ethereum) {
            console.log('try 실행중')
            const accounts = await window.ethereum.request({
            	method: "eth_requestAccounts",
            });
            setAccount(accounts[0]); // 객체를 불러움
            console.log('지갑주소 : ',account)
            // 지갑주소 대소문자 상관없음
        }
    }catch(e){
	    console.log('에러발생')
	}
}

 

 

createContext를 통해서 전역적으로 데이터를 공유할 수 있는 AccountContext를 만들 수 있다.

전역적으로 데이터를 공유할 범위를 지정할 Provider를 생성하기 위해 AccountProvider를 생성한다.

( ContextAPI를 생성함으로써 Provider를 사용할 수 있게 된다. )

내가 Provider를 통해 전달하고 싶은 값(value)는 지갑주소인 account이므로,

<AccountContext.Provider value={account}>를 작성한다.

필요한 코드를 모두 작성했다면 export를 통해서 내보내준다.

export const AccountContext = createContext()

const AccountProvider = ({children}) => { 
	...
  return(
    <AccountContext.Provider value={account}>
      {children}
    </AccountContext.Provider> 
  )
}

export default AccountProvider
export const useAccountContext = () => useContext(AccountContext)

 

 

 

사용할 범위를 ContextAPI가 있는 컴포넌트로 감싸주기

나는 사이트를 접속하자마자 메타마스크와 연동하고 싶으므로 최상단에 만들어놓은 ContextAPI를 넣어줄것이다.

만들어놓은 최상단 App.jsx를 미리 만들어놓은 MetaLogin 컴포넌트로 감싸준다

import React, { FC, useEffect, useState } from "react";
import Web3 from 'web3'
import DefaultLayout from "../components/DefaultLayout"
import MetaLogin from './_metaLogin'

const App = ({Component,pageProps}) => {
    return(
        <>
        <MetaLogin>
        <DefaultLayout >
            <div>
            <Component {...pageProps} />
            </div>
        </DefaultLayout>
        </MetaLogin>
        </>
    )
}

export default App

 

 

데이터를 끌고오고 싶은 컴포넌트에 useContext 끌고오기

import React, { FC, useEffect, useState } from "react";
import { useAccountContext } from "../_metaLogin";

export interface MarketAppProps {
  account: string;
}

const MarketApp: FC<MarketAppProps> = () => {
  const accountContext = useAccountContext();
  console.log("accountContext", accountContext);

  //메타마스크 주소를 담는 state
  const [data, setData] = useState<string>("");

  // 계정을 가져오는 코드
  const getAccount = () => {
    try {
      if (accountContext) {
        setData(accountContext); // 객체를 불러움
      }
    } catch (error) {
      // 이더리움이 없는 경우
      console.error(error);
    }
  };

  useEffect(() => {
    getAccount();
  }, [data]);

  console.log("marketApp에서 받은 account", data);

  return (
    <BrowserRouter>
      <Layout>
        <Routes>
          <Route
            path="/market/main"
            element={<Main account={accountContext} />}
          />
      </Layout>
    </BrowserRouter>
  );
};

export default MarketApp;

 

 

 

 


생성한 nft가 갤러리에서 보이지 않는 이슈 발생

code -32603

MetaMask - RPC 오류: 내부 JSON-RPC 오류입니다. {코드: -32603, 메시지: '내부 JSON-RPC 오류입니다.

MetaMask - RPC Error:
Internal JSON-RPC error.
{code: -32603, message: 'Internal JSON-RPC error.

 

밑도끝도없이 다음 에러를 띄워서 검색하니까 이유가 세가지가 나온다.

1.   gas limit 이슈

2.   metamask 거래지연건이 쌓인 이슈

3.   ganache 연결 오류 이슈

 

 

나는 2, 3번 이슈는 해당되지 않기때문에 gas 문제만이 남는다.

민팅을 진행하고 생성한 nft를 모아보는 갤러리 렌더링을 할때 에러가 발생하기 때문에 한줄씩 교수님이.. 손수 파주심..

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "./SaleAnimalToken.sol";

contract MintAnimalToken is ERC721Enumerable {
    constructor() ERC721("h662Animals", "HAS") {}
    SaleAnimalToken public saleAnimalToken;
    
    mapping(uint256 => uint256) public animalTypes;

    struct AnimalTokenData {
        uint256 animalTokenId;
        uint256 animalType;
        uint256 animalPrice;
    }

    function mintAnimalToken() public {
        uint256 animalTokenId = totalSupply() + 1;

        uint256 animalType = uint256(
            keccak256( 
                abi.encodePacked(
                    block.timestamp, msg.sender, animalTokenId
                )
            )
        ) % 5 + 1;
        
        animalTypes[animalTokenId] = animalType;
        _mint(msg.sender, animalTokenId);
    }

    function getAnimalTokens(address _animalTokenOwner) view public returns (AnimalTokenData[] memory) {
        uint256 balanceLength = balanceOf(_animalTokenOwner);

        require(balanceLength != 0, "Owner did not have token.");

        AnimalTokenData[] memory animalTokenData = new AnimalTokenData[](balanceLength);

        for(uint256 i = 0; i < balanceLength; i++) {
            uint256 animalTokenId = tokenOfOwnerByIndex(_animalTokenOwner, i);
            uint256 animalPrice = 0;
            uint256 animalType = animalTypes[animalTokenId];
            animalTokenData[i] = AnimalTokenData(animalTokenId, animalType, animalPrice);
        }

        return animalTokenData;
    }

    // function getAnimalTokens(address _animalTokenOwner) view public returns (uint256) {
    //     uint256 balanceLength = balanceOf(_animalTokenOwner);
    //     require(balanceLength != 0, "baboo..");
    //     return balanceLength;
    // }
	
    function setSaleAnimalToken(address _saleAnimalToken) public {
        saleAnimalToken = SaleAnimalToken(_saleAnimalToken);
    }
}

 

 

 

코드 분석

보통 revert 에러건은 require 때문에 일어난다고하는데, 우선 실행이 멈추는 getAnimalToken 함수부터 뜯어보기 시작했다.

코드가 가스비를 많이 소비하는 구조로 짜여져있는 코드일지도 모른다는 가설을 체크하기위해 최소한의 기능만 남겨놓은 함수부터 돌려보았다. 그리고 코드가 돌아간다. 가설이 맞다는 이야기임. 껄껄. 교수님은 언제나 옳으시다.

function getAnimalTokens(address _animalTokenOwner) view public returns (AnimalTokenData[] memory) {
    uint256 balanceLength = balanceOf(_animalTokenOwner);
    return animalTokenData;
}

 

 

for문에서 어느 조건이 토큰과소비를 하는지 알 수 없기 때문에 animalTokenId, animalPrice, animalType를 하나씩 체크해보았다.

우선 animalTokenId이 돌아가는지 알기 위해 나머지 초기값을 0으로 두었다.

이런식으로 나머지 animalPrice, animalType도 체크해본다.

function getAnimalTokens(address _animalTokenOwner) view public returns (AnimalTokenData[] memory) {
    uint256 balanceLength = balanceOf(_animalTokenOwner);
    require(balanceLength != 0, "Owner did not have token.");

    AnimalTokenData[] memory animalTokenData = new AnimalTokenData[](balanceLength);

    for(uint256 i = 0; i < balanceLength; i++) {
        uint256 animalTokenId = tokenOfOwnerByIndex(_animalTokenOwner, i);
        uint256 animalPrice = 0;
        uint256 animalType = 0;
        //animalTokenData[i] = AnimalTokenData(animalTokenId, animalType, animalPrice);
    }

    return animalTokenData;
}

 

 

nft 토큰의 가격을 설정하기 위한 변수 animalPrice에서 터지는걸 발견.

이 메서드가 있어야 가격을 설정할 수 있지만 우선 돌아가는게 우선이니 0으로 놓았다.

나중에 솔리디티 수업 들으면 고이고이 데려와서 손 봐야지ㅇ>-<

 

function getAnimalTokens(address _animalTokenOwner) view public returns (AnimalTokenData[] memory) {
    uint256 balanceLength = balanceOf(_animalTokenOwner);
    require(balanceLength != 0, "Owner did not have token.");

    AnimalTokenData[] memory animalTokenData = new AnimalTokenData[](balanceLength);

    for(uint256 i = 0; i < balanceLength; i++) {
        uint256 animalTokenId = tokenOfOwnerByIndex(_animalTokenOwner, i);
        uint256 animalType = animalTypes[animalTokenId];
        uint256 animalPrice = 0;
        
        animalTokenData[i] = AnimalTokenData(animalTokenId, animalType, animalPrice);
    }
    return animalTokenData;
}

 

 

댓글