[Truffle] ropsten testnet에 ERC-721 배포

yooni·2022년 4월 5일
0
post-thumbnail

🖼 Purpose

solidity를 사용해 간단한 NFT 발행을 위한 개발을 진행하고자 한다.

  • 컨트랙트는 truffle을 이용해 infura-ropsten 네트워크에 배포된다.
  • NFT 수령인의 주소와 NFT의 정보를 담고 있는 엔드포인트 URL을 입력하면 NFT가 민팅되는 함수를 구현할 것이다.



⌨️ 진행 과정


대략적인 개발 흐름은 다음과 같다.

  • truffle 개발 환경 설정 (ropsten network)
  • 코드 작성
  • truffle을 통해 네트워크에 배포
  • NFT 민팅 실행



(1) truffle 개발 환경 설정

프로젝트 루트 디렉토리에서 터미널을 열고 truffle 개발 환경을 셋팅해준다.

truffle init
npm init
npm install -D truffle-plugin-verify



❗️ truffle-plugin-verify
etherscan에서 컨트랙트 코드를 자동으로 검증할 수 있게 하는 플러그인이다. 이 플러그인이 없으면 컨트랙트를 배포하고 etherscan에서 코드를 Verify & Publish 하려고 할 때 아래와 같은 에러 메시지가 뜰 것이다. 에러 메시지가 말해주는 대로 Remix를 사용한다면 에러가 발생하지 않겠지만 truffle-plugin-verify 플러그인을 사용하여 truffle로 진행해보려고 한다.


truffle-config.js 설정

truffle-config.js 파일에서 네트워크 관련 설정을 해줘야 한다. 파일의 주석처리 된 내용 중 다음과 같이 Infura를 통해 배포하고자 할 때 추가되어야 할 코드들이 담겨 있다. 이 내용에 따르면 배포에 이용하고자 하는 계정(나는 메타마스크 이용 예정)의 니모닉 코드Infura API key가 필요하다.


Infura를 사용하기 위해 @truffle/hdwallet-provider를 설치해준다.

npm install @truffle/hdwallet-provider

❗️ [중요] 메타마스크에서 니모닉 코드를 가져온 뒤 프로젝트 폴더 내에 .secret 파일을 생성하여 그 안에 니모닉을 담는다. 이 파일은 절대 유출되면 안되므로 .gitignore 파일에 추가해야 한다.


주석 처리되어있던 아래의 코드들을 활성화 해주고, YOUR-PROJECT-IDInfura API KEY를 복사해서 붙여 넣어준다.

const HDWalletProvider = require('@truffle/hdwallet-provider');

const fs = require('fs');
const mnemonic = fs.readFileSync(".secret").toString().trim();

module.exports = {
  networks: {
    ropsten: {
    provider: () => new HDWalletProvider(mnemonic, `https://ropsten.infura.io/v3/YOUR-PROJECT-ID`),
    network_id: 3,       // Ropsten's id
    gas: 5500000,        // Ropsten has a lower block limit than mainnet
    confirmations: 2,    // # of confs to wait between deployments. (default: 0)
    timeoutBlocks: 200,  // # of blocks before a deployment times out  (minimum/default: 50)
    skipDryRun: true     // Skip dry run before migrations? (default: false for public nets )
    },
  },
  
  // plugins와 api_keys는 truffle-plugin-verify 사용을 위해 추가한 것이다.
  plugins: [
    'truffle-plugin-verify'
  ],
  api_keys: {
    etherscan: 'VDPIVK42DMZVGX9F5XK4T2JA2DZQWPZCF9'
  }
}



(2) contract 코드 작성

truffle 및 ropsten network에 대한 세팅이 완료되었으므로 배포할 컨트랙트 코드를 작성한다. OpenZeppelinerc721 라이브러리를 활용하기 위해 OpenZeppenlin을 프로젝트에 설치해준다.

npm install @openzeppelin/contracts

프로젝트의 contracts 폴더 내에 sol 파일을 생성하고 컨트랙트 코드를 작성한다. ERC721 표준 코드에는 NFT 민팅 함수가 구현되어 있지 않다. 따라서 OpenZeppelin의 ERC721 코드를 상속 받았어도 민팅을 위한 함수는 스스로 작성해주어야 한다. 민팅의 방식은 개발자와 제공 서비스에 따라서 로직이 크게 달라질 수 있기 때문에 표준에는 민팅 함수가 구현되어 있지 않은 듯 하다.

아래의 코드는 NFT 정보가 담긴 NFT를 tokenURI로 입력하면 NFT가 민팅되도록 하고 있다. 오직 컨트랙트 생성자(owner)만이 민팅을 실행할 수 있으며, 민팅된 NFT는 owner에게 발급된다.

//Contract based on [https://docs.openzeppelin.com/contracts/3.x/erc721](https://docs.openzeppelin.com/contracts/3.x/erc721)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";

