🦁 [PROJECT LION] NFT 블록체인 마켓 앱 만들기 with 그라운드X 3기 - 챕터 5-4 회고


KIP-17

  • KIP-17은 ERC-721 토큰에서 파생되었다

  • ‘KIP-17’ 기반으로 발행된 토큰은 대체가 불가능하다
    👉 대체 불가능 토큰 (NFT, Non-Fungible Token)


NFT 발행하고 조회하기

-1. Klaytn IDE 에서 ‘KIP-17’ 스마트 컨트랙트 코드 배포(Deploy)

⭐ Klip 은 테스트넷(Baobab) 에서 실행을 못하기때문에
메인넷에서 사용할 실제 KLAY 가 필요하다 ❗

  • ‘KIP17Token’ 을 선택해서 컴파일한 후 ABI 복사

  • ‘KIP17Token’ 을 배포하고 나온 컨트랙트 주소 를 복사한다


-2. ‘KIP17Token’ 을 배포하고 나온 함수를 실행시켜 NFT 발행 (민팅)

mintWithTokenURI 함수 실행

to 👉 NFT 발행 지갑주소 (소유주)
tokenId 👉 발행하는 NFT 임의의 번호
tokenURI 👉 NFT의 정보 (이미지, 제목, 세부내용 등등)

