[DApp] NFT Market - My Virtual Animal 프로젝트

jhcha·2023년 10월 25일
0

DApp

목록 보기
9/9
post-thumbnail

작성중

0. Overview

본 글에서는 DApp 두 번째 프로젝트 NFT Marketplace, My Virtual Animal를 진행하며 관련 내용을 정리하겠습니다.
지난 DEX Superswap 프로젝트와 다르게, Next.js 프레임워크를 사용했습니다. 스마트 컨트랙트는 오픈제플린을 사용하면서도 백엔드 서버를 대체할 수 있도록 필요하다고 생각하는 기능들을 추가로 작성했습니다.
프로젝트 진행 중 주요 기능 개발 내용을 순서대로 나열했고, 작성한 코드를 중점으로 기능 설명을 추가했습니다. 따라서, 환경 설정과 관련된 기초 자료들은 해당 Velog에서 작성된 참고자료 링크로 대체했습니다.

  • 개발 환경
    Proivder - Infura, Metamask (Wallet)
    Blockchain Network - Etherum Goerli Testnet
    UI - React, MUI (Design System)
    Framework - Next.js, Truffle
    Language - Typescript, Javscript, Solidity
    etc. - Web3.js, Ehters.js, OpenZeppelin

1. Infura - Provider Endpoint

Infura 회원가입 후 API Key, Goerli Testnet Endpoint 생성
참고자료: Goerli 이더리움 테스트넷 사용하기 with Infura

2. Metamask, Goerli Testnet 설정

메타마스크 설치 후 계정 생성, Georli Testnet 설정, GoerliETH 발급 받기
참고자료: Goerli 이더리움 테스트넷 사용하기 with Infura

3. Truffle - 스마트 컨트랙트 배포

프로젝트에서 사용할 하나의 컨트랙트 MvaV1Market를 생성했다. 해당 컨트랙트는 ERC-721 토큰을 생성하고, 토큰에 URI 정보를 추가할 수 있다. 추가적으로, 클라이언트에서 필요한 NFT 토큰 정보를 관리할 수 있는 몇가지 상태 변수를 추가했다.

  • 상태 변수
    컨트랙트 내에서 사용하는 고유 토큰 ID, 해당 NFT 토큰 가격, 마켓 판매 상태 정보, 사용자가 소유한 토큰 정보를 생성했다.
contract MvaV1Market is ERC721URIStorage {
	uint256 private _nextTokenId;
    mapping(uint256 => uint256) private _nftPrices;
    mapping(uint256 => bool) private _isForSale;
    mapping(address => uint256[]) private _ownedTokens;

    event NFTPriceUpdated(uint256 indexed tokenId, uint256 price);
    event NFTSaleStatusUpdated(uint256 indexed tokenId, bool isForSale);
    event NFTSold(uint256 indexed tokenId, address indexed buyer, address indexed seller, uint256 price);

    constructor() ERC721("MyVirtualAnimal", "MVA"){}
}
  • 토큰 발행
    NFT 토큰에 저장하는 tokenURI는 IPFS에 파일 업로드 후 반환되는 CID 정보를 입력한다.
    추가적으로, 마이페이지에서 NFT 목록을 조회할 수 있도록 ownToken 리스트 상태 변수에 해당 토큰 ID를 저장한다.
contract MvaV1Market is ERC721URIStorage {
	function createNFT(address owner, string memory tokenURI)
        public
        returns (uint256)
    {
        uint256 tokenId = _nextTokenId++;
        _mint(owner, tokenId);
        _setTokenURI(tokenId, tokenURI);
        _ownedTokens[owner].push(tokenId);

        return tokenId;
    }
}
  • 토큰 상태 관리
    토큰 소유자가 해당 토큰에 대한 상태를 관리할 수 있는 기능들을 정의했다. 토큰 소유자는 setNFTPrice를 통해 소유하고 있는 토큰의 가격을 설정하고, setNFTForSale를 통해 토큰의 판매 상태를 변경할 수 있다.
