NFT の一般的で実用的なユース ケースは、ライブ イベントのチケットを生成することです。イーサリアムなどのブロックチェーンは、デジタル アイテムの所有権、発信者、真正性を保証し、偽造チケットの問題を効果的に解決します。 Ticketmasterなどの主要なプレーヤーは、ダフ屋 (誰が、どこで、いくらでチケットを転売できるかを必死にコントロールしようとする) やチケット詐欺を軽減するのに苦労していますが、 web3には既にソリューションがあります。チケット業界は混乱の機が熟しています。
このチュートリアルでは、 ConsenSys Truffle 、 Infura 、および Infura NFT API を使用して、このようなチケット ソリューションを作成する方法を見ていきます。チケット サービスとして機能し、チケットを ERC-20 非代替トークン (NFT) として作成するスマート コントラクトを展開します。また、コントラクトと連携し、統合されたフルスタックの web3 チケット システムとして機能する可能性のあるフロントエンドのいくつかのアーキテクチャについても説明します。
建てよう!
システムの基本的なアーキテクチャは、チケットを非代替トークン (NFT) として発行するスマート コントラクトを作成することを目的としています。 NFT は、私たちが構築したいものに最適です。これらは証明可能な一意のデジタル トークンであり、すべてのチケットが一意であり、コピーや偽造できないことを保証します。これにより、コンサート参加者に安全なチケット体験が保証されるだけでなく、アーティスト (およびイベント主催者) がチケットの配布、価格設定、再販をより細かく制御できるようになります。スマート コントラクトと NFT を使用すると、ロイヤルティの支払いや収益分配などの新しい収益源も可能になります。
(これらの用語、ブロックチェーン技術、または web3 全般に関する背景情報が必要な場合は、 Web3 スタックを探索して Web3 開発者になる方法を学習するという記事を参照してください)。
最初に、MetaMask ウォレットをセットアップし、Sepolia テスト ネットワークを追加します。 MetaMask は、世界で最も人気があり、安全で使いやすいセルフカストディアル デジタル ウォレットです。
まず、 MetaMask 拡張機能をダウンロードします。拡張機能をインストールすると、MetaMask がウォレットをセットアップします。その過程で、秘密のフレーズが与えられます。安全に保管し、いかなる場合でも公開しないでください。
MetaMask を設定したら、右上の [ネットワーク] タブをクリックします。テスト ネットワークを表示/非表示にするオプションが表示されます。
テスト ネットワークをオンにすると、ドロップダウン メニューに Sepolia テスト ネットワークが表示されます。本当のお金を使わずにシステムを展開してテストできるように、Sepolia ネットワークを使用したいと考えています。
スマート コントラクトをデプロイして操作するには、無料のテスト ETH が必要です。無料の Sepolia ETH は、 Sepolia faucetから取得できます。
ウォレットに資金を投入したら、MetaMask で Sepolia テスト ネットワークに切り替えると、ゼロ以外の残高が表示されるはずです。
すべての Ethereum dapps と同様に、node と npm を使用してプロジェクトをビルドします。これらがローカル マシンにインストールされていない場合は、ここでインストールできます。
すべてが正しく機能していることを確認するには、次のコマンドを実行します。
$ node -v
すべてがうまくいけば、Node.js のバージョン番号が表示されるはずです。
コントラクトを Sepolia ネットワークにデプロイするには、Infura アカウントが必要です。 Infura は RPC エンドポイントへのアクセスを提供し、選択したブロックチェーンへの高速で信頼性が高く、簡単なアクセスを可能にします。
無料アカウントにサインアップします。アカウントを作成したら、ダッシュボードに移動して[Create New Key]を選択します。
ネットワークの場合は、 Web3 APIを選択し、 Ticketing Systemなどの任意の名前を付けます。
Createをクリックすると、Infura が API キーを生成し、Ethereum、Goerli、Sepolia、L2、および非 EVM L1 (およびそれらに対応するテストネット) への RPC エンドポイントを自動的に提供します。
このチュートリアルでは、Sepolia RPC エンドポイントのみに関心があります。この URL の形式はhttps://sepolia.infura.io/v3/←API KEY→
次のコマンドを実行して、空のプロジェクト リポジトリをセットアップしましょう。
$ mkdir nft-ticketing && cd nft-ticketing $ npm init -y
世界クラスの開発環境であり、EVM スマート コントラクトのテスト フレームワークである Truffle を使用して、暗号通貨スマート コントラクトを構築および展開します。以下を実行して Truffle をインストールします。
$ npm install —save truffle
次のコマンドを実行して、ベアボーン Truffle プロジェクトを作成できるようになりました。
$ npx truffle init
すべてが正常に機能するかどうかを確認するには、次を実行します。
$ npx truffle test
これで、Truffle が正常に構成されました。次に、 OpenZeppelin契約パッケージをインストールしましょう。このパッケージは、ERC-721 基本実装 (代替不可能なトークンの標準) へのアクセスと、いくつかの役立つ追加機能を提供します。
$ npm install @openzeppelin/contracts
Truffle が MetaMask ウォレットを使用し、トランザクションに署名し、ガスを支払うことを許可するには、 hdwalletprovider
という別のパッケージが必要です。次のコマンドを使用してインストールします。
$ npm install @truffle/hdwallet-provider
最後に、ウォレットの機密情報を安全に保つために、 dotenv
パッケージを使用します。
$ npm install dotenv
コード エディター (例: VS Code) でプロジェクト リポジトリを開きます。 contracts
フォルダーで、 NftTicketing.sol
という名前の新しいファイルを作成します。
私たちの発券コントラクトは、OpenZeppelin のERC721Enumerable
実装によって提供されるすべての機能を継承します。これには、転送、メタデータ追跡、所有権データなどが含まれます。
次の機能をゼロから実装します。
次のコードをNftTicketing.sol
に追加します。
//SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol"; import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; import "@openzeppelin/contracts/utils/Counters.sol"; import "@openzeppelin/contracts/utils/Base64.sol"; import "@openzeppelin/contracts/utils/Strings.sol"; contract NftTicketing is ERC721, ERC721Enumerable, ERC721URIStorage, Ownable { using Counters for Counters.Counter; Counters.Counter private _tokenIds; // Total number of tickets available for the event uint public constant MAX_SUPPLY = 10000; // Number of tickets you can book at a time; prevents spamming uint public constant MAX_PER_MINT = 5; string public baseTokenURI; // Price of a single ticket uint public price = 0.05 ether; // Flag to turn sales on and off bool public saleIsActive = false; // Give collection a name and a ticker constructor() ERC721("My NFT Tickets", "MNT") {} // Generate NFT metadata function generateMetadata(uint tokenId) public pure returns (string memory) { string memory svg = string(abi.encodePacked( "<svg xmlns='http://www.w3.org/2000/svg' preserveAspectRatio='xMinyMin meet' viewBox='0 0 350 350'>", "<style>.base { fill: white; font-family: serif; font-size: 25px; }</style>", "<rect width='100%' height='100%' fill='red' />", "<text x='50%' y='40%' class='base' dominant-baseline='middle' text-anchor='middle'>", "<tspan y='50%' x='50%'>NFT Ticket #", Strings.toString(tokenId), "</tspan></text></svg>" )); string memory json = Base64.encode( bytes( string( abi.encodePacked( '{"name": "NFT Ticket #', Strings.toString(tokenId), '", "description": "A ticket that gives you access to a cool event!", "image": "data:image/svg+xml;base64,', Base64.encode(bytes(svg)), '", "attributes": [{"trait_type": "Type", "value": "Base Ticket"}]}' ) ) ) ); string memory metadata = string( abi.encodePacked("data:application/json;base64,", json) ); return metadata; } // Reserve tickets to creator wallet function reserveNfts(uint _count) public onlyOwner { uint nextId = _tokenIds.current(); require(nextId + _count < MAX_SUPPLY, "Not enough NFTs left to reserve"); for (uint i = 0; i < _count; i++) { string memory metadata = generateMetadata(nextId + i); _mintSingleNft(msg.sender, metadata); } } // Airdrop NFTs function airDropNfts(address[] calldata _wAddresses) public onlyOwner { uint nextId = _tokenIds.current(); uint count = _wAddresses.length; require(nextId + count < MAX_SUPPLY, "Not enough NFTs left to reserve"); for (uint i = 0; i < count; i++) { string memory metadata = generateMetadata(nextId + i); _mintSingleNft(_wAddresses[i], metadata); } } // Set Sale state function setSaleState(bool _activeState) public onlyOwner { saleIsActive = _activeState; } // Allow public to mint NFTs function mintNfts(uint _count) public payable { uint nextId = _tokenIds.current(); require(nextId + _count < MAX_SUPPLY, "Not enough NFT tickets left!"); require(_count > 0 && _count <= MAX_PER_MINT, "Cannot mint specified number of NFT tickets."); require(saleIsActive, "Sale is not currently active!"); require(msg.value >= price * _count, "Not enough ether to purchase NFTs."); for (uint i = 0; i < _count; i++) { string memory metadata = generateMetadata(nextId + i); _mintSingleNft(msg.sender, metadata); } } // Mint a single NFT ticket function _mintSingleNft(address _wAddress, string memory _tokenURI) private { // Sanity check for absolute worst case scenario require(totalSupply() == _tokenIds.current(), "Indexing has broken down!"); uint newTokenID = _tokenIds.current(); _safeMint(_wAddress, newTokenID); _setTokenURI(newTokenID, _tokenURI); _tokenIds.increment(); } // Update price function updatePrice(uint _newPrice) public onlyOwner { price = _newPrice; } // Withdraw ether function withdraw() public payable onlyOwner { uint balance = address(this).balance; require(balance > 0, "No ether left to withdraw"); (bool success, ) = (msg.sender).call{value: balance}(""); require(success, "Transfer failed."); } // Get tokens of an owner function tokensOfOwner(address _owner) external view returns (uint[] memory) { uint tokenCount = balanceOf(_owner); uint[] memory tokensId = new uint256[](tokenCount); for (uint i = 0; i < tokenCount; i++) { tokensId[i] = tokenOfOwnerByIndex(_owner, i); } return tokensId; } // The following functions are overrides required by Solidity. function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize) internal override(ERC721, ERC721Enumerable) { super._beforeTokenTransfer(from, to, tokenId, batchSize); } function _burn(uint256 tokenId) internal override(ERC721, ERC721URIStorage) { super._burn(tokenId); } function tokenURI(uint256 tokenId) public view override(ERC721, ERC721URIStorage) returns (string memory) { return super.tokenURI(tokenId); } function supportsInterface(bytes4 interfaceId) public view override(ERC721, ERC721Enumerable) returns (bool) { return super.supportsInterface(interfaceId); } }
次を実行して、コントラクトが正しくコンパイルされていることを確認します。
npx truffle compile
私たちのコントラクトはすでにかなり複雑ですが、必要に応じていくつかの機能を追加することができます。
たとえば、コントラクト内にスキャルピング防止メカニズムを実装できます。そのための手順は次のとおりです。
コントラクトのコンストラクターの下に次のスニペットを追加します。
mapping(address => bool) canMintMultiple; // Function that allowlists addresses to hold multiple NFTs. function addToAllowlist(address[] calldata _wAddresses) public onlyOwner { for (uint i = 0; i < _wAddresses.length; i++) { canMintMultiple[_wAddresses[i]] = true; } }
最後に、_ beforeTokenTranfer関数を次のように変更します。
// The following functions are overrides required by Solidity. function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize) internal override(ERC721, ERC721Enumerable) { if (balanceOf(to) > 0) { require(to == owner() || canMintMultiple[to], "Not authorized to hold more than one ticket"); } super._beforeTokenTransfer(from, to, tokenId, batchSize); }
上記の Truffle コマンドを使用して、コントラクトをもう一度コンパイルします。
プロジェクトのルート ディレクトリに.env
という新しいファイルを作成し、次の内容を追加します。
INFURA_API_KEY = "https://sepolia.infura.io/v3/<Your-API-Key>" MNEMONIC = "<Your-MetaMask-Secret-Recovery-Phrase>"
次に、ウォレット、Infura RPC エンドポイント、および Sepolia ネットワークに関する情報を Truffle 構成ファイルに追加しましょう。 truffle.config.js
の内容を次のように置き換えます。
require('dotenv').config(); const HDWalletProvider = require('@truffle/hdwallet-provider'); const { INFURA_API_KEY, MNEMONIC } = process.env; module.exports = { networks: { development: { host: "127.0.0.1", port: 8545, network_id: "*" }, sepolia: { provider: () => new HDWalletProvider(MNEMONIC, INFURA_API_KEY), network_id: '5', } } };
コントラクトを Sepolia ブロックチェーンにデプロイするスクリプトを書きましょう。
migrations
フォルダーで、 1_deploy_contract.js
という名前の新しいファイルを作成し、次のコードを追加します。
// Get instance of the NFT contract const nftContract = artifacts.require("NftTicketing"); module.exports = async function (deployer) { // Deploy the contract await deployer.deploy(nftContract); const contract = await nftContract.deployed(); // Mint 5 tickets await contract.reserveNfts(5); console.log("5 NFT Tickets have been minted!") };
準備完了です!次のコマンドを実行してコントラクトをデプロイします。
truffle migrate --network sepolia
すべてがうまくいけば、次のような出力 (コントラクト アドレスを含む) が表示されるはずです。
Starting migrations... ====================== > Network name: 'sepolia' > Network id: 5 > Block gas limit: 30000000 (0x1c9c380) 1_deploy_contract.js ==================== Deploying 'NftTicketing' ----------------------- > transaction hash: … > Blocks: 2 Seconds: 23 … > Saving artifacts ------------------------------------- > Total cost: 0.1201 ETH Summary ======= > Total deployments: 1 > Final cost: 0.1201 ETH
Sepolia etherscan で契約アドレスを検索して、ライブで見ることができます。
おめでとう!コントラクトを Sepolia に正常にデプロイしました。
ステップ 9: スマート コントラクトとのインターフェイス
スマートコントラクトができました!次のステップは、コントラクトとやり取りするフロントエンドをデプロイし、誰でも mint 関数を呼び出して寄付を行い、自分のチケットを作成できるようにすることです。
完全に機能するチケット サービスには、通常、次のフロントエンドが必要です。
これらのシステムをゼロから構築することは、このチュートリアルの範囲外ですが、いくつかのリソースとヒントを残します。
フロントエンドのマイニング Web サイトについては、出発点としてThank You NFTチュートリアルで作成したフロントエンドを確認してください。
Etherscan で契約を確認すると、契約の任意の機能を呼び出すことができる管理ポータルが自動的に提供されます。これは、カスタム ソリューションの構築を決定する前の適切な最初のステップです。
ウォレットにコレクションのチケットがあることを確認することは、 balanceOf
関数を使用して非常に簡単です。誰かが私たちのチケットの 1 つを含むウォレットを所有していることを証明できれば、それは基本的にその人がチケットを持っている証拠です。これは、デジタル署名を使用して実現できます。
もう 1 つのヒント: スマート コントラクトとフロントエンドが完成したら (またはフロントエンドが完成し、すべてが機能することを証明したい場合でも)、 Infura NFT APIを使用して、新しい NFT が存在することを確認できます。 Infura NFT API は、多くの NFT 関連のコードを単一の API 呼び出しに置き換える簡単な方法です。
たとえば、NFT の所有権を示すために必要な情報は、API を介して簡単に入手できます。提供する必要があるのは、ウォレットのアドレスだけです。コードは次のようになります。
const walletAddress = <your wallet address> const chainId = "1" const baseUrl = "https://nft.api.infura.io" const url = `${baseUrl}/networks/${chainId}/accounts/${walletAddress}/assets/nfts` // API request const config = { method: 'get', url: url, auth: { username: '<-- INFURA_API_KEY –>', password: '<-- INFURA_API_SECRET –>', } }; // API Request axios(config) .then(response => { console.log(response['data']) }) .catch(error => console.log('error', error));
それを実行します …
$ node <filename>.js
次のように表示されます。
{ total: 1, pageNumber: 1, pageSize: 100, network: 'ETHEREUM', account: <account address>, cursor: null, assets: [ { contract: <NFT contract address>, tokenId: '0', supply: '1', type: 'ERC20', metadata: [Object] }, … ] }
このチュートリアルでは、 Truffle 、 Infura 、および Infura NFT APIを使用して、完全に機能する NFT チケット サービスをデプロイしました。
もちろん、Ticketmaster を混乱させるのに必要なすべてが揃っているわけではありません。このコードを使用せずに独自の NFT チケット プラットフォームを開始したとしても、その過程で web3 について少し学んだことを願っています。
この記事のリード画像は、「大きなスタジアムでのロック コンサート」というプロンプトを介して、HackerNoon のAI Image Generatorによって生成されました。