[TIL] DAY25~26 NFT 제작

박동우·2023년 4월 24일

블록체인 스쿨 3기

목록 보기
25/72

[TIL] 2023-04-20 DAY25
2023-04-24 DAY26


NFT 제작

GIT 주소 : https://github.com/dowoo303/web3_metamask
https://github.com/dowoo303/layers_nft_hashLips


오늘 배운것들

  1. NFT 발행해보기
  2. 메타마스크 프론트엔드 학습(잔액조회, NFT민팅)
  3. 각 파츠별 그림을 이용하여 조합한 NFT를 제작 후 민팅해보기

각종 팁

  1. 값이 배열이면 뒤에 [0] 이런식으로 값을 담으면 됨.
  2. 각종 주소는 ?앞까지만
  3. 읽을 때는 call
    보낼 때는 send

회고



DAY25 - NFT 발행

시작

사이트모음

NFT 생성 사이트(피나타): https://www.pinata.cloud/
메타데이터 설명서: https://docs.opensea.io/docs/metadata-standards
Remix: https://remix.ethereum.org/#lang=en&optimize=false&runs=200&evmVersion=null&version=soljson-v0.8.18+commit.87f61d96.js
-> 어제처럼 리믹스에서 새 파일에 코드 붙여넣고 컴파일 후 발행해보기

1. 피나타에 사진과 json 등록

2. 리믹스에 NFT 코드 입력

// SPDX-License-Identifier: MIT
 
pragma solidity ^0.8.18;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";

contract MintNft is ERC721Enumerable {
    mapping(uint => string) public metadataUri;
    uint public totalNft;

    constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {}

    function mintNft(string memory _metadataUri) public {
        uint tokenId = totalSupply() + 1;

        _mint(msg.sender, tokenId);
        
        metadataUri[tokenId] = _metadataUri;
    }

    function tokenURI(uint _tokenId) public override view returns(string memory) {
        return metadataUri[_tokenId];
    }
}


-> ENVIRONMENT를 인젝트 프로바이더로 변경하고 밑에 DEPLOY에서 이름과 심볼 설정 후 디플로이

민팅


-> 위 pinata에서 올린 json링크(메타데이터주소 ?전까지)(사진링크아님)를 mintNft에 넣어주고 민팅


-> 오픈씨에서 확인 가능 !



메타마스크를 이용한 사이트 제작

시작

사이트 모음

메마 가이드: https://docs.metamask.io/wallet/get-started/detect-metamask/
Web3 js: https://web3js.readthedocs.io/en/v1.8.2/

코드

import { useEffect, useState } from "react";

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

  // 메타마스크 연결
  const onClickAccount = async () => {
    try {
      const accounts = await window.ethereum.request({
        method: "eth_requestAccounts",
      });
      setAccount(accounts[0]);
    } catch (error) {
      console.error(error);
    }
  };

  const onClickLogOut = () => {
    setAccount("");
  };

  return (
    <div className="bg-red-100 min-h-screen flex justify-center items-center">
      {account ? (
        <div className="text-main font-semibold">
          {account.substring(0, 4)}...{account.substring(account.length - 4)}
          <button className="ml-4 btn-style" onClick={onClickLogOut}>
            로그아웃
          </button>
        </div>
      ) : (
        <button className="btn-style" onClick={onClickAccount}>
          <img
            className="w-12"
            src={`${process.env.PUBLIC_URL}/images/metamask.png`}
          />
        </button>
      )}
    </div>
  );
}

export default App;

-> 메타마스크 연결 및 로그아웃 구현

Web3 js

개념

이더리움과 상호작용할 수 있는 라이브러리
설치명령어 npm i web3


-> ABI 복사 후 web.config.js에 붙여넣기

코드

import { useEffect, useState } from "react";
import Web3 from "web3";
import {
  NFT_ABI,
  NFT_CONTRACT_ADDRESS,
  TOKEN_ABI,
  TOKEN_CONTRACT_ADDRESS,
} from "./web3.config";

// 프로바이더 추가 -> 마치 메마에서 다른 생태계 네트워크 추가하는 것과 같음
const web3 = new Web3("https://rpc-mumbai.maticvigil.com");
const contract = new web3.eth.Contract(TOKEN_ABI, TOKEN_CONTRACT_ADDRESS); // ABI와 컨트랙트 주소 연결
const NFTcontract = new web3.eth.Contract(NFT_ABI, NFT_CONTRACT_ADDRESS); // ABI와 컨트랙트 주소 연결