contract yooniNFT is ERC721URIStorage, Ownable {
    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;

  	// "MyNFTs"와 "MNFT"는 NFT 컨트랙트의 이름과 심볼이 될 것이다.
    constructor() public ERC721("yooniNFT", "YN") {}

    function mintNFT(string memory tokenURI)
        public onlyOwner
        returns (uint256)
    {
        _tokenIds.increment();

        uint256 newItemId = _tokenIds.current();
        _mint(msg.sender, newItemId);
        _setTokenURI(newItemId, tokenURI);

        return newItemId;
    }
}



(3) migration 코드 작성

migrations 폴더 내의 1_initial_migration.js 코드를 아래와 같이 수정하여 작성한 yooniNFT.sol 코드도 배포될 수 있도록 한다.

const Migrations = artifacts.require('Migrations');
const yooniNFT = artifacts.require('yooniNFT.sol'); // yooniNFT.sol 파일 추가

module.exports = function (deployer) {
	deployer.deploy(Migrations);
	deployer.deploy(yooniNFT); // yooniNFT를 배포에 추가
};



(4) truffle로 ropsten에 배포하기

프로젝트 루트 디렉토리에서 터미널을 열고 truffle을 통해 배포를 진행한다. ropsten 네트워크와 상호작용해야 하기 때문에 시간이 걸릴 수 있다.

truffle migrate --compile-all --network ropsten

성공적으로 배포되었다면 터미널에 아래와 같이 뜰 것이다.


etherscan에서의 컨트랙트 코드 검증을 위해 truffle-plugin-verify를 실행한다. 플러그인을 제대로 실행하면 etherscan에서 컨트랙트 코드를 verify & publish 하지 않아도 코드가 자동적으로 검증되어 올라가 있다.

truffle run verify yooniNFTs --network ropsten


컨트랙트가 잘 배포되었는지 확인하기 위해 truffle-console에 진입해 배포된 컨트랙트의 인스턴스를 얻어 name과 symbol을 확인한다.

truffle console --network ropsten

name과 symbol을 잘 받아오고 있음을 확인할 수 있다. 배포가 잘 되었다는 뜻이다! 🥳




🖥 동작 확인

(1) truffle에서 NFT 발행

컨트랙트가 잘 배포되었으니, 이제 직접 NFT를 발행해 보려고 한다. 민팅 함수를 실행하기 위해서는 tokenURI가 필요하며 미리 NFT storage에서 tokenURI를 생성해두었다.


실습에 활용할 나의 NFT 이미지


truffle console에서 민팅 함수를 실행한다. 시간이 꽤 걸린다.

instance.mintNFT("ipfs://bafkreigvfb6bxdp6qon2gpps2qsaeplsgizomaken4ig76awufzf2zahc4")

민팅이 성공적으로 되면, 아래와 같이 트랜잭션 정보가 터미널에 뜨게 된다.



(2) etherscan에서 NFT 발행

새로운 tokenURI를 생성하고, etherscan에서 두 번째 NFT를 발행해 보았다.

NFT가 성공적으로 발행되어 tokenId를 통해 생성된 NFT의 tokenURI를 확인할 수 있다.




✏️ 개발 회고

ERC-721 표준 코드가 이미 OpenZeppelin 라이브러리에 있으므로 그대로 끌어와 배포하면 되지 않을까 간단하게 생각했었지만, 의외로 신경써주어야 할 부분들이 꽤 있었고 그런 부분들을 해결하면서 많이 배운 것 같다.

우선, 기존에 truffle로는 ganache 환경으로 실습을 진행했던 것과 달리 ropsten 테스트넷을 활용했다. ropsten 네트워크로 환경을 맞춰주기 위해서는 @truffle/hdwallet-provider 라이브러리, Infura API KEY, 메타마스크 계정 니모닉 코드를 사용해야 했다.

그리고 truffle에서 ropsten network로 컨트랙트를 배포할 때, OpenZeppelin과 같은 라이브러리를 상속받은 경우 etherscan에 코드 검증(verify & publish)에서 에러가 뜬다. 구글링 결과 라이브러리까지 포함된 단일 병합 코드를 업로드해야 한다고 하는데 사실 무슨 말인지 이해가 잘 가지 않았고(🥲) 다행히 이런 상황에서 자동으로 코드를 verify & publish 해주는 truffle-plugin-verify 라이브러리를 알게 되어 etherscan에 코드를 잘 올릴 수 있었다. (truffle이 아닌 remix에서 배포를 진행했다면 라이브러리를 사용한 컨트랙트여도 에러 없이 코드가 잘 올라간다.)

또한 NFT 엔드포인트 URI를 생성하기 위해 NFT stroage라는 서비스를 이용했는데, 간단한 제작을 위해 단순히 이미지만 업로드하여 URI를 받아와서 사용했다. 하지만 실제 tokenURI는 NFT의 메타데이터 정보가 체계적인 방식으로 저장되어 있기 때문에, 메타데이터를 포함시키기 위한 추가적인 학습이 필요할 듯 하다.



📌 Reference
https://forum.openzeppelin.com/t/how-to-verify-with-hardhat-or-truffle-a-smart-contract-using-openzeppelin-contracts/4119

profile
멋쟁이 코린이

0개의 댓글