はじめに
こんにちは。株式会社レコチョクの山崎です。
2022年2月に中途で入社し、次世代ビジネス推進部に所属しております。
入社と同時にWeb3の世界に飛び込むことになったので、Web3との関わり歴は1年半ほど(執筆時点)と短いですが、日々Web3の奥深さや新たな発見などがあり、1年半経過しても新鮮な気持ちで仕事している今日この頃です。(毎日がエブリデイ)
今回は、レコチョクチケットのスマートコントラクトについて解説します。
また、これまでのチケットにおける課題の解決やユーザー体験を高められるように工夫をした点について紹介します。
ご存知の方もいるかもしれないですが、レコチョクチケットは2023年5月に実際の公演で利用されました!
プレスリリースもよければご確認ください!
目次
レコチョクチケットについて
初めに、プレスリリースでは「レコチョクチケットとは、チケットの発券・販売・入場管理・顧客管理までをトータルで担うソリューション」と記載しておりますが、本記事では、チケットを「レコチョクチケット」と表現させていただきますので、予めご了承ください。
レコチョクチケットとは、Web3の技術を用いて、これまでの電子チケットにおける課題を解決し、ユーザーの体験を向上させられるような仕掛けが可能となったチケットです。
これまでの電子チケットにおける課題として、以下のような項目が挙げられると思いますが、レコチョクチケットでは全て解決されております。
- 不正な転売
 - チケットの偽造
 - 入場時の本人確認
 
また、電子チケットをただただNFT化したというわけではなく、これまでになかった体験ができるようになっています。
- ダイナミックNFT(チケットがもぎられた後にQRコードからユニーク画像へ切り替わる)
 
不正な転売、チケットの偽造
BlockChainの技術を活用しているため、言わずもがなだと思うので、省略させていただきます。
1点補足としては、転売を禁止するのではなく、”不正な”転売を禁止するという点です。
OpenSeaなどのマーケットプレイスでの転売は許可することで、公式に転売を許可することが可能です。
また、ロイヤリティ設定が可能なため、転売される度にx%をクリエイターに還元されるといったことも可能です。
入場時の本人確認
通常のチケットだと、そもそも不正な転売や、チケットの偽造の可能性があるので、
- 免許証の提示
 - 顔写真の事前登録
 
等の本人確認を徹底し、転売対策をする必要がありました。
チケットをNFT化することで、不正な転売やチケットの偽造がないため、NFTがwalletに紐づいているかどうか判別するだけで本人確認が完了します。
つまり、個人情報を提示することなく入場が可能になります!(運営側からすると、個人情報を取り扱わなくて良いので、両者winwinです)
ダイナミックNFT
2023年5月の公演で活用されたレコチョクチケットでは、チケットもぎり完了後、QRコードがキャストの画像に変化しました。
一緒に来場した友達とのコミュニケーションとなったり、推しのキャストに切り替わったことで喜んでいたりと、好評でした。
OpenSeaで、どのような画像に切り替わったかご確認いただけます。
https://opensea.io/ja/collection/recochoku-nft-ticket-21
スマートコントラクトについて
スマートコントラクトの言語は Solidity、フレームワークは OpenZeppelin で開発しています。
また、solidityのversionは0.8.19を使用しています。
まず、mintです。
mintの際にtokenIdに対して、チケットステータスをunusedでsetします。
mapping(uint256 => string) private _ticketStatuses; function _updateTicketStatus(uint256 targetTokenId, string memory status)     private {     _ticketStatuses[targetTokenId] = status; } function mint(address to) public override onlyOwner {     tokenIds.increment();     uint256 tokenId = tokenIds.current();     _mint(to, tokenId);     _updateTicketStatus(tokenId, "unused"); }  | 
					
