paint-brush
让我们通过构建 Web3 票务系统来颠覆 Ticketmaster经过@MichaelB
2,225 讀數
2,225 讀數

让我们通过构建 Web3 票务系统来颠覆 Ticketmaster

经过 Michael17m2023/04/29
Read on Terminal Reader

太長; 讀書

NFT 的一个流行且实用的用例是生成现场活动的门票。我们将部署一个充当票务服务的智能合约,并将票创建为 ERC-20 不可替代代币 (NFT)。使用智能合约和 NFT 甚至可以实现新的收入流,例如版税支付和收入分享。
featured image - 让我们通过构建 Web3 票务系统来颠覆 Ticketmaster
Michael HackerNoon profile picture
0-item
1-item
2-item

NFT 的一个流行且实用的用例是生成现场活动的门票。以太坊等区块链可以保证数字物品的所有权、来源和真实性,有效解决假票问题。虽然Ticketmaster等主要参与者正在努力减少黄牛(拼命控制谁可以转售门票、在哪里转售以及转售多少)和门票欺诈——web3已经有了解决方案。票务行业颠覆的时机已经成熟。


在本教程中,我们将了解如何使用 ConsenSys TruffleInfura和 Infura NFT API 创建此类票务解决方案。我们将部署一个充当票务服务的智能合约,并将票据创建为 ERC-20 不可替代令牌 (NFT)。我们还将介绍一些可以与合约交互的潜在前端架构,并一起作为一个集成的、全栈的、web3 票务系统。


让我们开始建设吧!

在以太坊上创建 NFT 票务系统

我们系统的基本架构旨在创建一个智能合约,将我们的门票作为不可替代的代币 (NFT) 发行。 NFT 非常适合我们想要构建的东西。它们是可证明的唯一数字代币,使我们能够确保每张票都是独一无二的,无法复制或伪造。这不仅保证了音乐会观众的安全票务体验,而且还使艺术家(和活动组织者)能够更好地控制门票分配、定价和转售。使用智能合约和 NFT 甚至可以带来新的收入来源,例如版税支付和收益分享!


(如果您需要任何这些术语、区块链技术或 web3 的背景信息,请查看这篇关于通过探索 Web3 堆栈学习成为 Web3 开发人员的文章)。

第 1 步:安装 MetaMask

我们要做的第一件事是设置一个 MetaMask 钱包并将 Sepolia 测试网络添加到其中。 MetaMask 是世界上最受欢迎、最安全且易于使用的自托管数字钱包。

首先,下载 MetaMask 扩展。安装扩展程序后,MetaMask 将为您设置钱包。在此过程中,您将获得一个密语。保持安全,在任何情况下都不应将其公开。


设置 MetaMask 后,单击右上角的“网络”选项卡。您将看到一个显示/隐藏测试网络的选项。


打开测试网络后,您应该能够在下拉菜单中看到 Sepolia 测试网络。我们想使用 Sepolia 网络,这样我们就可以部署和测试我们的系统,而无需花费任何真金白银。

第 2 步:获得一些测试 ETH

为了部署我们的智能合约并与之交互,我们需要一些免费的测试 ETH。您可以从Sepolia 水龙头获取免费的 Sepolia ETH。


一旦你为你的钱包注入资金,当你切换到 MetaMask 上的 Sepolia 测试网络时,你应该会看到一个非零余额。



第三步:安装 NPM 和 Node

像所有以太坊 dapps 一样,我们将使用 node 和 npm 构建我们的项目。如果您没有在本地计算机上安装这些,您可以在此处安装。


为确保一切正常,请运行以下命令:

 $ node -v


如果一切顺利,您应该会看到 Node.js 的版本号。

第 4 步:注册 Infura 帐户

为了将我们的合约部署到 Sepolia 网络,我们需要一个 Infura 帐户。 Infura 使我们能够访问 RPC 端点,从而可以快速、可靠且轻松地访问我们选择的区块链。


注册一个免费帐户。创建帐户后,导航到仪表板并选择Create New Key



对于网络,选择Web3 API并将其命名为Ticketing System或您选择的名称。


单击创建后,Infura 将为您生成一个 API 密钥,并自动为您提供以太坊、Goerli、Sepolia、L2 和非 EVM L1(及其相应的测试网)的 RPC 端点。

对于本教程,我们只对 Sepolia RPC 端点感兴趣。此 URL 的格式为https://sepolia.infura.io/v3/←API KEY→

第 5 步:创建 Node 项目并安装必要的包

让我们通过运行以下命令来设置一个空的项目存储库:

 $ mkdir nft-ticketing && cd nft-ticketing $ npm init -y