저는 일단 이미지 2개를 준비해서
tokenId 100번, 101번 으로 발행시켜 보겠습니다
( https:// ~~ .png 로 준비했습니다 )


-3. NFT를 발행 했으면 조회해본다

  • balanceOf 👉 해당 주소에 몇개의 NFT를 보유중인지 개수 확인

  • ownerOf 👉 NFT의 TokenId 를 입력하면 누가 소유중인지 주소를 확인

  • tokenOfOwnerByIndex 👉 주소와, 배열의 몇번째 를 입력하면 TokenId 를 확인
    (총 2개를 발행했으니 배열에는 [100, 101] 이렇게 2개가 담겨있다
    👉 0번째 -> 100 / 1번째 -> 101

  • tokenURI 👉 NFT의 TokenId 를 입력하면 URI를 확인


-4. ‘ABI / 컨트랙트 주소’ 로 caver-js 를 통해 리액트와 연결한다

⭐ Klaytn IDE에서 조회를 했던 함수들을 리액트에서도 호출할 수 있다

조회를 할때는 지갑에 가지고있는 NFT의 이미지를 다 client 에 출력할 것이므로 여러과정을 거쳐야한다

  1. balanceOf 를 이용해 몇개의 NFT를 보유중인지 개수를 변수에 담는다
  2. 개수를 반복문을 돌려서 순차적으로 대입
  3. tokenOfOwnerByIndex 를 이용해 TokonId 추출
  4. tokenURI 를 이용해 tokenURI 추출
  5. tokenIdtokenURI 를 짝지어서 출력한다

-5. App.js 👉 client 에 출력하기 위한 코드입력

호출한 함수들을 useState 빈배열에 담아준다


-6. npm run start 로 실행시킨뒤 Klip 지갑 연결

QR코드로 Klip 지갑을 연결하고
버튼을 누르면 지갑에 있는 모든 NFT를 조회 할 수 있다


KIP-17 스마트컨트랙트 코드

KIP-17 스마트컨트랙트 코드


caver-js 를 통해 리액트와 연결하는 코드

UseCaver.js

import Caver from "caver-js";
import KIP17ABI from "../abi/KIP17TokenABI.json";
import {
  ACCESS_KEY_ID,
  SECRET_ACCESS_KEY,
  CHAIN_ID,
  COUNT_CONTRACT,
  KIP17,
} from "../constants/index";

const option = {
  // Klaytn Node API를 사용하기 위한 코드 (Docs에 나와있음)
  headers: [
    {
      name: "Authorization",
      value:
        "Basic " +
        Buffer.from(ACCESS_KEY_ID + ":" + SECRET_ACCESS_KEY).toString("base64"),
    },
    { name: "x-chain-id", value: CHAIN_ID },
  ],
};

const caver = new Caver(
  new Caver.providers.HttpProvider(
    "https://node-api.klaytnapi.com/v1/klaytn",
    option
  )
); // Klaytn Node API를 사용하기 위한 코드 (Docs에 나와있음)

const NFTContract = new caver.contract(KIP17ABI, KIP17);
// // ⭐ 개발자는 '스마트 컨트랙트 주소'와 'ABI'를 알면 'caver' 를 통해
// // ⭐ 스마트 컨트랙트를 생성하고 / 특정 함수를 실행할 수 있다
export const fetchCardsOf = async (address) => {
  const nftOf = await NFTContract.methods.balanceOf(address).call();
  // 스마트컨트랙트 배포후에 나오는 함수
  // 해당 주소에 몇개의 NFT를 보유중인지 개수를 표시하는 함수 balanceOf
  console.log(nftOf);

  const tokenIds = [];
  for (let i = 0; i < nftOf; i++) {
    const id = await NFTContract.methods.tokenOfOwnerByIndex(address, i).call();
    // 해당주소와 배열의 몇번째인지 숫자를 입력하면 tokenId 가 나온다
    tokenIds.push(id);
    // 빈배열에 반복문을 돌려서 tokenId를 push 한다
  }
  const tokenUris = [];
  for (let i = 0; i < nftOf; i++) {
    const uris = await NFTContract.methods.tokenURI(tokenIds[i]).call();
    // 해당 tokenId를 입력하면 tokenURI(이미지) 가 나온다
    tokenUris.push(uris);
    // 빈배열에 반복문을 돌려서 tokenURI를 push 한다
  }
  console.log(`${tokenIds}`);
  console.log(tokenUris);

  const nfts = [];
  for (let i = 0; i < nftOf; i++) {
    nfts.push({ id: tokenIds[i], uri: tokenUris[i] });
    // 빈배열에 반복문을 돌려서 객체타입으로 담는다
  }
  console.log(nfts);
  return nfts;
};

// 블록체인 노드에 직접적으로 콜하기 어려워서 KAS 를 사용
// KAS 는 우리가 블록체인에 접근하는걸 도와준다
// 스마트컨트랙트에 있는 데이터를 읽고 실행하는걸 도와준다
// 이 과정에서 Caver-js 를 쓰면 인간이 사용하는 코드를
// 스마트컨트랙트 블록체인이 이해할수 있는 코드로 변형해줘서
// Klaytn 노드와 상호작용할 수 있도록 도와준다

export const getBalance = (address) => {
  return caver.rpc.klay.getBalance(address).then((res) => {
    // 클레이 잔고를 확인 (다른코인이 있다면 klay말고 적으면 된다)
    const balance = caver.utils.convertFromPeb(
      // convertFromPeb -> 10 ** 18 로 클레이단위를 변경
      caver.utils.hexToNumberString(res)
      // 16진수문자열로 변경
    );
    console.log(balance);
    return balance;
  });
};

App.js 👉 client 에 출력하기 위한 코드

App.js

import "./App.css";
import "./market.css";
import { useState } from "react";
import QRCode from "qrcode.react";
import * as KlipAPI from "./api/UseKlip";
import { fetchCardsOf, getBalance } from "./api/UseCaver";
import { Alert, Container, Card } from "react-bootstrap";
import "bootstrap/dist/css/bootstrap.min.css";

const DEFAULT_QR_CODE = "DEFAULT"; // QR코드는 기본 DEFAULT 값
const DEFAULT_ADDRESS = "0x00000000000000000"; // 주소 기본값

function App() {
  const [nfts, setNfts] = useState([]);
  // 인자로 들어가는값은 tokenId, tokenUri
  const [myBalance, setMyBalance] = useState("0");
  const [myAddress, setMyAddress] = useState(DEFAULT_ADDRESS);
  const [qrvalue, setQrvalue] = useState(DEFAULT_QR_CODE);

  const fetchMyNFT = async () => {
    const nftList = await fetchCardsOf(
      "0x319229707F620F673a1261DCcCe4E239A71f3Bc0"
    ); // 해당 주소에 있는 nft 리스트
    setNfts(nftList);
    // 빈배열에 담아준다
  };
  const getUserData = () => {
    KlipAPI.getAddress(setQrvalue, async (address) => {
      setMyAddress(address);
      const balance = await getBalance(address);
      // UseCaver.js 에서 가져온 getBalance
      setMyBalance(balance);
    });
  };

  return (
    <div className="App">
      <div>
        <div> 지갑</div>
        {myAddress}
        <br />
        <Alert onClick={getUserData} variant={"balance"}>
          {myBalance}
        </Alert>
        <div className="container">
          {/* map을 이용하여 nfts 배열에서 index 순서대로 이미지 출력 */}
          {nfts.map((list, index) => (
            <Card.Img className="img-responsive" src={nfts[index].uri} />
          ))}
        </div>
      </div>
      <Container>
        <QRCode value={qrvalue} size={256} />
      </Container>
      <button onClick={fetchMyNFT}>NFT 가져오기</button>
    </div>
  );
}

export default App;