[Project] Opensea NFT 거래소 Clone Coding

dhkim·2022년 8월 16일
1
post-thumbnail

[Project] BEB 프로젝트 #1 회고

오픈씨 클론 코딩 프로젝트는 간단한 기능을 가진 Opensea Clone 사이트로
메타마스크등 지갑 연결을 통해 본인이 소유중인 NFT뿐만 아니라 다양한 NFT를 조회, 거래, 민팅까지 할 수 있습니다.

프로젝트 팀 구성 & 역할 분담

프로젝트 기간 (8/8 ~ 8/12)
인원수 4명

  • Front-end 👉 (내가 담당한 역할 😎)
    리액트를 이용한 web3 웹페이지 개발 및 CSS 작업
  • Back-end
    파이어베이스 db 설계 및 연동
  • Smart Contract
    ERC-721 기반 NFT 발행, 조회, 전송을 위한 컨트랙트 개발 및 배포
    Hardhat contract deploy 구현
    marcket place Contract 구현

개발 내용

  • Wallet 👉 홈페이지와 MetaMask 지갑과 연결
  • Explore 👉 판매등록한 NFT를 확인하고 구매할 수 있음
  • Activity 👉 NFT 거래 내역을 최신 순으로 확인할 수 있음
  • Create 👉 NFT를 신규로 만들고 민팅할 수 있음
  • Mypage 👉 자신이 소유중인 NFT를 조회 할 수 있음, 판매대금 확인 / 인출 가능
  • 상세 페이지 👉 NFT 상세 정보를 확인 할 수 있고 판매 / 구매 가능함

세부 기능

현재 마켓에 판매중인 NFT 둘러보기 기능 ✅
로그인 성공 시 색상이 변경되는 기능 (MetaMask 지갑 연결) ✅
내가 소유한 NFT 모아 보는 페이지 ✅
특정 NFT 상세 페이지 ✅
NFT 만들기 및 민팅 기능 ✅
내가 소유한 NFT 판매 등록 기능 ✅
판매중인 NFT 구매 기능 ✅
마켓의 거래 내역을 확인 할 수 있는 페이지 ✅
이미지 파일 업로드하여 바로 민팅 할 수 있는 기능 ✅
나의 판매 대금을 확인 할 수 있는 기능 ✅
나의 판매 대금을 인출 할 수 있는 기능 ✅

내가 맡은 포지션 & 스택

  • Position: Front-End
  • Stack : NODE js, React-Native, React-Hooks, React-Navigation, React-Native-Element, Firebase-DB, Github
  • Contribution :
  1. 메인 페이지 구현(Navigation, Home, Footer)
  2. 메타마스크 지갑 연결 기능 구현
  3. Explore 페이지 구현
  4. Activity 페이지 구현
  5. Flow Chart 및 Wireframe 작성

Flow Chart

이 프로젝트의 플로우차트 다이어그램은 아래와 같고, 전체적인 흐름만 파악하기 위해 기능과 파라미터들은 생략했으며 페이지와 컴포넌트로 표현하였다.

파이어베이스 DB 스키마

이 프로젝트에는 실시간으로 마켓 플레이스에 등록된 nft 정보를 저장하고 불러내기 위해 실시간 동기화 클라우드 데이터 베이스인 Firebase-RealtimeDB를 사용하였고, Firebase 실시간 데이터베이스는 NoSQL 형식으로 이 프로젝트의 데이터 스키마는 아래와 같다.

  • Tokenlist: 해당 웹페이지에서 민팅된 NFT들의 정보를 나타낸 list, tokenId로 인덱싱 되어있다.
  • History: 거래 기록으로 이루어진 list, 거래가 추가된 순서대로 인덱싱 되어있다.

Wireframe

데모

1) Main Page : 오픈씨 메인 페이지 첫 화면

페이지 상단 네비게이션 바 에서 Explore, Activity, Create, Mypage, Wallet을 선택할 수 있음

페이지 중앙에서 Explore, Create page로 한번에 이동 가능

2) Explore : 거래소에 판매중인 NFT 리스트를 볼 수 있는 페이지

  1. 구매 버튼을 누르면 해당 NFT 구매 가능
  2. NFT 이미지를 누르면 해당 NFT 상세 페이지로 이동 가능

3) Create : 새로운 NFT를 생성, 민팅하는 페이지

파일 선택을 눌러 png, jpg, gif 파일을 업로드 하고 Token Name에 이름을 기입 후 Mint 버튼을 누르면 새로운 NFT가 민팅된다.

4) MyPage : 내가 소유한 nft들을 볼 수 있는 페이지 (상단 네비게이션바 사람모양 로고를 눌러 이동)

nft를 눌러 nft detail 페이지로 이동 가능

상단 판매대금 확인 버튼을 눌러 이 마켓에서 nft를 판매하여 얻은 총 소득을 확인 가능

판매대금 인출 버튼을 눌러 마켓에 묶여 있는 나의 판매대금을 나의 account로 이동

5) NFT detail : Mypage 또는 Explore의 nft를 누르면 나타나는 nft detail 페이지