我们将使用世界一流的 EVM 智能合约开发环境和测试框架 Truffle 来构建和部署我们的加密货币智能合约。通过运行安装松露:

 $ npm install —save truffle


我们现在可以通过运行以下命令来创建准系统 Truffle 项目:

 $ npx truffle init


要检查一切是否正常,请运行:

 $ npx truffle test


我们现在已经成功配置了 Truffle。接下来让我们安装OpenZeppelin合同包。这个包将使我们能够访问 ERC-721 基础实现(不可替代令牌的标准)以及一些有用的附加功能。

 $ npm install @openzeppelin/contracts


为了允许 Truffle 代表我们使用我们的 MetaMask 钱包、签署交易和支付 gas,我们将需要另一个名为hdwalletprovider的包。使用以下命令安装它:

 $ npm install @truffle/hdwallet-provider


最后,为了保护我们敏感的钱包信息安全,我们将使用dotenv包。

 $ npm install dotenv

第 6 步:为 NFT 创建票务智能合约

在代码编辑器(例如:VS Code)中打开项目存储库。在contracts文件夹中,创建一个名为NftTicketing.sol的新文件。


我们的票务合约将继承 OpenZeppelin 的ERC721Enumerable实现提供的所有功能。这包括传输、元数据跟踪、所有权数据等。


我们将从头开始实现以下功能:

  1. 公开一级销售:我们的合同将赋予其所有者以特定价格出售门票的权力。所有者将有权开始和结束销售、更新门票价格以及提取发送给合同的任何款项以进行购票。只要销售开放且门票仍有供应,公众将有机会以销售价格铸造门票。
  2. 空投:所有者将能够将票空投到钱包地址列表。
  3. 预订:车主还可以为自己预订门票,而无需支付公开销售价格。


将以下代码添加到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


我们的合约已经很复杂了,但是可以添加一些你认为合适的额外功能。


例如,您可以在合约中实施反剥头皮机制。这样做的步骤如下:


  1. 定义一个 Solidity 映射,作为可以持有多张票的钱包的白名单。
  2. 创建一个允许所有者将地址添加到此白名单的函数。
  3. _beforeTokenTransfer中引入一个检查,仅当它在允许列表中时才允许铸造或转移到已经持有票证的钱包。


在合约的构造函数下方添加以下代码片段:

 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 命令再次编译合约。

第 7 步:更新 Truffle 配置并创建 .env 文件

在项目的根目录中创建一个名为.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', } } };

第八步:部署NFT智能合约

现在让我们编写一个脚本来将我们的合约部署到 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 函数进行捐赠并为自己铸造一张票。


对于功能齐全的票务服务,您通常需要以下前端:

  1. 一个网站(具有良好的用户体验),公共用户可以在其中支付和铸造他们的票。
  2. 一个管理员门户,所有者可以在其中预订和空投门票、更新定价、将管理员角色转移到另一个钱包、提取销售收入、开始和结束销售等。
  3. 一种工具,可以验证一个人是否在网上和 IRL 都有特定的票。


从头开始构建这些系统超出了本教程的范围,但我们将为您提供一些资源和技巧。

  1. 对于前端铸币网站,请查看我在Thank You NFT教程中构建的前端作为起点。

  2. 如果您在 Etherscan 上验证您的合约,它会自动为您提供一个管理门户,您可以在其中调用合约上的任何功能。这是您决定构建自定义解决方案之前的良好开端。

  3. 使用balanceOf函数验证钱包是否有您收藏的票证非常简单。如果有人能证明他们拥有一个包含我们其中一张门票的钱包,这基本上就是证明他们有一张门票。这可以使用数字签名来实现。


使用 Infura NFT API 进行验证

另一个提示:一旦你有了智能合约和前端(或者甚至在你的前端完成并且你想证明一切正常之前),你可以使用Infura NFT API来验证你的新 NFT 是否存在。 Infura NFT API 是一种通过单个 API 调用替换大量 NFT 相关代码的快速方法。


例如,我们可以通过 API 轻松获得我们需要显示 NFT 所有权的信息。我们只需要提供钱包地址。代码看起来像这样:

 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] }, … ] }

结论

在本教程中,我们使用TruffleInfura和 Infura NFT API部署了一个功能齐全的 NFT 票务服务。


这显然不是颠覆 Ticketmaster 所需的一切——但它是一个坚实的开始,也是一个很好的概念证明!即使您不使用此代码并启动自己的 NFT 票务平台,也希望您在此过程中对 web3 有所了解。


本文的主图是由 HackerNoon 的AI Image Generator通过提示“大体育场的摇滚音乐会”生成的。