2023 5월 Klaytn DevMeet 컨퍼런스 - Metaverse Knowledge Kit
포스트
취소

2023 5월 Klaytn DevMeet 컨퍼런스 - Metaverse Knowledge Kit

sendSignedTrancastion


join


나는 개인적으로 많은 블록체인 중에서도 클레이튼을 좋아하고, 응원한다.
이미 블록체인 생태계가 탄탄하게 구성된 이유도 있지만, 대한민국의 블록체인 중 하나라는 것이 크다. (화이팅!)



이번 클레이튼 데브밋 컨퍼런스에서는 메타버스와 블록체인을 접목한 Metaverse Knowledge Kit를 주제로 하고 있다.



Metaverse Knowledge Kit

Klaytn Metaverse Knowledge Kit

클레이튼은 블록체인에서도 메타버스를 활용하는 방안에 대해 연구하고, 서비스를 출시하기 위해 노력하고 있다고 한다. 그에 대하여 위 Klaytn Metaverse Knowledge Kit을 보면 해당하는 기술 서비스를 살펴볼 수 있다.


Metaverse

메타버스는 “현실 세계의 제약없이 일하고, 사교하고, 게임을 하는 등의 활동을 할 수 있는 가상의 세계를 뜻한다.

게임에서 길드를 만들고, 커뮤니티를 생성해 사교활동을 하는 것에서 메타버스를 이해할 수 있다. 하지만 게임에 중점이 되어있는게 아닌 현실 세계를 본 떠 만든 가상의 세계라고 이해하는게 옳을 것이다.


gatherToen

메타버스로 유명한 게더타운에서 스터디도 많이 이루어지고 있다.


메타버스에도 관점에 따른 종류를 구분할 수 있다고 한다.

참고 : METAVERSE 얼마나 알고 계시나요?
kindOf

그 중에서도 클레이튼이 소개하는 비즈니스 관점의 메타버스가 눈에 띄었다.



Why Klaytn?

클레이튼에서는 메타버스 시장을 주목하고 있다. 이번 세션에서 스피커를 맡은 콜린님의 말을 빌리자면, “메타버스가 급 성장을 하는 것은 아니지만, 지속적으로 성장하고 있고, 잠재력이 있는 시장”이라고 한다.


메타버스 사용사례

  • 가상 사무실
  • 가상 이벤트
  • 가상 쇼핑
  • 교육



Metaverse on Blockchain

이러한 메타버스에 블록체인 기술을 응용하게 되면 일반적인 가상세계가 아닌 좀 더 특성있는 분야가 된다.


  • 탈 중앙성을 확보할 수 있다.
    • 블록체인의 제일 큰 특징인 탈 중앙성(떄에 따라 다르겠지만 ㅎ)을 접목시킬 수 있다는 것은 사용자에게 매력적인 요소이다. 메타버스에 탈중앙화가 된다면 진정한 거버넌스의 실현을 볼 수 있다고 본다.
    • 블록체인 데이터로 메타버스를 구현.
    • 탈 중앙화가 실현되기 위해서는 ‘불변하는 블록체인 데이터’가 필요하다.
  • 상호운용성
    • 블록체인은 지갑을 통해 자산 가치 이전 및 소유가 가능하기 떄문에 조금 더 활발한 가상세계를 만들 수 있을 것이다.
    • 오픈소스로 이루어져 있는 블록체인 기술을 이용한 메타버스 기능 구현할 수 있다.
    • 블록체인의 매력 중 하나는 많은 프로젝트들이 오픈소스로 이루어져 있다는 것이다. 이는 세계관 확장에 있어 더욱 가속화를 이루어줄 것이라 믿는다.



Metaverse in Klaytn

Klaytn은 메타버스 구성을 위한 블록체인 기술 제공으로 다음과 같은 역할을 할 수 있다고 한다.

  • 특징
    • 안정적인 가스 모델
    • 커스터 마이징 가능한 블록체인(사이드 체인) => TPS 조절 가능
    • 기본적으로 빠른 TPS를 통해 인터렉티브한 서비스를 제공할 수 있다.


  • Klaytn 제공
    • 메타버스 관련 Reference
      • 여러가지 Use case
      • Guide
    • SDKs
    • APIs


  • 구성
    • 스마트 컨트랙트
    • 분산 저장소
    • 탈 중앙화 오라클
      • Price Feeds
      • VRF
      • External API Calls
    • Gaming sdk
    • Service sdk
      • Bridge Starter Kit
      • Dex Starter Kit
    • 개발 도구
      • Wallet(ex. Kaikas…)
      • Caver-js

