KAS Docs - 7 [ KAS기반 BApp예시 서버 분석]

Lumi·2021년 12월 1일
1

Klaytn

목록 보기
8/10
post-thumbnail

KAS Docs에서 다루고 있는 예시 코드를 분석함으로써 실제로 어떻게 사용이 되는지를 알아보고자 한다.

명품을 등록하는 영수증을 관리하는 애플리케이션 코드이다.

  • 내가 생각하는 부분으로 조금 수정을 하였고 직관적으로 볼 수가 있게 좀더 수정을 하였다.

틀릴수도 있다.

🔥 개요

전체적인 흐름도는 이와 같다.

[1] 가게 주인 개인키를 caver지갑에 등록 - 프론트 엔드

[2] 등록할 명품 정보 입력, 명품 정보를 백엔드 서버에 전송 - 프론트 엔드

[3] 프론트엔드에서 받은 명품 정보를 바탕으로 KIP-17컨트랙트 배포 - 백엔드

[4] 배포된 KIP-17컨트랜트에 대한 정보를 DB에 저장 - 백엔드

🔥 명품 생성 하기

app.post("/api/products", upload.single("image"), async (req, res) => {
  
  // 개인키를 바탕으로 keyring을 생성합니다.
  const keyring = caver.wallet.keyring.createFromPrivateKey(
    req.body.sellerPrivateKey
  );

  // 만약 wallet에 키링이 없다면 추가를 해준다
  if (!caver.wallet.getKeyring(keyring.address)) {
    caver.wallet.add(singleKeyRing);
  }

  // 넘어온 데이터를 바탕으로 새로운 KIP-17을 배포(=새로운 명품 등록)합니다.
  const kip17 = await caver.kct.kip17.deploy(
    {
      name: req.body.name,
      symbol: req.body.symbol,
    },
    keyring.address
  );

  // 이후 배포한 명품(NFT를 DB에 저장한다.)
  let sql =
    "INSERT INTO TEST.PRODUCT (name, symbol, contractAddr, image, registeredDate, isDeleted) VALUES (?, ?, ?, ?, now(), 0)";

  let image = "/image/" + req.file.filename;
  let name = req.body.name;
  let symbol = req.body.symbol;
  let params = [name, symbol, kip17.options.address, image];

  connection.query(sql, params, (err, rows) => {
    res.send(rows);
    if (err) console.log(err);
  });

  console.log("명품 생성 완료 및 NFT를 DB에 등록 완료!!");
});

기본적으로 값을 받고 지갑에 키링을 추가한뒤에 NFT를 생성해 그 값을 mysql에 저장을 하는 코드이다.

🔥 명품 판매시 손님에게 줄 영수증 발행

새롭게 출시되는 명품을 앞서 블록체인에 등록을 하였고 이제 명품이 판매가 되면 손님에게 영수증을 발급해야 한다.

블록체인 상에서 영수증을 발급하는 것은 새로운 토큰을 손님의 EOA로 발행하는 것과 같다.

caver-js를 통해서 KIP-17을 발급하려면 보통은 가게 주인의 개인키가 필요하다.

  • 실제 서비스에서는 서버에서 가게 주인의 개인키를 보관하는 것이 좋다.
