ABOUT ME

-

Today
-
Total
-
  • NFT 블록체인 마켓 앱 만들기 🔥 기능 구현
    Study/Blockchain 2022. 3. 23. 19:36
    반응형
    NFT 블록체인 마켓 앱 데모 영상

     

     

    NFT 마켓 앱을 구현하기 위한 핵심 기능은 아래와 같다.

    1. Klip 지갑 연동
    2. Klip 지갑 주소 조회
    3. KLAY 잔고 조회
    4. NFT 조회
    5. NFT 발행
    6. NFT 판매
    7. NFT 구매

     

    1. Klip 지갑 연동

    Klip 지갑을 연동하고 나면 BApp과 상호작용을 할 수 있다. 연동을 위해서 Klip App2App API를 사용할 것이다.

    Prepare 스텝에서 App2App API 요청을 위한 Request key를 발급받고, Request 스텝에서 Klip을 실행하고, 마지막 Result 스텝에서 요청에 대한 결과를 확인할 수 있다.

     

    [Step 1] Prepare

     

    가장 먼저 해야 하는 것은 Prepare API를 호출해서 request key를 발급받는 일이다.

    body에는 필요한 데이터를 담아서 전달하면 되는데, 아래와 같이 요청을 보낼 수 있다.

    response로 담겨 오는 request_key는 Request, Result API 호출 시에 사용할 수 있다.

    axios.post("https://a2a-api.klipwallet.com/v2/a2a/prepare", {
          bapp: {
            name: APP_NAME,
          },
          type: "auth",
        })

     

    [Step 2] Request

     

    이 단계에서는 PC, Mobile에 따라 요청 방법이 나뉜다. PC의 경우 QR code로 요청을 진행하고, Mobile은 Deep Link로 진행한다.

    Deep Link도 iOS, android 두 가지로 나뉘는데, 테스트 결과 iOS 링크로 안드로이드도 가능한 것으로 파악되긴 했다.

    어쨌든 Request Url 파라미터에는 앞서 발급받은 request key를 삽입하면 된다.

    const getKlipAccessUrl = (method, request_key) => {
      if (method === "QR") {
        return `https://klipwallet.com/?target=/a2a?request_key=${request_key}`;
      }
      return `kakaotalk://klipwallet/open?url=https://klipwallet.com/?target=/a2a?request_key=${request_key}`;
    };

     

    [Step 3] Result

     

    Request API가 성공적으로 결과를 받아오게 되면 Result API를 호출해서 결괏값을 받아볼 수 있다.

    파라미터로 request key를 삽입하면 된다.

    axios.get(`https://a2a-api.klipwallet.com/v2/a2a/result?request_key=${request_key}`)

     

    이 3단계를 거치면 Klip을 연동해서 스마트 컨트랙트를 실행시킬 수 있다.

     


    2. Klip 지갑 주소 조회

    마지막 단계 Result API를 호출하면 response data에 klaytn_address가 담겨있는데, 이게 지갑 주소이다.

    const A2P_API_PREPARE_URL = "https://a2a-api.klipwallet.com/v2/a2a/prepare";
    const APP_NAME = "KLAY_MARKET";
    const isMobile = /iPhone|iPad|iPod|Android|webOS|BlackBerry|Windows Phone/i.test(window.navigator.userAgent)
    
    // QR code 또는 Deep Link url로 Request API 호출
    const getKlipAccessUrl = (method, request_key) => {
      if (method === "QR") {
        return `https://klipwallet.com/?target=/a2a?request_key=${request_key}`;
      }
      return `kakaotalk://klipwallet/open?url=https://klipwallet.com/?target=/a2a?request_key=${request_key}`;
    };
    
    
    // Klip 주소값 가져오기
    export const getAddress = (setQrvalue, callback) => {
      //🔥 Prepare 단계
      axios
        .post(A2P_API_PREPARE_URL, {
          bapp: {
            name: APP_NAME,
          },
          type: "auth",
        })
        .then((response) => {
          const { request_key } = response.data;
          //🔥 Request 단계
          if (isMobile) {
            window.location.href = getKlipAccessUrl("deeplink", request_key);
          } else {
            setQrvalue(getKlipAccessUrl("QR", request_key));
          }
          let timerId = setInterval(() => {
            //🔥 Result 단계
            axios
              .get(
                `https://a2a-api.klipwallet.com/v2/a2a/result?request_key=${request_key}`
              )
              .then((res) => {
                if (res.data.result) {
                  console.log(`[Result] ${JSON.stringify(res.data.result)}`);
                  callback(res.data.result.klaytn_address);
                  clearInterval(timerId);
                  setQrvalue("DEFAULT");
                }
              });
          }, 1000);
        });
    };

     


    3. KLAY 잔고 조회

    caver-js는 Klaytn 노드와 상호작용할 수 있도록 하는 자바스크립트 API 라이브러리이다. 원래 Klaytn 블록체인을 사용하려면 Klaytn Endpoint Node(KEN)를 직접 운영해야 하지만, KAS Node API를 사용하면 노드 운영 필요 없이 Klaytn에 접속해 블록체인 플랫폼을 바로 활용할 수 있다.

    따라서 KAS Node API를 활용해서 caver 객체를 생성하고 이를 통해 스마트 컨트랙트와 상호작용해야 한다.

    KAS Console에서 발급받은 AccessKey ID, Secret AccessKey를 이용하여 Authorization 헤더를 생성하여 호출한다.

    chain id에는 Klaytn 네트워크 체인 ID (1001 or 8217)를 넣어주면 된다. 

     

    KAS Node API로 caver 객체 생성

     
    // KAS API 호출 시 필요한 헤더
    const option = {
      headers: [
        {
          name: 'Authorization',
          value: 'Basic ' + Buffer.from(ACCESS_KEY_ID + ':' + SECRET_ACCESS_KEY).toString('base64'),
        },
        { name: 'x-chain-id', value: CHAIN_ID },
      ],
    };
    
    // KAS API 사용을 위한 객체 생성
    const caver = new Caver(new Caver.providers.HttpProvider('https://node-api.klaytnapi.com/v1/klaytn', option));

     

    caver 객체를 활용하여 KLAY 보유량 조회

    caver.rpc.klay.getBalance 메서드를 사용해서 klaytn 계정 주소의 klay 잔고를 조회할 수 있다.

    // 지갑의 잔고 조회하기
    export const getBalance = (address) => {
      return caver.rpc.klay.getBalance(address).then((response) => {
      //response인 hex 문자열을 문자열로 된 숫자로 바꾸고, PEB단위에서 KLAY단위로 변환
        const balance = caver.utils.convertFromPeb(
          caver.utils.hexToNumberString(response)
        );
        return balance;
      });
    };

     

    4. NFT 조회

    KIP17 토큰 표준에 따라서 컨트랙트를 작성했다는 가정하에 NFT 리스트를 조회하는 방법은 2가지가 있다.

    - NFT 리스트 조회 방법 (1)
    1. balanceOf 함수를 사용해서 전체 NFT 개수를 가져온다.
    2. 전체 NFT 개수만큼 반복문을 돌면서 tokenOfOwnerByIndex 함수를 이용하여 tokenId를 하나씩 가져오고 tokenIds 배열에 담는다.
    3. tokenURI함수를 이용해 앞에서 담아둔 tokenIds 배열을 돌면서 tokenURI를 하나씩 가져온다.

    - NFT 리스트 조회 방법 (2)
    KAS API 중에 Token 관련 API를 사용한다.
    https://refs.klaytnapi.com/ko/th/latest#operation/getNftsByOwnerAddress

     

    아래에서는 첫 번째 방법을 활용해서 NFT 리스트를 조회한다.

    우선 토큰 컨트랙트 ABI와 배포된 토큰 컨트랙트 주소로 caver.contract 객체를 만들어서 스마트 컨트랙트와 상호작용할 수 있도록 한다.

    그리고 컨트랙트에 작성된 메서드를 활용해서 NFT 리스트를 조회하는 함수를 구현한다.

     

    // 참조 ABI와 스마트컨트랙트 주소를 통해 스마트컨트랙트 연동
    const NFTContract = new caver.contract(KIP17ABI, NFT_CONTRACT_ADDRESS);
    
    // NFT 리스트 조회
    export const fetchCardsOf = async (address) => {
      // Fetch Balance
      const balance = await NFTContract.methods.balanceOf(address).call();
    
      // Fetch Token IDs
      const tokenIds = [];
      for (let i = 0; i < balance; i++) {
        const id = await NFTContract.methods.tokenOfOwnerByIndex(address, i).call();
        tokenIds.push(id);
      }
      
      // Fetch Token URIs
      const tokenUris = [];
      for (let i = 0; i < balance; i++) {
        const metadataUrl = await NFTContract.methods.tokenURI(tokenIds[i]).call(); // KAS 메타데이터 response.uri
        const response = await axios.get(metadataUrl) // JSON 형식 메타데이터가 들어옴
        const uriJSON = response.data
        tokenUris.push(uriJSON.image);
      }
      const nfts = [];
      for (let i = 0; i < balance; i++) {
        nfts.push({ uri: tokenUris[i], id: tokenIds[i] });
      }
    
      return nfts;
    };

     


    5. NFT 발행

     

    Klip App2App API 요청을 통해 스마트 컨트랙트를  실행할 수 있다.

    type을 execute_contract로 설정하고 transaction에 해당하는 데이터들을 올바르게 넣는다.

    스마트 컨트랙트를 실행하는 함수는 NFT 발행, 판매, 구매 기능에 공통으로 사용되기 때문에 공통 모듈 함수로 생성해준다.

     

    export const executeContract = (txTo,functionJSON,value,params,setQrvalue,callback) => {
      //🔥 Prepare 단계
      axios
        .post(A2P_API_PREPARE_URL, {
          bapp: {
            name: APP_NAME,
          },
          type: "execute_contract",
          transaction: {
            to: txTo, //실행할 스마트 컨트랙트 주소
            abi: functionJSON,//실행할 컨트랙트 함수 정보
            value: value, //해당 컨트랙트에 전송할 KLAY (단위는 peb)
            params: params, //실행할 컨트랙트 함수에 필요한 파라미터
          },
        })
        .then((response) => {
          const { request_key } = response.data;
          //🔥 Request 단계
          if (isMobile) {
            window.location.href = getKlipAccessUrl("deeplink", request_key);
          } else {
            setQrvalue(getKlipAccessUrl("QR", request_key));
          }
          
          //🔥 Result 단계
          let timerId = setInterval(() => {
            axios
              .get(
                `https://a2a-api.klipwallet.com/v2/a2a/result?request_key=${request_key}`
              )
              .then((res) => {
                if (res.data.result) {
                  callback(res.data.result);
                  clearInterval(timerId);
                  setQrvalue("DEFAULT");
                }
              });
          }, 1000);
        });
    };

     

    NFT를 발행하기 위해서는 스마트 컨트랙트에 mintWithTokenURI라는 메서드를 사용해야 한다.

    따라서 ABI 파일에서 해당 메서드에 해당하는 json 데이터를 가져오고, 필요한 파라미터를 함께 넣어서 호출해준다.

     

    export const mintCardWithURI = async (toAddress,tokenId,uri,setQrvalue,callback) => {
      const functionJson = '{ "constant": false, "inputs": [ { "name": "to", "type": "address" }, { "name": "tokenId", "type": "uint256" }, { "name": "tokenURI", "type": "string" } ], "name": "mintWithTokenURI", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }';
      
      executeContract(
        NFT_CONTRACT_ADDRESS,
        functionJson,
        "0",
        `[\"${toAddress}\",\"${tokenId}\",\"${uri}\"]`,
        setQrvalue,
        callback
      );
    };

     


    6. NFT 판매

     

    NFT를 판매한다는 것은 NFT 소유권을 넘긴다는 의미와 같다. 우선 NFT 마켓을 통해 판매하려면 마켓이 NFT를 가지고 있어야 한다.

    따라서 소유권을 NFT 마켓 컨트랙트 계정으로 넘겨주면 된다.

    safeTransferFrom 메서드를 사용해서 소유권을 마켓 컨트랙트 계정으로 넘긴다.

     

    export const listingCard = async (fromAddress,tokenId,setQrvalue,callback) => {
      const functionJson = '{ "constant": false, "inputs": [ { "name": "from", "type": "address" }, { "name": "to", "type": "address" }, { "name": "tokenId", "type": "uint256" } ], "name": "safeTransferFrom", "outputs": [], "payable": false, "stateMutability": "nonpayable", "type": "function" }';
      
      executeContract(
        NFT_CONTRACT_ADDRESS,
        functionJson,
        "0",
        `[\"${fromAddress}\",\"${MARKET_CONTRACT_ADDRESS}\",\"${tokenId}\"]`,
        setQrvalue,
        callback
      );
    };

     


     

    7. NFT 구매

     

    NFT를 구매하려면 NFTMarket 스마트 컨트랙트의 buyNFT 메서드를 사용하면 된다.

    현재 NFTMarket 스마트 컨트랙트 내에서 NFT 값을 0.01 KLAY로 고정해놨기 때문에, 컨트랙트를 실행할 때 0.01 KLAY를 함께 전송해야 구매할 수 있다.

    buyNFT를 실행하면 토큰 컨트랙트의 safeTransferFrom 메서드가 실행되면서 NFT 소유권이 마켓에서 구매자에게 넘어간다.

     

    export const buyCard = async (tokenId, setQrvalue, callback) => {
      const functionJson = '{ "constant": false, "inputs": [ { "name": "tokenId", "type": "uint256" }, { "name": "NFTAddress", "type": "address" } ], "name": "buyNFT", "outputs": [ { "name": "", "type": "bool" } ], "payable": true, "stateMutability": "payable", "type": "function" }';
      executeContract(
        MARKET_CONTRACT_ADDRESS,
        functionJson,
        "10000000000000000",
        `[\"${tokenId}\",\"${NFT_CONTRACT_ADDRESS}\"]`,
        setQrvalue,
        callback
      );
    };

     

     

     

     

    NFT Market BApp을 통해 발행된 NFT는 카카오톡 Klip 에서 확인할 수 있다.

    원래는 Klip 지갑에서 발행한 NFT를 보려면 여러 절차를 거쳐야하는데, 수강생들을 대상으로 멋쟁이사자처럼 컨트랙트를 통해 발행한 NFT는 Klip에서 볼 수 있도록 처리해줬다!

     

     


    위에서 나열한 모든 기능을 구현한 소스코드는 깃허브에 올려놨다.

    멋쟁이사자처럼 측에서 제공한 소스코드와는 조금 다르게 구현해놨는데, 어떤 걸 참고하든 상관은 없을 것 같다.

     

    - 내 소스코드

    https://github.com/anxiubin/NFT-Market-BApp

    - 멋쟁이사자처럼 소스코드

    https://github.com/genie19197/recording-klay-market

    https://github.com/genie19197/lecture-klay-market

     

     

     NFT 블록체인 마켓 앱 만들기 with 그라운드X 시리즈 

    1. 블록체인이란?

    2. 클레이튼이란?

    3. NFT란?

    4. Smat Contract & Solidity

    5. Klaytn 개발환경 세팅

    6. BApp 설계

    7. BApp 기능 구현 

     

    반응형

    댓글