Một trường hợp sử dụng phổ biến và thiết thực cho NFT là tạo vé tham dự các sự kiện trực tiếp. Các chuỗi khối như Ethereum có thể đảm bảo quyền sở hữu, người khởi tạo và tính xác thực của một mặt hàng kỹ thuật số, giải quyết hiệu quả vấn đề vé giả. Trong khi những người chơi lớn như Ticketmaster đấu tranh để giảm thiểu những kẻ đầu cơ (cố gắng hết sức để kiểm soát ai có thể bán lại vé, ở đâu và với giá bao nhiêu) và gian lận vé— thì web3 đã có giải pháp. Ngành công nghiệp bán vé đã chín muồi cho sự gián đoạn.
Trong hướng dẫn này, chúng ta sẽ xem xét cách tạo một giải pháp bán vé như vậy bằng cách sử dụng ConsenSys Truffle , Infura và Infura NFT API. Chúng tôi sẽ triển khai một hợp đồng thông minh hoạt động như một dịch vụ bán vé và tạo các vé dưới dạng mã thông báo không thể thay thế (NFT) ERC-20. Chúng ta cũng sẽ xem qua một vài kiến trúc của giao diện người dùng tiềm năng có thể giao tiếp với hợp đồng và cùng nhau hoạt động như một hệ thống bán vé web3, đầy đủ, tích hợp.
Hãy xây dựng!
Kiến trúc cơ bản của hệ thống của chúng tôi nhằm tạo ra một hợp đồng thông minh phát hành vé của chúng tôi dưới dạng mã thông báo không thể thay thế (NFT). NFT hoàn hảo cho những gì chúng tôi muốn xây dựng. Chúng là những mã thông báo kỹ thuật số duy nhất đã được chứng minh cho phép chúng tôi đảm bảo rằng mọi vé là duy nhất và không thể sao chép hoặc giả mạo. Điều này không chỉ đảm bảo trải nghiệm mua vé an toàn cho khán giả mà còn trao quyền cho các nghệ sĩ (và nhà tổ chức sự kiện) kiểm soát tốt hơn việc phân phối, định giá và bán lại vé. Sử dụng hợp đồng thông minh và NFT thậm chí còn cho phép tạo ra các luồng doanh thu mới như thanh toán tiền bản quyền và chia sẻ doanh thu!
(Nếu bạn cần thông tin cơ bản về bất kỳ thuật ngữ nào trong số này, công nghệ chuỗi khối hoặc web3 nói chung, hãy xem bài viết này về Học cách trở thành nhà phát triển Web3 bằng cách khám phá Web3 Stac k).
Điều đầu tiên chúng ta sẽ làm là thiết lập ví MetaMask và thêm mạng thử nghiệm Sepolia vào đó. MetaMask là ví kỹ thuật số tự giam giữ phổ biến, an toàn và dễ sử dụng nhất thế giới.
Đầu tiên, tải xuống tiện ích mở rộng MetaMask . Sau khi bạn cài đặt tiện ích mở rộng, MetaMask sẽ thiết lập ví cho bạn. Trong quá trình này, bạn sẽ nhận được một cụm từ bí mật. Giữ nó an toàn, và trong mọi trường hợp bạn không nên công khai nó.
Khi bạn đã thiết lập MetaMask, hãy nhấp vào tab Mạng ở trên cùng bên phải. Bạn sẽ thấy một tùy chọn để hiển thị/ẩn các mạng thử nghiệm.
Sau khi bật mạng thử nghiệm, bạn sẽ có thể thấy mạng thử nghiệm Sepolia trong menu thả xuống. Chúng tôi muốn sử dụng mạng Sepolia để có thể triển khai và thử nghiệm hệ thống của mình mà không tốn bất kỳ khoản tiền thật nào.
Để triển khai hợp đồng thông minh của chúng tôi và tương tác với nó, chúng tôi sẽ yêu cầu một số ETH thử nghiệm miễn phí. Bạn có thể nhận Sepolia ETH miễn phí từ vòi Sepolia .
Sau khi cấp tiền cho ví của mình, bạn sẽ thấy số dư khác không khi chuyển sang mạng thử nghiệm Sepolia trên MetaMask.
Giống như tất cả các dapp Ethereum, chúng tôi sẽ xây dựng dự án của mình bằng cách sử dụng nút và npm. Trong trường hợp bạn chưa cài đặt những thứ này trên máy cục bộ của mình, bạn có thể thực hiện tại đây .
Để đảm bảo mọi thứ hoạt động chính xác, hãy chạy lệnh sau:
$ node -v
Nếu mọi việc suôn sẻ, bạn sẽ thấy số phiên bản cho Node.
Để triển khai hợp đồng của chúng tôi với mạng Sepolia, chúng tôi sẽ cần có tài khoản Infura. Infura cung cấp cho chúng tôi quyền truy cập vào các điểm cuối RPC, cho phép truy cập nhanh chóng, đáng tin cậy và dễ dàng vào chuỗi khối mà chúng tôi lựa chọn.
Đăng ký một tài khoản miễn phí . Khi bạn đã tạo tài khoản của mình, hãy điều hướng đến trang tổng quan và chọn Tạo khóa mới .
Đối với mạng, chọn API Web3 và đặt tên là Hệ thống bán vé hoặc tên nào đó bạn chọn.
Sau khi bạn nhấp vào Tạo , Infura sẽ tạo khóa API cho bạn và cung cấp cho bạn các điểm cuối RPC cho Ethereum, Goerli, Sepolia, L2 và L1 không phải EVM (và các mạng thử nghiệm tương ứng của chúng).
Đối với hướng dẫn này, chúng tôi chỉ quan tâm đến điểm cuối Sepolia RPC. URL này có dạng https://sepolia.infura.io/v3/←API KEY→
Hãy thiết lập một kho lưu trữ dự án trống bằng cách chạy các lệnh sau:
$ mkdir nft-ticketing && cd nft-ticketing $ npm init -y
Chúng tôi sẽ sử dụng Truffle, môi trường phát triển đẳng cấp thế giới và khuôn khổ thử nghiệm cho các hợp đồng thông minh EVM, để xây dựng và triển khai hợp đồng thông minh tiền điện tử của chúng tôi. Cài đặt Truffle bằng cách chạy:
$ npm install —save truffle
Bây giờ chúng ta có thể tạo dự án Truffle barebones bằng cách chạy lệnh sau:
$ npx truffle init
Để kiểm tra xem mọi thứ có hoạt động bình thường không, hãy chạy:
$ npx truffle test
Bây giờ chúng ta đã cấu hình Truffle thành công. Tiếp theo chúng ta hãy cài đặt gói hợp đồng OpenZeppelin . Gói này sẽ cung cấp cho chúng tôi quyền truy cập vào triển khai cơ sở ERC-721 (tiêu chuẩn cho mã thông báo không thể thay thế) cũng như một số chức năng bổ sung hữu ích.
$ npm install @openzeppelin/contracts
Để cho phép Truffle sử dụng ví MetaMask của chúng tôi, ký giao dịch và thanh toán gas thay cho chúng tôi, chúng tôi sẽ yêu cầu một gói khác có tên hdwalletprovider
. Cài đặt nó bằng cách sử dụng lệnh sau:
$ npm install @truffle/hdwallet-provider
Cuối cùng, để giữ an toàn cho thông tin ví nhạy cảm của chúng tôi, chúng tôi sẽ sử dụng gói dotenv
.
$ npm install dotenv
Mở kho lưu trữ dự án trong trình chỉnh sửa mã (ví dụ: Mã VS). Trong thư mục contracts
, tạo một tệp mới có tên NftTicketing.sol
.
Hợp đồng bán vé của chúng tôi sẽ kế thừa tất cả chức năng được cung cấp bởi việc triển khai ERC721Enumerable
của OpenZeppelin. Điều này bao gồm chuyển nhượng, theo dõi siêu dữ liệu, dữ liệu quyền sở hữu, v.v.
Chúng tôi sẽ triển khai các tính năng sau từ đầu:
Thêm đoạn mã sau vào 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); } }
Đảm bảo hợp đồng được biên dịch chính xác bằng cách chạy:
npx truffle compile
Hợp đồng của chúng tôi đã khá phức tạp rồi, nhưng bạn có thể thêm một số tính năng bổ sung nếu thấy phù hợp.
Ví dụ: bạn có thể triển khai cơ chế chống nhân rộng trong hợp đồng của mình. Các bước để làm như vậy sẽ như sau:
Thêm đoạn mã sau bên dưới hàm tạo của hợp đồng:
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; } }
Cuối cùng, sửa hàm _ beforeTokenTranfer như sau:
// 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); }
Biên dịch hợp đồng một lần nữa bằng lệnh Truffle ở trên.
Tạo một tệp mới trong thư mục gốc của dự án có tên là .env
và thêm các nội dung sau:
INFURA_API_KEY = "https://sepolia.infura.io/v3/<Your-API-Key>" MNEMONIC = "<Your-MetaMask-Secret-Recovery-Phrase>"
Tiếp theo, hãy thêm thông tin về ví của chúng tôi, điểm cuối Infura RPC và mạng Sepolia vào tệp cấu hình Truffle của chúng tôi. Thay thế nội dung của truffle.config.js
bằng nội dung sau:
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', } } };
Bây giờ chúng ta hãy viết một kịch bản để triển khai hợp đồng của chúng ta lên chuỗi khối Sepolia.
Trong thư mục migrations
, hãy tạo một tệp mới có tên 1_deploy_contract.js
và thêm mã sau:
// 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!") };
Chúng ta đã sẵn sàng! Triển khai hợp đồng bằng cách chạy lệnh sau:
truffle migrate --network sepolia
Nếu mọi việc suôn sẻ, bạn sẽ thấy đầu ra (chứa địa chỉ hợp đồng) giống như sau:
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
Bạn có thể tìm kiếm địa chỉ hợp đồng của mình trên Sepolia etherscan và xem trực tiếp.
Chúc mừng! Bạn đã triển khai thành công hợp đồng cho Sepolia.
Bước 9: Giao diện với hợp đồng thông minh
Chúng tôi có hợp đồng thông minh của chúng tôi! Bước tiếp theo là triển khai các giao diện người dùng có giao diện với hợp đồng và cho phép bất kỳ ai gọi chức năng đúc tiền để quyên góp và đúc một vé cho chính họ.
Đối với một dịch vụ bán vé đầy đủ chức năng, thông thường bạn sẽ cần các giao diện người dùng sau:
Việc xây dựng các hệ thống này từ đầu nằm ngoài phạm vi của hướng dẫn này, nhưng chúng tôi sẽ cung cấp cho bạn một số tài nguyên và mẹo.
Đối với trang web khai thác giao diện người dùng, hãy xem giao diện người dùng mà tôi đã tạo trong hướng dẫn Cảm ơn NFT làm điểm bắt đầu.
Nếu bạn xác minh hợp đồng của mình trên Etherscan, nó sẽ tự động cung cấp cho bạn một cổng quản trị nơi bạn có thể gọi bất kỳ chức năng nào trong hợp đồng của mình. Đây là bước đầu tiên tốt trước khi bạn quyết định xây dựng một giải pháp tùy chỉnh.
Việc xác minh rằng ví có vé từ bộ sưu tập của bạn cực kỳ đơn giản bằng cách sử dụng hàm balanceOf
. Nếu ai đó có thể chứng minh rằng họ sở hữu một chiếc ví có chứa một trong các vé của chúng tôi, thì về cơ bản đó là bằng chứng rằng họ có vé. Điều này có thể đạt được bằng cách sử dụng chữ ký số.
Một gợi ý nữa: sau khi bạn có hợp đồng thông minh và giao diện người dùng (hoặc thậm chí trước khi giao diện người dùng của bạn hoàn tất và bạn muốn chứng minh rằng mọi thứ đều hoạt động), bạn có thể sử dụng API Infura NFT để xác minh rằng NFT mới của bạn tồn tại. API Infura NFT là một cách nhanh chóng để thay thế nhiều mã liên quan đến NFT bằng một lệnh gọi API duy nhất.
Ví dụ: thông tin chúng tôi cần để thể hiện quyền sở hữu NFT của mình có sẵn dễ dàng cho chúng tôi thông qua API. Tất cả những gì chúng ta cần cung cấp là địa chỉ ví. Mã sẽ trông giống như thế này:
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));
Chạy nó…
$ node <filename>.js
Và bạn sẽ thấy một cái gì đó như thế này:
{ 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] }, … ] }
Trong hướng dẫn này, chúng tôi đã triển khai dịch vụ bán vé NFT đầy đủ chức năng bằng cách sử dụng Truffle , Infura và Infura NFT API .
Rõ ràng đó không phải là tất cả những gì bạn cần để phá vỡ Ticketmaster—nhưng đó là một khởi đầu vững chắc và là một bằng chứng tuyệt vời về khái niệm! Ngay cả khi bạn không lấy mã này và bắt đầu nền tảng bán vé NFT của riêng mình, hy vọng rằng bạn đã học được một chút về web3 trong quá trình này.
Hình ảnh chính cho bài viết này được tạo bởiTrình tạo hình ảnh AI của HackerNoon thông qua lời nhắc "một buổi hòa nhạc rock trong một sân vận động lớn".