해당 nft의 이름, 소유자, 현재 가격 등이 나타나고, 내가 소유한 nft면 판매등록 버튼이, 내가 소유하지 않은 nft면 구매 버튼이 나타남

6) Activity : 마켓의 거래 내역이 최신 순으로 보이는 페이지 (상단 내비게이션 바의 Activity를 누르면 이동)

react 라우터 연결 및 props 전달

<BrowserRouter>
        <Nav
            setmainaccount={setMainAccount}
            setmainweb3={setMainweb3}
            setislogin={setIsLogin}
            login={isLogin}
        />
        <Routes>
            <Route exact={true} path="/" 
                element={<Home 
                setmainaccount={setMainAccount}
                setmainweb3={setMainweb3} />} />
            <Route path="/explore" 
                element={<Explore 
                web3={mainweb3}
                account={mainAccount} />} />
            <Route path="/activity" element={<Activity />} />
            <Route path="/create" element={<MintNft account={mainAccount} web3={mainweb3} />} /> 
            <Route path="/mypage" 
                element={<Mypage account={mainAccount} web3={mainweb3} />} /> 
             <Route path="/nftdetail/:tokenId" element={<NFTdetail web3={mainweb3} account={mainAccount} />} />
        </Routes>
        <div></div>
</BrowserRouter>

App.js에서 각 페이지마다 Route를 연결하고 필요한 web3, account 주소를 props로 전달

useEffect(() => {
    if (typeof window.ethereum !== "undefined") {
      // window.ethereum이 있다면
      try {
        const web = new Web3(window.ethereum); // 새로운 web3 객체를 만든다

        setWeb3(web);
      } catch (err) {
        console.log(err);
      }
    }
  }, []);

  const connectWallet = async () => {
    try {
      // try 문 안의 코드가 쭉 실행되고 에러가 없다면 catch는 건너뛴다
      if (window.ethereum) {
        // if 메타마스크가 설치되어있으면 실행된다
        const accounts = await window.ethereum.request({
          method: "eth_requestAccounts",
        }); // 연결된 메타마스크의 주소가 나온다
        // window.ethereum.request 을 console.log에 찍어보면 확인할수 있다

        setAccount(accounts[0]);
        //console.log(accounts);
        // 연결된 메타마스크의 주소를 useState에 담는다
      } else {
        // 메타마스크가 설치되어있지 않다면 alert 문구가 나온다
        alert("Install Metamask!");
      }
    } catch (error) {
      // 에러가 발생한다면 catch 실행
      console.error(error); // 에러가 발생했다고 출력
    }
  };

useEffect로 로그인한적이 있는지 한번만 확인하며 로그인되면 web3로 받아 전달하고 연결된 메타마스크 주소를 useState에 담는다.

Explore 페이지

useEffect(() => {
        const tokenId = '';

        const dbRef = ref(getDatabase());
        get(child(dbRef, `Test/Tokenlist/${tokenId}`))
            .then((snapshot) => {
                if (snapshot.exists()) {

                    const jsonData = snapshot.val();
  
                    let fiteredArray = [];

                    fiteredArray = jsonToArray(jsonData)
                                   .filter(token => token.sellBool === true)

                    setTokenList(fiteredArray);
                    // console.log(tokenList);
                    isSetLoading(false);

                } else {
                    console.log("No data available");
                }
            })
            .catch((error) => {
                console.error(error);
            });
    }, []);

    //json 다중 객체를 배열로 변환
    function jsonToArray(json){
        var result = [];
        var keys = Object.keys(json);
        keys.forEach(function(key){
            result.push(json[key]);
        });
        return result;
    }

파이어베이스에 저장된 NFT 정보를 json으로 가져와서 배열로 변환하고 필터를 이용해 판매등록된 NFT만 가져온다.

<ViewItems>
          <div className="board_title">
              <h2>Explore</h2>
          </div>

            <ItemCount>
                {tokenList.length}
                &nbsp;items</ItemCount>
            <ItemContainer>
                {
                    tokenList && tokenList                                  
                        .map((token) => {
                            return (
                                <Erc721
                                    web3 = {web3}
                                    account = {account}
                                    tokenId={token.tokenId}
                                    tokenUri={token.tokenURI}
                                    tokenName={token.tokenName}                                  
                                    tokenOwner={token.tokenOwner}
                                    price={token.price}
                                    link={`/nftdetail/${token.tokenId}`}
                                    isLoading={isLoading}
                                    key={token.tokenId}
                                     />
                            );
                        })                     
                }
            </ItemContainer>
        </ViewItems>

useState 배열에 담긴 tokenList를 map을 이용하여 Erc721 컴포넌트로 props 데이터를 넘겨준다.

Activity 페이지

useEffect(() => {
        const tokenId = '';

        const dbRef = ref(getDatabase());
        // history 구매내역 데이터 전체를 가져온다.
        get(child(dbRef, `Test/History/${tokenId}`))
            .then((snapshot) => {
                if (snapshot.exists()) {

                    const jsonData = snapshot.val();

                    let reversed = [];
                    // 최신 내역 순서대로 보여주기 위해 들어간 순서 반대로 베열에 다시 삽입
                    jsonToArray(jsonData).forEach(child => {
                        reversed.unshift(child);
                    });

                    setHistory(reversed);
                    isSetLoading(false);

                    console.log(history);
                } else {
                    console.log("No data available");
                }
            })
            .catch((error) => {
                console.error(error);
            });
    }, []);