function App() {
  const [account, setAccount] = useState("");
  const [mybalance, setMybalance] = useState();

  // 메타마스크 연결
  const onClickAccount = async () => {
    try {
      const accounts = await window.ethereum.request({
        method: "eth_requestAccounts",
      });
      setAccount(accounts[0]);
    } catch (error) {
      console.error(error);
    }
  };

  const onClickLogOut = () => {
    setAccount("");
  };

  // 잔액조회
  const onClickBalance = async () => {
    try {
      if (!account || !contract) return;

      // 콜 함수
      const balance = await contract.methods.balanceOf(account).call();
      const totalSupply = await contract.methods.totalSupply().call();

      console.log(totalSupply, web3.utils.fromWei(totalSupply));
      setMybalance(web3.utils.fromWei(balance)); // 10^18 나누기 (/ 10**18)
    } catch (error) {
      console.error(error);
    }
  };

  // 민팅
  const onClickMint = async () => {
    try {
      const result = await NFTcontract.methods
        .mintNft(
          "https://gateway.pinata.cloud/ipfs/QmZ5ynCXHF5LyFwehgMxQQuxrq3x1hs7zcgo1bQ2QsRCmH"
        )
        .send({
          from: account,
        });

      console.log(result);
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <div className="bg-red-100 min-h-screen flex justify-center items-center">
      {account ? (
        <div>
          <div className="text-main font-semibold">
            {account.substring(0, 4)}...{account.substring(account.length - 4)}
            <button className="ml-4 btn-style" onClick={onClickLogOut}>
              로그아웃
            </button>
          </div>
          <div>
            <button className="btn-style m-8" onClick={onClickBalance}>
              잔액 조회
            </button>
            {mybalance && (
              <div className="flex items-center">{mybalance} tMatic</div>
            )}
          </div>
          <div className="flex items-center mt-4">
            <button className="m-8 btn-style" onClick={onClickMint}>
              민팅
            </button>
          </div>
        </div>
      ) : (
        <button className="btn-style" onClick={onClickAccount}>
          <img
            className="w-12"
            src={`${process.env.PUBLIC_URL}/images/metamask.png`}
          />
        </button>
      )}
    </div>
  );
}

export default App;

-> 잔액조회까지 완료 ! 위 리믹스에서 진행했던 민팅을 구현하려다 오류나서 다음날로


DAY 26

시작

DAY 25에 이어서 시작
const web3 = new Web3(window.ethereum); 코드 변경

-> 위 기능들(balanceOf, tokenOfOwnerByIndex 등)을 코드로 구현할 예정

코드

import axios from "axios";
import { useEffect, useState } from "react";
import Web3 from "web3";
import {
  NFT_ABI,
  NFT_CONTRACT_ADDRESS,
  TOKEN_ABI,
  TOKEN_CONTRACT_ADDRESS,
} from "./web3.config";

// 프로바이더 추가 -> 마치 메마에서 다른 생태계 네트워크 추가하는 것과 같음
const web3 = new Web3(window.ethereum);
const contract = new web3.eth.Contract(TOKEN_ABI, TOKEN_CONTRACT_ADDRESS); // ABI와 컨트랙트 주소 연결
const NFTcontract = new web3.eth.Contract(NFT_ABI, NFT_CONTRACT_ADDRESS); // ABI와 컨트랙트 주소 연결

function App() {
  const [account, setAccount] = useState("");
  const [mybalance, setMybalance] = useState();
  const [nftMetadata, setNftMetadata] = useState();

  // 메타마스크 연결
  const onClickAccount = async () => {
    try {
      const accounts = await window.ethereum.request({
        method: "eth_requestAccounts",
      });
      setAccount(accounts[0]);
    } catch (error) {
      console.error(error);
    }
  };

  const onClickLogOut = () => {
    setAccount("");
  };

  // 잔액조회
  const onClickBalance = async () => {
    try {
      if (!account || !contract) return;

      // 콜 함수
      const balance = await contract.methods.balanceOf(account).call();
      const totalSupply = await contract.methods.totalSupply().call();

      console.log(totalSupply, web3.utils.fromWei(totalSupply));
      setMybalance(web3.utils.fromWei(balance)); // 10^18 나누기 (/ 10**18)
    } catch (error) {
      console.error(error);
    }
  };

  // 민팅
  const onClickMint = async () => {
    try {
      const uri =
        "https://gateway.pinata.cloud/ipfs/QmXV6PgzzfBJxvhfp6PeXB7H7XktPCBcKCuXJr1uUvndwe";
		// NFT mint
      const result = await NFTcontract.methods.mintNft(uri).send({
        from: account,
      });

      if (!result.status) return;

      // // 이 지갑이 갖고있는 nft갯수 조회
      // const balanceOf = await NFTcontract.methods.balanceOf(account).call();
      // // 문자열로 들어옴

      // // 현재까지 발행된 NFT 갯수 조회
      // const tokenOfOwnerByIndex = await NFTcontract.methods
      //   .tokenOfOwnerByIndex(account, parseInt(balanceOf) - 1)
      //   .call();

      // // 방금 생성한 NFT의 메타데이터(json)을 가져옴 (사실 mintNft 와 같음)
      // const tokenURI = await NFTcontract.methods
      //   .tokenURI(tokenOfOwnerByIndex)
      //   .call();

      // // axios를 통해 메타데이터를 response에 저장
      // const response = await axios.get(tokenURI);
      const response = await axios.get(uri);

      setNftMetadata(response.data);
    } catch (error) {
      console.error(error);
    }
  };

  return (
    <div className="bg-red-100 min-h-screen flex justify-center items-center">
      {account ? (
        <div>
          <div className="text-main font-semibold">
            {account.substring(0, 4)}...{account.substring(account.length - 4)}
            <button className="ml-4 btn-style" onClick={onClickLogOut}>
              로그아웃
            </button>
          </div>
          <div>
            <button className="btn-style m-8" onClick={onClickBalance}>
              잔액 조회
            </button>
            {mybalance && (
              <div className="flex items-center">{mybalance} tMatic</div>
            )}
          </div>
          <div className="flex items-center mt-4">
            {nftMetadata && (
              <div className="m-4">
                <img src={nftMetadata.image} alt="NFT" />
                <div>Name: {nftMetadata.name}</div>
                <div>description: {nftMetadata.description}</div>
                <div>
                  {nftMetadata.attributes.map((v, i) => {
                    return (
                      <div key={i}>
                        <span>{v.trait_type} : </span>
                        <span>{v.value}</span>
                      </div>
                    );
                  })}
                </div>
              </div>
            )}
            <button className="m-8 btn-style" onClick={onClickMint}>
              민팅
            </button>
          </div>
        </div>
      ) : (
        <button className="btn-style" onClick={onClickAccount}>
          <img
            className="w-12"
            src={`${process.env.PUBLIC_URL}/images/metamask.png`}
          />
        </button>
      )}
    </div>
  );
}

export default App;

-> uri가 어짜피 존재하기 때문에 중간 주석처리한 부분들은 사실 코딩할 필요가 없었음.(연습삼아 진행)
nftMetadata안에 nft에 대한 정보들이 다 들어있으므로 이걸 이용하면 여러가지 정보 표현 가능.

결과


-> 민팅한 NFT 화면에 띄워보기 및 NFT 데이터 불러오기까지 완료



layer을 이용한 아트 제너레이팅

시작

HashLips를 이용하여 여러가지 layer들도 조합된NFT를 제작해본다.
-> 각 layer들은 pixilart나 포토샵등 다양한 곳에서 제작 후 이름을 orange#50 와 같은 형식으로 폴더별로 저장해준다.(#50의 뜻은 이 특성이 50% 확률로 나타난다는 뜻임)
-> 그렇게 만든 폴더들을 layers 폴더 하나에 넣은 후 HashLips에 사용

사이트

layer 그림 그리는 사이트: https://www.pixilart.com/draw
아트 제너레이팅: https://github.com/HashLips

제작 순서

  1. HashLips 가져 온 후 npm 설치
    npm install
    !윈도우 설치 에러시 -> npm update 먼저!

  2. layers 폴더 내가 만든 layers 폴더로 변경

  3. config.js 변경 -> layers마다 순서 생각해서 넣기
// If you have selected Solana then the collection starts from 0 automatically
const layerConfigurations = [
  {
    growEditionSizeTo: 50,
    layersOrder: [
      { name: "Background" },
      { name: "Face" },
      { name: "Hat" },
      { name: "Eyes" },
    ],
  },
];
  1. npm run build로 생성 -> growEditionSizeTo: 50 갯수만큼 build폴더에 생성됨

  2. pinata에 이미지 폴더만 폴더째 업로드

  3. 메타데이터 재정의(config.js)
const namePrefix = "BCS3rd";
const description = "First generating art";
const baseUri = "https://gateway.pinata.cloud/ipfs/QmUP8nRQRc7XQgGSzcwf4KXxCzw3Qtg8b6vbFpZftdKS4v";

-> baseUri는 4번에서 pinata에 등록한 이미지 경로


6. 5번에서 재정의한 메타데이터 업데이트
npm run update_info


7. 업데이트가 끝난 json폴더 또한 pinata에 업로드


8. 저번과 마찬가지로 리믹스에서 nft mint진행
단, 저번과 코드는 다르게 진행

리믹스 코드

// SPDX-License-Identifier: MIT
 
pragma solidity ^0.8.18;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";

contract MintNft is ERC721Enumerable {
    string public metadataUri;
    uint public totalNft;

    constructor(
        string memory _name, 
        string memory _symbol, 
        string memory _metadataUri, 
        uint _totalNft
    ) ERC721(_name, _symbol) {
        metadataUri = _metadataUri;
        totalNft = _totalNft;
    }

    function mintNft() public {
        require(totalNft > totalSupply(), "No more mint.");

        uint tokenId = totalSupply() + 1;

        _mint(msg.sender, tokenId);
    }

    function batchMint(uint _amount) public {
        for(uint i = 0; i < _amount; i++) {
            mintNft();
        }
    }

    function tokenURI(uint _tokenId) public override view returns(string memory) {
        return string(abi.encodePacked(metadataUri, '/', Strings.toString(_tokenId), '.json'));
    }

    function burnNFT(uint _tokenId) public {
        require(msg.sender == ownerOf(_tokenId), "Caller is not token owner.");

        _burn(_tokenId);
    }
}

-> 폴더째로 올렸기 때문에 코드가 저번과는 약간 다름 + burn기능 추가


-> deploy (메타데이터URI는 위 7번에서 올린 json 경로)


-> batchMint로 한번에 민팅


결과


layer를 각 조합해 자동으로 만든 NFT 6개 민팅 완료 !

profile
HELLO!

0개의 댓글