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에서 가져온다.
그후 그 값을 이용하여 새로운 NFT를 만든다.
그후 랜덤한 ID값을 생성해서 그러한 ID를 가진 NFT가 있는지를 확인한다.
이떄 없는것이 옳게 된 것이다.
나도 아직은 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을 통해서 개발을 하게 된다면 이러한 로직을 참고해 보는것은 많이 도움이 될 것이라고 생각을 한다!!
계속 공부해 나가야 겠다!! 재밌다!!