기존 클레이튼 개발 도구와 더불어 메타버스를 위한 기술 서비스가 추가된 것으로 보인다.


이번 컨퍼런스에서는 이 많은 기술 중에서도 Dynamic NFTs에 대한 소개가 중점적이었다.



dNFT(Dynamic NFT)

NFT는 고유한 데이터로 가상의 “고유한 자산” 역할을 해왔다. NFT에서 소유권을 확인할 수 있는 기능으로써 Token Id, Metadata...를 예로 들수 있다.


이 ‘소유권’이라는 특정은 지금의 NFT 시장을 구축하기도, 새로운 기술로써 사용되기도 한다.

예를 들어, P2E 게임 서비스를 보면 NFT를 활용한 게임 아이템을 구현하여 현실에서도 가치와 소유권을 인증하는 방식으로 쓰인다.


메타버스 게임을 서비스들이 늘어나고 있는 추세에 이 NFT를 이용한 게임 아이템은 확실히 매력이 있다.


그런데, 게임 같은 다이나믹한 환경에서의 불변하는 Metadata는 어찌보면 아쉬울 수 있을 것이다.

개인적인 경험 :
프로젝트를 위해 기껏 민팅해놨던 NFT의 메타데이터를 변경해야 하는데, 정적인 NFT이기 때문에 메타데이터 변경을 할 수가 없었다. 결국, 다시한번 NFT를 민팅하는 방향으로 프로젝트를 진행했어야 했다.


이에 대한 해결책으로 dNFT를 사용할 수 있다.


dNFT는 동적(Dynamic) NFT라는 뜻으로 스마트 컨트랙트의 논리 조건에 따라 변경될 수 있는 메타데이터라고 소개한다.

dNFT 작동방식

  • dNFT 요청이 스마트 컨트랙트로 전송
  • 컨트랙트는 블록체인의 온체인 데이터를 쿼리
  • Oracle에 의존하여 오프체인 데이터에 연결
  • 두 쿼리 결과에 따라 스마트 컨트랙트가 특정 유형의 미디어를 반환(ex. json)
  • 미디어는 IPFS에 저장



Tutorial dNFT 리뷰

이번 컨퍼런스에서는 현장에서 dNFT를 실습해보는 이벤트를 진행했다.

이 현장 이벤트에서 첫번째로 끝내서 15만원 상당의 하드월렛을 받았다는 것은 안비밀(?)
참조

이번 실습에선 다음의 경험이 있으면 아주 쉽게 따라할 수 있었다.

  • 스마트 컨트랙트 배포를 해본적 있는가?
  • NFT 민팅을 해본적 있는가?
  • Solidity를 사용해본적 있는가?
  • 기타 월렛(ex. Kaikas, Metamask…)을 사용하여 컨트랙트 IDE에 연결할 수 있는가?


나는 다행히 실무에서 모두 다뤄본적 있었다.


이 컨트랙트에서 중점적으로 봐야하는 것은 checkUpkeepperformUpkeep 함수이다.


컨트랙트에 설정한 시간이 지나면 메타데이터를 업데이트 시킬 수 있고, 업데이트되는 메타데이터는 컨트랙트에 저장이 되어있다.