続いて、メタ情報取得のtokenURIです。
ダイナミックNFTを実現するために、チケットのステータスがusedかどうか判別する必要があります。
_isUnusedTicketがfalseで帰ってきた場合、メタ情報取得のendpointのpathが変更されます。
つまり、未使用と使用済みの場合でメタ情報取得のendpointをスマートコントラクト上で変更することでダイナミックNFTを実現しています。
function _getTicketStatus(uint256 tokenId)     private     view     returns (string memory) {     return _ticketStatuses[tokenId]; } function _isUnusedTicket(uint256 targetTokenId)     private     view     returns (bool) {     return         keccak256(abi.encodePacked(_getTicketStatus(targetTokenId))) ==         keccak256(abi.encodePacked("unused")); } function tokenURI(uint256 tokenId)     public     view     override     returns (string memory) {     _requireMinted(tokenId);     string memory baseURI = _baseURI();     string memory path = "";     if (!_isUnusedTicket(tokenId)) {         path = string(abi.encodePacked("/", _ticketStatuses[tokenId]));     }     if (bytes(baseURI).length > 0) {         return             string(                 abi.encodePacked(baseURI, Strings.toString(tokenId), path)             );     }     return ""; }  | 
					
最後に、チケットのステータスを使用済みに変更します。
function useTicket(uint256 targetTokenId) public onlyOwner {     require(         _isUnusedTicket(targetTokenId),         "Ticket status is not unused"     );     _updateTicketStatus(targetTokenId, "used"); }  | 
					
以上で、レコチョクチケットを実現することが可能です!
(極論を言えば、未使用 or 使用済みのステータスを持つだけでもレコチョクチケットを実現可能です!)
以下が全体のコードになります。
// SPDX-License-Identifier: MIT pragma solidity 0.8.19; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract NFTTicket is ERC721, Ownable {     using Counters for Counters.Counter;     Counters.Counter internal tokenIds;     string internal _tokenURIDomain;     mapping(uint256 => string) private _ticketStatuses;     constructor(         string memory tokenURIDomain_,         string memory name_,         string memory symbol_     ) ERC721(name_, symbol_) {         _tokenURIDomain = tokenURIDomain_;     }     function _baseURI() internal view override returns (string memory) {         return             string(                 abi.encodePacked(                     "https://",                     _tokenURIDomain,                     "/v1/contract/",                     toString(address(this)),                     "/token_id/"                 )             );     }     function tokenURI(uint256 tokenId)         public         view         override         returns (string memory)     {         _requireMinted(tokenId);         string memory baseURI = _baseURI();         string memory path = "";         if (!_isUnusedTicket(tokenId)) {             path = string(abi.encodePacked("/", _ticketStatuses[tokenId]));         }         if (bytes(baseURI).length > 0) {             return                 string(                     abi.encodePacked(baseURI, Strings.toString(tokenId), path)                 );         }         return "";     }     function mint(address to) public override onlyOwner {         tokenIds.increment();         uint256 tokenId = tokenIds.current();         _mint(to, tokenId);         _updateTicketStatus(tokenId, "unused");     }     function _isUnusedTicket(uint256 targetTokenId)         private         view         returns (bool)     {         return             keccak256(abi.encodePacked(_getTicketStatus(targetTokenId))) ==             keccak256(abi.encodePacked("unused"));     }     function _getTicketStatus(uint256 tokenId)         private         view         returns (string memory)     {         return _ticketStatuses[tokenId];     }     function _updateTicketStatus(uint256 targetTokenId, string memory status)         private     {         _ticketStatuses[targetTokenId] = status;     }     function useTicket(uint256 targetTokenId) public onlyOwner {         require(             _isUnusedTicket(targetTokenId),             "Ticket status is not unused"         );         _updateTicketStatus(targetTokenId, "used");     } }  | 
					
まとめ
今回はレコチョクチケットについて解説しました。
本記事に合わせて一部ソースコードを抜粋して紹介しました。
レコチョクチケットで実際に使用されたスマートコントラクトは以下から確認可能なので、ご興味ある方はご確認ください!!!!!
https://polygonscan.com/address/0xFb63e4728705542beC0E046992Bb1C4a7DAd7F66#code
最後まで読んでいただきありがとうございます。