app.post("/api/receipts", async (req, res) => {

  // 개인키를 바탕으로 keyring을 생성합니다.
  const keyring = caver.wallet.keyring.createFromPrivateKey(
    req.body.sellerPrivateKey
  );

  // wallet에 keyring이 추가되지 않은 경우에만 keyring을 추가합니다.
  if (!caver.wallet.getKeyring(keyring.address)) {
    caver.wallet.add(singleKeyRing);
  }

  // 넘어온 productID와 일치하는 명품이 등록되어 있는지 조회합니다.
  connection.query(
    "SELECT * FROM TEST.PRODUCT WHERE isDeleted = 0 AND id = ?",
    req.body.productID,
    async (err, rows, fields) => {
    
      // 넘어온 productID에 대응되는 컨트랙트 주소입니다.
      contractAddr = rows[0]["contractAddr"];
      
      // 컨트랙트 주소 기반으로 KIP-17 오브젝트를 생성합니다.
      const kip17 = new caver.kct.kip17(contractAddr);
      // 새로 발행하는 토큰에 임의의 tokenId를 할당하기 위해 Math.random 사용 및 중복 여부를 체크합니다.
      minted = false;
      
      while (true) {
        randomTokenID = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER);
        try {
          owner = await kip17.ownerOf(randomTokenID);
        } catch (e) {
          // owner가 존재하지 않는 경우(=존재하지 않는 tokenID) 에러가 리턴됩니다.
          // 에러를 받으면 해당 tokenID로 토큰 생성이 가능합니다.
          // tokenURI에는 임의의 정보를 넣어줄 수 있습니다.
          // 본 예제에서는 임의의 sellerID와 productID를 json 형태로 저장합니다.
          // 토큰 이미지 URL이나 기타 정보를 tokenURI에 저장할 수 있습니다.

          tokenURI = JSON.stringify({
            sellerID: 0,
            productID: req.body.productID,
          });

          // KIP-17.mintWithTokenURI를 이용해서 새로운 토큰을 발행합니다.
          mintResult = await kip17.mintWithTokenURI(
            req.body.toAddr,
            randomTokenID,
            tokenURI,
            { from: keyring.address }
          );

          // 클레이튼 네트워크에 토큰이 발행된 다음, 생성된 토큰을 `receipt` 데이터베이스에 저장합니다.
          let sql =
            "INSERT INTO TEST.RECEIPT " +
            "(sellerID, productID, tokenID, tokenURI, contractAddr, fromAddr, toAddr, registeredDate, lastUpdatedDate, isDeleted)" +
            " VALUES (?, ?, ?, ?, ?, ?, ?, NOW(), NOW(), 0)";
          let params = [
            0,
            req.body.productID,
            randomTokenID,
            tokenURI,
            contractAddr,
            "0x0000000000000000000000000000000000000000",
            req.body.toAddr,
          ];

          connection.query(sql, params, (err, rows, fields) => {
            if (!err) {
              console.log("error while inserting a new receipt", "err", err);
            }
          });
          minted = true;
        }
        // 발급된 경우, 즉 randomTokenID를 갖는 토큰이 존재하지 않아서 새로운 토큰이 발급된 경우에만 break 합니다.
        // 발급되지 않은 경우에는 새로운 randomTokenID를 바탕으로 재시도합니다.
        if (minted) {
          break;
        }
      }
      res.send(rows);
    }
  );
  console.log("새로운 NFT를 통해서 영수증 발행 완료!");
});

일단 마찬가지로 키링을 생성하고 req를 통해서 들어온 id값과 isDeleted값이 0인 값을 sql구문을 통해서 DB에서 가져온다.

  • 그러면 그 값이 rows를 통해서 들어오게 된다.

그후 그 값을 이용하여 새로운 NFT를 만든다.

그후 랜덤한 ID값을 생성해서 그러한 ID를 가진 NFT가 있는지를 확인한다.

이떄 없는것이 옳게 된 것이다.

  • 랜덤한 ID값이기 떄문에

나도 아직은 caver에 익숙하지 않고 많이 아는 것은 아니라서 이런식으로 흐름이 진행된다는 것만 이해를 하였다.

🔥 발행한 영수증 전송

app.post("/api/receipts/send", async (req, res) => {

  // 개인키를 바탕으로 keyring을 생성합니다.
  let senderPrivateKey = req.body.customerPrivateKey;
  const senderKeyring = caver.wallet.keyring.createFromPrivateKey(
    senderPrivateKey
  );

  // wallet에 keyring이 추가되지 않은 경우에만 keyring을 추가합니다.
  if (!caver.wallet.getKeyring(senderKeyring.address)) {
    caver.wallet.add(senderKeyring);
  }
  let contractAddr = req.body.contractAddr;
  // receipt 테이블에서 fromAddr와 toAddr를 새로운 값으로 업데이트합니다.

  connection.query(
    "UPDATE TEST.RECEIPT SET fromAddr = ?, toAddr = ?, lastUpdatedDate=NOW() WHERE contractAddr=? AND tokenID=?",
    [
      senderKeyring.address,
      req.body.receiverAddr,
      contractAddr,
      req.body.tokenId,
    ],
    // receipt 테이블 업데이트 이후에 KIP-17을 새로운 주인에게 전송합니다.
    async (err, rows, fields) => {
      const kip17 = new caver.kct.kip17(contractAddr);
    
      transferResult = await kip17.transferFrom(
        senderKeyring.address,
        req.body.receiverAddr,
        req.body.tokenId,
        { from: senderKeyring.address, gas: 200000 }
      );
      
      res.send(transferResult);
    }
  );
});

앞서 새로 만든 토큰을 DB에 추가를 하였다면 그 추가된 토큰의 데이터를 업데이트 해주는 코드이다.

이떄 실질적으로 트랜잭션이 발생을 하게 되고 그러기 떄문에 가스비도 소요 된다는 것을 알 수가 있다.

🔥 후기

간략하게 어떠한 방향으로 로직이 작동을 하는지에 대해서만 다루어 보았다.

후에 만약 klaytn을 통해서 개발을 하게 된다면 이러한 로직을 참고해 보는것은 많이 도움이 될 것이라고 생각을 한다!!

계속 공부해 나가야 겠다!! 재밌다!!

profile
[기술 블로그가 아닌 하루하루 기록용 블로그]

0개의 댓글