Ein beliebter und praktischer Anwendungsfall für NFTs ist die Generierung von Tickets für Live-Events. Blockchains wie Ethereum können den Besitz, den Urheber und die Authentizität eines digitalen Gegenstands garantieren und so das Problem gefälschter Tickets effektiv lösen. Während große Player wie Ticketmaster darum kämpfen, Scalper (die verzweifelt kontrollieren wollen, wer Tickets wo und für wie viel weiterverkaufen darf) und Ticketbetrug einzudämmen, hat web3 bereits eine Lösung. Die Ticketing-Branche ist reif für Umwälzungen.
In diesem Tutorial schauen wir uns an, wie man eine solche Ticketing-Lösung mit ConsenSys Truffle , Infura und der Infura NFT API erstellt. Wir werden einen intelligenten Vertrag einsetzen, der als Ticketservice fungiert und Tickets als nicht fungible ERC-20-Token (NFTs) erstellt. Wir werden auch einige Architekturen potenzieller Frontends durchgehen, die mit dem Vertrag verbunden sein könnten und zusammen als integriertes, umfassendes Web3-Ticketsystem fungieren könnten.
Lasst uns bauen!
Die Grundarchitektur unseres Systems zielt darauf ab, einen Smart Contract zu erstellen, der unsere Tickets als nicht fungible Token (NFTs) ausgibt. NFTs sind perfekt für das, was wir bauen wollen. Es handelt sich um nachweislich einzigartige digitale Token, mit denen wir sicherstellen können, dass jedes Ticket einzigartig ist und nicht kopiert oder gefälscht werden kann. Dies garantiert nicht nur ein sicheres Ticketerlebnis für Konzertbesucher, sondern ermöglicht Künstlern (und Veranstaltern) auch eine bessere Kontrolle über den Ticketvertrieb, die Preisgestaltung und den Weiterverkauf. Der Einsatz von Smart Contracts und NFTs ermöglicht sogar neue Einnahmequellen wie Lizenzzahlungen und Umsatzbeteiligung!
(Wenn Sie Hintergrundinformationen zu einem dieser Begriffe, der Blockchain-Technologie oder Web3 im Allgemeinen benötigen, lesen Sie diesen Artikel zum Thema „Lernen, ein Web3-Entwickler zu werden, indem Sie den Web3-Stack erkunden “).
Als Erstes richten wir ein MetaMask-Wallet ein und fügen das Sepolia-Testnetzwerk hinzu. MetaMask ist die weltweit beliebteste, sicherste und benutzerfreundlichste selbstverwaltete digitale Geldbörse.
Laden Sie zunächst die MetaMask-Erweiterung herunter . Nachdem Sie die Erweiterung installiert haben, richtet MetaMask das Wallet für Sie ein. Dabei erhalten Sie eine Geheimphrase. Bewahren Sie es sorgfältig auf und machen Sie es unter keinen Umständen öffentlich.
Nachdem Sie MetaMask eingerichtet haben, klicken Sie oben rechts auf die Registerkarte „Netzwerk“. Sie sehen eine Option zum Ein-/Ausblenden von Testnetzwerken.
Sobald Sie Testnetzwerke aktivieren, sollten Sie das Sepolia-Testnetzwerk im Dropdown-Menü sehen können. Wir möchten das Sepolia-Netzwerk nutzen, damit wir unser System bereitstellen und testen können, ohne echtes Geld auszugeben.
Um unseren Smart Contract einzusetzen und mit ihm zu interagieren, benötigen wir eine kostenlose Test-ETH. Sie können kostenlose Sepolia ETH über den Sepolia-Faucet erhalten.
Sobald Sie Ihr Guthaben aufgeladen haben, sollte beim Wechsel zum Sepolia-Testnetzwerk auf MetaMask ein Guthaben ungleich Null angezeigt werden.
Wie alle Ethereum-Dapps werden wir unser Projekt mit Node und NPM erstellen. Falls Sie diese nicht auf Ihrem lokalen Computer installiert haben, können Sie dies hier tun.
Um sicherzustellen, dass alles ordnungsgemäß funktioniert, führen Sie den folgenden Befehl aus:
$ node -v
Wenn alles gut geht, sollten Sie eine Versionsnummer für Node sehen.
Um unseren Vertrag im Sepolia-Netzwerk bereitzustellen, benötigen wir ein Infura-Konto. Infura ermöglicht uns den Zugriff auf RPC-Endpunkte, die einen schnellen, zuverlässigen und einfachen Zugriff auf die Blockchain unserer Wahl ermöglichen.
Eröffnen Sie ein kostenloses Konto . Sobald Sie Ihr Konto erstellt haben, navigieren Sie zum Dashboard und wählen Sie „Neuen Schlüssel erstellen“ aus.
Wählen Sie als Netzwerk die Web3-API und nennen Sie sie „Ticketing System“ oder einen Namen Ihrer Wahl.
Sobald Sie auf „Erstellen“ klicken, generiert Infura einen API-Schlüssel für Sie und stellt Ihnen automatisch RPC-Endpunkte für Ethereum, Goerli, Sepolia, L2s und Nicht-EVM-L1s (und ihre entsprechenden Testnetze) zur Verfügung.
Für dieses Tutorial interessieren wir uns nur für den Sepolia RPC-Endpunkt. Diese URL hat die Form https://sepolia.infura.io/v3/←API KEY→
Lassen Sie uns ein leeres Projekt-Repository einrichten, indem wir die folgenden Befehle ausführen:
$ mkdir nft-ticketing && cd nft-ticketing $ npm init -y
Wir werden Truffle verwenden, eine erstklassige Entwicklungsumgebung und ein Testframework für EVM-Smart Contracts, um unseren Kryptowährungs-Smart Contract zu erstellen und bereitzustellen. Installieren Sie Truffle, indem Sie Folgendes ausführen:
$ npm install —save truffle
Wir können jetzt ein Barebone-Truffle-Projekt erstellen, indem wir den folgenden Befehl ausführen:
$ npx truffle init
Um zu überprüfen, ob alles ordnungsgemäß funktioniert, führen Sie Folgendes aus:
$ npx truffle test
Wir haben Truffle jetzt erfolgreich konfiguriert. Als nächstes installieren wir das OpenZeppelin- Vertragspaket. Mit diesem Paket erhalten wir Zugriff auf die ERC-721-Basisimplementierung (den Standard für nicht fungible Token) sowie auf einige hilfreiche Zusatzfunktionen.
$ npm install @openzeppelin/contracts
Damit Truffle unser MetaMask-Wallet nutzen, Transaktionen signieren und in unserem Namen Benzin bezahlen kann, benötigen wir ein weiteres Paket namens hdwalletprovider
. Installieren Sie es mit dem folgenden Befehl:
$ npm install @truffle/hdwallet-provider
Um unsere sensiblen Wallet-Informationen zu schützen, verwenden wir schließlich das Paket dotenv
.
$ npm install dotenv
Öffnen Sie das Projekt-Repository in einem Code-Editor (z. B. VS Code). Erstellen Sie im contracts
eine neue Datei mit dem Namen NftTicketing.sol
.
Unser Ticketing-Vertrag übernimmt alle Funktionen, die von der ERC721Enumerable
-Implementierung von OpenZeppelin angeboten werden. Dazu gehören Übertragungen, Metadatenverfolgung, Eigentumsdaten usw.
Wir werden die folgenden Funktionen von Grund auf implementieren:
Fügen Sie den folgenden Code zu NftTicketing.sol
hinzu.
//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); } }
Stellen Sie sicher, dass der Vertrag korrekt kompiliert wird, indem Sie Folgendes ausführen:
npx truffle compile
Unser Vertrag ist bereits ziemlich komplex, aber es ist möglich, nach Belieben einige zusätzliche Funktionen hinzuzufügen.
Sie können beispielsweise einen Anti-Scalping-Mechanismus in Ihren Vertrag integrieren. Die Schritte dazu wären wie folgt:
Fügen Sie den folgenden Ausschnitt unter dem Konstruktor des Vertrags hinzu:
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; } }
Ändern Sie abschließend die Funktion _ beforeTokenTranfer wie folgt:
// 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); }
Kompilieren Sie den Vertrag noch einmal mit dem obigen Truffle-Befehl.
Erstellen Sie im Stammverzeichnis des Projekts eine neue Datei mit dem Namen .env
und fügen Sie den folgenden Inhalt hinzu:
INFURA_API_KEY = "https://sepolia.infura.io/v3/<Your-API-Key>" MNEMONIC = "<Your-MetaMask-Secret-Recovery-Phrase>"
Als Nächstes fügen wir unserer Truffle-Konfigurationsdatei Informationen über unser Wallet, den Infura RPC-Endpunkt und das Sepolia-Netzwerk hinzu. Ersetzen Sie den Inhalt von truffle.config.js
durch Folgendes:
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', } } };
Lassen Sie uns nun ein Skript schreiben, um unseren Vertrag in der Sepolia-Blockchain bereitzustellen.
Erstellen Sie im migrations
eine neue Datei mit dem Namen 1_deploy_contract.js
und fügen Sie den folgenden Code hinzu:
// 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!") };
Wir sind bereit! Stellen Sie den Vertrag bereit, indem Sie den folgenden Befehl ausführen:
truffle migrate --network sepolia
Wenn alles gut geht, sollten Sie eine Ausgabe (mit der Vertragsadresse) sehen, die etwa so aussieht:
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
Sie können auf Sepolia etherscan nach Ihrer Vertragsadresse suchen und diese live sehen.
Glückwunsch! Sie haben den Vertrag erfolgreich für Sepolia bereitgestellt.
Schritt 9: Schnittstelle zum Smart Contract
Wir haben unseren Smart Contract! Der nächste Schritt besteht darin, Frontends bereitzustellen, die mit dem Vertrag interagieren und es jedem ermöglichen, die Mint-Funktion aufzurufen, um eine Spende zu tätigen und ein Ticket für sich selbst zu prägen.
Für einen voll funktionsfähigen Ticketservice benötigen Sie normalerweise die folgenden Frontends:
Der Aufbau dieser Systeme von Grund auf ist nicht Teil dieses Tutorials, aber wir geben Ihnen ein paar Ressourcen und Tipps.
Schauen Sie sich für die Frontend-Minting-Website das Frontend an, das ich im Thank You NFT- Tutorial als Ausgangspunkt erstellt habe.
Wenn Sie Ihren Vertrag auf Etherscan verifizieren, erhalten Sie automatisch ein Admin-Portal, über das Sie jede Funktion Ihres Vertrags aufrufen können. Dies ist ein guter erster Schritt, bevor Sie sich für den Aufbau einer individuellen Lösung entscheiden.
Mit der balanceOf
Funktion können Sie ganz einfach überprüfen, ob ein Wallet ein Ticket aus Ihrer Sammlung enthält. Wenn jemand nachweisen kann, dass er eine Brieftasche mit einem unserer Tickets besitzt, ist das im Grunde ein Beweis dafür, dass er ein Ticket hat. Dies kann mithilfe digitaler Signaturen erreicht werden.
Noch ein Hinweis: Sobald Sie Ihren Smart Contract und Ihr Frontend haben (oder sogar bevor Ihr Frontend fertig ist und Sie beweisen möchten, dass alles funktioniert), können Sie die Infura NFT API verwenden, um zu überprüfen, ob Ihr neues NFT existiert. Die Infura NFT API ist eine schnelle Möglichkeit, eine Menge NFT-bezogenen Code mit einem einzigen API-Aufruf zu ersetzen.
Beispielsweise stehen uns die Informationen, die wir zum Nachweis des Eigentums an unserem NFT benötigen, über die API problemlos zur Verfügung. Wir müssen lediglich die Wallet-Adresse angeben. Der Code würde etwa so aussehen:
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));
Starte es …
$ node <filename>.js
Und Sie sollten etwa Folgendes sehen:
{ 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] }, … ] }
In diesem Tutorial haben wir einen voll funktionsfähigen NFT-Ticketing-Service mit Truffle , Infura und der Infura NFT API bereitgestellt.
Es ist natürlich nicht alles , was Sie brauchen, um Ticketmaster zu revolutionieren – aber es ist ein solider Anfang und ein großartiger Proof of Concept! Auch wenn Sie diesen Code nicht verwenden und Ihre eigene NFT-Ticketing-Plattform starten, haben Sie dabei hoffentlich etwas über web3 gelernt.
Das Hauptbild für diesen Artikel wurde vomAI Image Generator von HackerNoon über die Eingabeaufforderung „ein Rockkonzert in einem großen Stadion“ generiert.