contract MvaV1Market is ERC721URIStorage {
	function setNFTPrice(uint256 tokenId, uint256 price) public {
        require(ownerOf(tokenId) == msg.sender, "Only owner can set the price");
        _nftPrices[tokenId] = price;
        emit NFTPriceUpdated(tokenId, price);
    }

    function setNFTForSale(uint256 tokenId, bool isForSale) public {
        require(ownerOf(tokenId) == msg.sender, "Only owner can set the sale status");
        require(_nftPrices[tokenId] > 0, "NFT price should be greater than 0");
        if (isForSale) approve(address(this), tokenId);
        _isForSale[tokenId] = isForSale;
        emit NFTSaleStatusUpdated(tokenId, isForSale);
    }
}
  • NFT 토큰 거래
    payable buyNFT를 통해 판매중인 토큰을 구매할 수 있는 기능을 작성했다.
contract MvaV1Market is ERC721URIStorage {
	function buyNFT(uint256 tokenId) public payable {
        require(_isForSale[tokenId], "NFT is not for sale");
        require(msg.value == _nftPrices[tokenId], "Incorrect Ether sent");

        address seller = ownerOf(tokenId);
        _transfer(seller, msg.sender, tokenId);
        payable(seller).transfer(msg.value);

        uint256 indexToRemove = 0;
        for(uint256 i = 0; i < _ownedTokens[seller].length; i++) {
            if(_ownedTokens[seller][i] == tokenId) {
                indexToRemove = i;
                break;
            }
        }
        _ownedTokens[seller][indexToRemove] = _ownedTokens[seller][_ownedTokens[seller].length - 1];
        _ownedTokens[seller].pop();
        _ownedTokens[msg.sender].push(tokenId);

        _isForSale[tokenId] = false;
        emit NFTSold(tokenId, msg.sender, seller, msg.value);
    }
}

작성한 코드는 컴파일을 통해 ABI 파일을 생성하고, Goerli Network에 배포하여 배포된 컨트랙트 주소를 클라이언트에서 사용한다.

Truffle 환경 구축, 컴파일, 배포 참고자료: Truffle - 스마트 컨트랙트 배포 에러 정리
Solidity 이더리움 토큰 관련 내용 참고자료: [Solidity] ERC-721, ERC-1155 / [Solidity] ERC-20

4. IPFS NFT 이미지 업로드

블록체인은 NFT 토큰에 저장한 이미지 정보를 통해 해당 이미지의 소유권이 토큰 소유자에게 있음을 증명할 수 있다. 하지만, NFT 토큰에 저장된 이미지 정보는 변경되지 않지만, 해당 이미지가 저장된 경로가 변경된다면 NFT 토큰 가치가 변경될 수 있는 문제점이 있다. 따라서, 일반적으로 NFT 토큰은 저장하는 이미지 정보에 IPFS를 통해 변경되지 않는 고유의 CID (Content Identifier)를 저장하게 된다.
IPFS에 이미지 파일을 업로드하기 위해 Pinata를 이용한다. Pinata는 IPFS에 파일 업로드를 도와주는 플랫폼 서비스이고, 회원가입 후 일정 수의 파일 업로드에 대해서 무료 서비스를 제공한다. (Pinata 사이트)
Pinata 사이트 로그인 후 마이페이지에서 파일을 업로드하거나 IPFS에 업로드한 파일의 정보를 관리할 수 있다.

이후 DApp에서 Pinata SDK를 사용하여 IPFS에 파일을 업로드하기 위해 API Key를 발급받는다.

그리고, 업로드한 파일에 접근할 수 있도록 Gateways 도메인도 확인한다.

위에서 얻은 정보를 토대로 다음과 같이 정리할 수 있다.
1. 발급받은 API Key는 클라이언트에서 Pinata SDK를 이용하여 IPFS에 파일을 업로드한다.
2. IPFS에 파일을 업로드하면 고유의 CID 값을 반환한다.
3. IPFS Gateway + CID를 통해 IPFS 네트워크에 저장된 이미지 파일에 접근할 수 있다.

8. 프로젝트 코드

프로젝트 코드는 아래 Github 링크에서 확인할 수 있습니다.
Front 프로젝트 > https://github.com/jh-cha/my-virtual-animal
스마트 컨트랙트 배포 프로젝트 > https://github.com/jh-cha/my-virtual-animal-truffle

0개의 댓글

관련 채용 정보