예제에는 두 개의 메타데이터만 사용하지만, 블록에 Uri를 저장하는 함수를 만들어서 사용하면 될 듯하다.
이미 _setTokenURI 함수를 사용해서 커스터 마이징하면 좋겠다.


  1. 지갑 준비하기 먼저 자신이 평소에 많이 사용하는 지갑을 준비한다. (개인적으로 메타마스크를 많이 써서 메타마스크를 가져왔다.)


  1. Klaytn IDE를 이용하여 컨트랙트 배포
    컨트랙트 전체 코드
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    
       // SPDX-License-Identifier: GPL-3.0
    
       pragma solidity ^0.8.0;
    
       import "@klaytn/contracts/KIP/token/KIP17/KIP17.sol";
       import "@klaytn/contracts/KIP/token/KIP17/extensions/KIP17Enumerable.sol";
       import "@klaytn/contracts/KIP/token/KIP17/extensions/KIP17URIStorage.sol";
       import "@klaytn/contracts/utils/Counters.sol";
       import "@klaytn/contracts/utils/Strings.sol";
       import "@klaytn/contracts/access/Ownable.sol";
    
    
    
       // witnet pricefeed
    
       import "witnet-solidity-bridge/contracts/interfaces/IWitnetPriceRouter.sol";
       import "witnet-solidity-bridge/contracts/interfaces/IWitnetPriceFeed.sol";
    
       contract KdynamicNFT is KIP17, KIP17Enumerable, KIP17URIStorage, Ownable {
           using Counters for Counters.Counter;
    
            Counters.Counter private _tokenIdCounter;
    
           uint public interval; 
           uint public lastTimeStamp;
    
           int256 public currentPrice;
    
           IWitnetPriceRouter public witnetPriceRouter;
           IWitnetPriceFeed public klayUsdtPrice;
    
           // IPFS URIs for the dynamic nft graphics/metadata.
           // NOTE: These connect to my IPFS Companion node.
           // You should upload the contents of the /ipfs folder to your own node for development.
               string bullUrisIpfs = "https://ipfs.io/ipfs/QmdcURmN1kEEtKgnbkVJJ8hrmsSWHpZvLkRgsKKoiWvW9g?filename=simple_bull.json";
               string bearUrisIpfs = "https://ipfs.io/ipfs/QmbKhBXVWmwrYsTPFYfroR2N7NAekAMxHUVg2CWks7i9qj?filename=simple_bear.json";
    
           event TokensUpdated(string marketTrend);
    
           // YOu can pass in 30(seconds) for `updateInterval`
           constructor(uint updateInterval) KIP17("Klaytn dNFT", "KDNFT") {
               // Set the keeper update interval
               interval = updateInterval; 
               lastTimeStamp = block.timestamp;  //  seconds since unix epoch
    
               // Baobab Price-feed contract address
               witnetPriceRouter = IWitnetPriceRouter(0xeD074DA2A76FD2Ca90C1508930b4FB4420e413B0);
               updateKlayUsdtPriceFeed();
    
               // gets the current KLAY/USDT price and store it to currentPrice var 
               (currentPrice ,) = getKlayUsdtPrice();
           }
    
           function safeMint(address to) public  {
               // Current counter value will be the minted token's token ID.
               uint256 tokenId = _tokenIdCounter.current();
    
               // Increment it so next time it's correct when we call .current()
               _tokenIdCounter.increment();
    
               // Mint the token
               _safeMint(to, tokenId);
    
               // Default to a bull NFT
               string memory defaultUri = bullUrisIpfs;
               _setTokenURI(tokenId, defaultUri);
           }
    
           function checkUpkeep(bytes calldata checkData) external view  returns (bool upkeepNeeded, bytes memory performData) {
                // interval 시간이 지났는지 확인하는 
               upkeepNeeded = (block.timestamp - lastTimeStamp) > interval;
    
               return (upkeepNeeded, checkData);
           }
    
    
           function performUpkeep() external  {
               //We highly recommend revalidating the upkeep in the performUpkeep function
               if ((block.timestamp - lastTimeStamp) > interval ) {
                   int latestPrice;
                   lastTimeStamp = block.timestamp;         
                   (latestPrice, ) =  getKlayUsdtPrice();
                      
                   // latestPrice를 계속해서 업데이트 시켜줌.
                   if(compareStrings(tokenURI(0), bearUrisIpfs)) {
                       latestPrice = currentPrice + 1;
                   } else {
                       latestPrice = currentPrice - 1;
                   }
    
                   if (latestPrice == currentPrice) {
                       return;
                   }
    
                   if (latestPrice < currentPrice) {
                       // bear
                       updateAllTokenUris("bear");
    
                   } else {
                       // bull
                       updateAllTokenUris("bull");
                   }
    
                   // update currentPrice
                   currentPrice = latestPrice;
               } else {
                   return ;
               }
    
    
           }
    
           function updateAllTokenUris(string memory trend) internal {
               if (compareStrings("bear", trend)) {
                   for (uint i = 0; i < _tokenIdCounter.current() ; i++) {
                       _setTokenURI(i, bearUrisIpfs);
                   } 
    
               } else {     
    
                   for (uint i = 0; i < _tokenIdCounter.current() ; i++) {
                       _setTokenURI(i, bullUrisIpfs);
                   }  
               }   
               emit TokensUpdated(trend);
           }
    
           function updateKlayUsdtPriceFeed() public {
               IERC165 _newPriceFeed = witnetPriceRouter.getPriceFeed(bytes4(0x5d9add33));
               if (address(_newPriceFeed) != address(0)) {
                   klayUsdtPrice = IWitnetPriceFeed(address(_newPriceFeed));
               }
           }
    
           /// Returns the KlAY / USDT price (6 decimals), ultimately provided by the Witnet oracle, and
           /// the timestamps at which the price was reported back from the Witnet oracle's sidechain 
           /// to Klaytn Baobab. 
            function getKlayUsdtPrice() public view returns (int256 _lastPrice, uint256 _lastTimestamp) {
               (_lastPrice, _lastTimestamp,,) = klayUsdtPrice.lastValue();
           }
    
           function compareStrings(string memory a, string memory b) internal pure returns (bool) {
               return (keccak256(abi.encodePacked((a))) == keccak256(abi.encodePacked((b))));
           }
    
           function setInterval(uint256 newInterval) public onlyOwner {
               interval = newInterval;
           }
    
           // The following functions are overrides required by Solidity.
           function _beforeTokenTransfer(address from, address to, uint256 tokenId)
               internal
               override(KIP17, KIP17Enumerable)
           {
               super._beforeTokenTransfer(from, to, tokenId);
           }
    
           function _burn(uint256 tokenId) internal override(KIP17, KIP17URIStorage) {
               super._burn(tokenId);
           }
    
           function tokenURI(uint256 tokenId)
               public
               view
               override(KIP17, KIP17URIStorage)
               returns (string memory)
           {
               return super.tokenURI(tokenId);
           }
    
           function supportsInterface(bytes4 interfaceId)
               public
               view
               override(KIP17, KIP17Enumerable)
               returns (bool)
           {
               return super.supportsInterface(interfaceId);
           }
       }
    


    위의 코드를 IDE에 복/붙 하여 컨트랙트를 배포한다.
    이 때, 주석처리 되어있는 곳 때문에 아마 Warning이 뜰 것이다. Solidity를 만질 수 있다면, 고쳐보자.
    배포할 때는 튜토리얼에 나와있는 것처럼 인터벌을 30 정도 주었다.
    스크린샷 2023-05-15 오후 3 10 46


  1. 민팅 이제 내 주소에 민팅을 한다.
    컨트랙트 자체에 메타데이터가 기입되어있기 때문에 따로 TokenUri를 설정할 필요가 없다.
    mint

  2. 메타데이터 변경해보기

    참고

    • checkUpkeep 함수를 call하여 performUpkeep 함수 실행을 할 수 있는지 확인해야 한다.
      checkUpkeep
      인자에는 빈 배열([])을 넣자.


    • checkUpkeep 결과가 true가 나오면, performUpkeep 함수를 실행시켰다.
      performUpkeep
      마찬가지로 인자에는 빈 배열([])을 넣었다.
      결과로 나오는 Uri가 bull -> bear 로 변경되면 성공!


마치며

여기까지 실습을 마치고, 확인을 받았다.

주어지는 상품(15만원 상당의 하드월렛)을 받기위해선 다음의 사항을 확인 받아야 한다.


확인 사항

  • QR코드로 지급해준 Klaytn DevMeet - Metaverse Knowledge Kit IWTT NFT를 내 주소에 받았는지 확인
    IWTT - Metaverse Knowledge Kit
    IWTT
  • Klaytn IDE 로 컨트랙트 결과 확인
  • 6월 DevMeet에 필참한다는 약속 ㅋ


이번 행사 참여로 인해 15만원 상당의 지갑을 얻었다는 것보다, 콜린님과 함께하는 블록체인 코어 스터디를 하게되어 너무 기뻤다.


컨퍼런스를 마치고, 준비해주신 저녁 도시락을 먹고,


dosirak


끝 마쳤다. 아주 재미있었다. ㅎㅎ



클레이튼이 여러모로 신경 쓸 것이 많은 시기일 것 같은데도 불구하고, 계속해서 개발자들을 위한 컨퍼런스를 진행해주어서 너무 좋았다.


다음 6월 컨퍼런스도 기대가 된다.

이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.