파이어베이스에 저장된 구매내역 NFT 정보를 json으로 가져와서 배열로 변환하고 최신 내역 순서대로 보여주기 위해 들어간 순서 반대로 배열에 다시 넣어준다.

개발 회고(KPT 방식)

Keep : 프로젝트의 라우터 연결시 각 페이지 및 컴포넌트마다 잘 배치될 수 있도록 구현하였고 App.js 최상위 컴포넌트에서 web3, account 주소 등 props를 효율적으로 관리할 수 있도록 노력하였으며, 앞으로 프로젝트에서도 각 컴포넌트 배치 및 props에 대한 정의를 잘할 수 있도록 팀원들과 커뮤니케이션을 많이 해야겠다. 또한 컴포넌트를 정확히 분리해서 각 컴포넌트가 가지는 고유한 기능을 잘 살리도록 유지해야겠다.
그리고 시간이 부족한 상황에서 빠른 결정을 할 수 있도록 내가 아는 지식과 그 지식들이 프로젝트에 적용하기 괜찮은지 커뮤니케이션을 충분히 하고 구글링을 통해 검증하며, 다이어그램 툴이나 파이어베이스 db 등과 같은 아이디어를 제공한 것처럼 프로젝트에 도움이 될 수 있는 의견을 잘 정리해서 제안해야겠다. 앞으로 어떤 아이디어가 있으면 적극적으로 서치해보고 제안을 해서 커뮤니케이션을 활발하게 이끄는 것도 중요하다고 생각했다.

Problem : 처음 프로젝트를 진행하고 깃허브를 세팅하다 보니까 브랜치를 추가하고 이후에 merge 부분에서 충돌이 나거나 upstream repo에 PR이 안되는 문제가 있었고 처음에는 당황하여 초기라고 생각해서 fork, clone을 다시 해서 해결을 했었는데 다음에는 절대 이렇게 하지 않고 충돌이 나도 문제를 찾아서 정확히 해결해야 겠다는 다짐을 했다. 또한 초기에 세팅해야 되는 부분들에 대한 기준을 정하지 않아서 충돌이 나는 파일들도 있었는데, 팀원들과 커뮤니케이션을 충분히 해서 초기 기준에 대한 팀 룰을 명확하게 잡아야겠다는 생각을 했다.
그리고 스마트 컨트랙트를 개발하고 싶었지만 팀에서 프론트엔트 지원이 없어서 프론트엔드 구현을 맡아봤는데 배운 기간이 좀 지났다보니까 어떻게 컴포넌트 구조를 짜고, props를 효율적으로 넘겨주는지 공부하면서 진행하는 어려움이 있었다.
또한 메인페이지를 작업하면서 첫번째로 기능 구현이 우선이지만, CSS를 최소한으로는 꾸며야겠다고 생각했고 하다보니까 욕심이 생겨서 MUI와 같은 리액트 UI 컴포넌트와 CSS를 찾는데 너무 많은 시간을 소요했던 것 같다. 하루에 배분된 시간을 항상 체크해서 우선순위를 두고 스케줄링하며 개발을 진행해야겠다는 생각을 했다.
그리고 시간이 부족한 관계로 db연동을 빠르게 하기 위해 파이어베이스를 도입했지만, 데이터 read 연동 과정에서 최근에 바뀐 9버전의 문서를 찾기가 상당히 어려웠고 시간이 꽤 많이 소요되는 문제가 있었다. 또한 관계형 db의 WHERE절과 같은 조건을 주는 db 쿼리가 적용이 안되서 전체 데이터를 배열에 받아서 필터로 해결하였다. 이처럼 빠른 개발을 위해 신규 기술을 개발하는 것도 좋지만, 충분한 검토와 호환성등을 확인하는게 뒷받침되어야 될 것이다.

Try : 파이어베이스에서 조건을 줘서 데이터를 가져오는 것이 전체를 가져와서 필터하는 것보다 효율적이기 때문에 관련 db 쿼리를 찾아봐서 적용해 볼 것이고, MetaMask 로그아웃 기능을 구현하다가 시간 부족으로 딜레이했는데 추후에 구현해 볼 것이다. 그리고 스마트 컨트랙트 개발을 못해서 아쉬움이 있기 때문에 마이페이지에서 NFT를 전송하는 기능을 추가로 구현해 볼 생각이다.
그리고 처음에 기획 단계에서부터 잘 정비해서 깃허브 세팅, 와이어프레임, db스키마, 폴더 구조, 파일 관리 등의 앞으로 충돌이 생길 수 있는 부분을 방지하기 위해 팀 룰의 기준을 정확히 정해서 프로젝트를 진행하도록 노력할 것이다.

깃허브 주소

Github : https://github.com/codestates/BEB-ON-Project1

profile
Blockchain developer

0개의 댓글