주요 서비스
ERC20 토큰으로 유저가 커스텀한 이미지를 가진 NFT 민팅
유저 간 ERC20 토큰 전송 기능
백엔드는 로컬 블록체인 노드-서버-DB
구조를 가지고 있으며, NFT 민팅을 위한 이미지를 IPFS 파일 Pinning 서비스인 Pinata를 이용해 IPFS 네트워크 상에서 올렸다.
컨트랙트는 컨트랙트 배포부터 트랜잭션 전송, 테스트, 로컬 블록체인 노드를 제공하는 Foundry 라는 툴을 이용해서 배포 및 로컬에서 테스트했다.
DB는 MySQL을 이용했으며, Sequelize라는 ORM 서비스를 이용해 쿼리문 없이 쉽게 DB와 상호작용할 수 있도록 셋팅했다.
브라우저에 입력된 파일을 로컬에 저장하여 IPFS에 pinning 하기 위해서 Multer라는 Express 미들웨어를 사용했다.
서비스의 플로우는 크게 회원일때와 비회원일때로 나눠서 생각할 수 있다.
비회원일때는 메인페이지의 게시글 열람 및 게시글 세부 페이지 열람만 가능하다.
회원일 경우에는 게시글 열람은 기본이고, ETH faucet 요청, 게시글 작성 및 SWT 보상 받기, 게시글 삭제(수정은 API만 만들었고, 클라이언트 적용은 아직 못했음), SWT를 이용해서 커스텀 이미지를 가진 NFT 민팅, 유저 간 SWT 전송이 가능하다.
팀원은 총 네명이었고, 백엔드 포지션 1명, 프론트엔드 포지션 2명, 그리고 나는 리소스가 부족하다 싶은 곳은 전부 참여했다.
맡았던 태스크를 공유해보자면,
- 컨트랙트 배포
- ERC20
- ERC721URIStorage - NFTLootBox 형태, 지정한 ERC 20 토큰의 특정 양으로 구매가능
- API Docs 작성- API 구현
- users.js - 특정 유저의 정보 조회
- posts.js - 게시글 조회, 게시글 작성, 게시글 삭제, 게시글 수정
- fildUpload.js - 로컬에 파일 저장, ipfs에 이미지 파일 pinning 및 ipfsHash 가져오기
- nft.js - NFTLootBox 로 NFT 민팅- 클라이언트 API 연동
- 회원가입, 로그인, 게시글 조회, 유저 정보 조회, eth-faucet 요청, 게시글 포스팅, nft 민팅- 클라이언트 유저 정보 관리(react Context hook, localStorage 사용)
- 프로젝트 진행 과정 매니징
위와 같다.
프로젝트는 약 10일간 진행되었고, 이전의 짧은(약 5일) 프로젝트를 경험하고 나서 한거라 훨씬 여유가 있었던 것 같다.
단순 리액트 state로 유저 정보를 관리하면, 페이지 새로고침 시에 유저 정보가 사라진다.
그래서 브라우저의 로컬 스토리지에 유저가 로그인 시에 받은 jsonwebtoken을 저장하고, 해당 토큰을 이용하여 새로 고침 시에, jsonwebtoken이 있으면, 유저 정보를 다시 불러오도록 바꾸었다.
그리고 state로 관리하기엔 유저 정보를 사용해야 할 곳이 많기 때문에 상속을 매번 해줘야 하는 불편함이 있었다. 그 해결책으로 Context hook을 이용해서 유저 정보를 한번에 관리하였다.
ETH, ERC20 토큰의 경우엔 Decimal(소수점)이 18자리를 차지했기 때문에 JavaScript로 그냥 수를 사용하면 오버플로우 오류가 일어났다. 그래서 BigInt로 감싸줘서 사용을 했다.
useEffect 함수는 아무것도 반환을 하지 않거나 clean up 함수를 반환해야 하므로
useEffect(async() => {
const exampleData = await exampleData();
console.log(exampleData);
}, []);
위와 같이 작성하면 오류가 발생한다.
useEffect 내에 함수를 만들어서 사용하면 해결된다.
useEffect(() => {
const fetchData = async () => {
const post = await viewPost();
setPosts(post.data);
console.log(post.data);
};
fetchData();
}, []);
transaction response는 컨트랙트 인스턴스의 메소드를 호출한 것 자체(마이닝 되기 전!)이고,
transaction receipt는 컨트랙트 인스턴스의 메소드 호출한 거 마이닝되기를 기다린 후 나온 결과이다
→ logs에서 원하는 정보 꺼내쓸 수 있다! 근데 이더스캔 없으면 인덱싱이 안되어 있어서 알아보기 어려울 수도 있다..!
→ 로컬 블록체인으로 개발할때, 세폴리아 테스트넷으로도 항상 연동할 수 있도록 환경 세팅해두어야 한다..!
→ 10진수가 아닌 주소나 여러 정보들은 숫자가 아닌 문자열로 나오기 때문에 parseInt(Number(string), 16)
을 항상 생각하고 있어야 한다
파운드리에서 외부 컨트랙트를 설치해서 사용할 때, 깃허브의 레포지토리를 클론해오므로, 서브모듈이 생성된다.
이 상태로 현재 작업하고 있는 깃을 깃허브에 올린 다음, 깃 클론해오면 서브모듈을 따로 설치해주는 과정을 거쳐야 한다.
git submodule update --init
# if there are nested submodules:
git submodule update --init --recursive
그때, 위와 같은 명령어를 사용해주면, 당시에 다운받았던 버전과 같은 버전의 깃허브를 다시 클론해온다.
백엔드부터 스마트 컨트랙트, 프론트엔드까지 두루두루 작업하다 보니, 서비스의 전체적인 아키텍처 익히기에 매우 좋았다. 그렇지만, 상당히 정신이 없었던 것은 사실이고, 디테일적인 측면에서 놓친 부분이 많았던 것 같다. 그래서 1차적인 기능 구현 완료 이후에도 코드를 계속 리팩토링하고, 기능을 테스트 해보면서 계속 개선해나갔다.
실제로 서비스를 런칭하는 단계에서 테스트가 얼마나 중요한지 다시금 깨닫게 되었다. 예전에 PM 직무를 수행할 때는 기능 테스트의 중요성을 이정도로 느끼진 못했는데, 직접 기능들을 구현해보니, 예기치 못한 상황에서 에러가 발생할 수 있단 것을 깨달았고, 그것들을 수정하는데도 많은 에너지가 할애되어야 한다는 생각을 가지게 해준 프로젝트였던 것 같다.