Un cas d'utilisation populaire et pratique pour les NFT est la génération de billets pour des événements en direct. Les chaînes de blocs telles qu'Ethereum peuvent garantir la propriété, l'auteur et l'authenticité d'un article numérique, résolvant efficacement le problème des billets contrefaits. Alors que des acteurs majeurs tels que Ticketmaster luttent pour atténuer les scalpers (essayant désespérément de contrôler qui peut revendre des billets, où et pour combien) et la fraude aux billets, web3 a déjà une solution. L'industrie de la billetterie est mûre pour les perturbations.
Dans ce didacticiel, nous verrons comment créer une telle solution de billetterie à l'aide de ConsenSys Truffle , Infura et de l' API Infura NFT. Nous allons déployer un contrat intelligent qui agit comme un service de billetterie et crée des billets sous forme de jetons non fongibles (NFT) ERC-20. Nous passerons également en revue quelques architectures d'interfaces potentielles qui pourraient s'interfacer avec le contrat et fonctionner ensemble comme un système de billetterie Web3 intégré et complet.
Commençons à construire !
L'architecture de base de notre système est destinée à créer un contrat intelligent qui émet nos tickets sous forme de jetons non fongibles (NFT). Les NFT sont parfaits pour ce que nous voulons construire. Ce sont des jetons numériques uniques qui nous permettent de garantir que chaque billet est unique et ne peut être copié ou falsifié. Cela garantit non seulement une expérience de billetterie sécurisée pour les spectateurs, mais donne également aux artistes (et aux organisateurs d'événements) un meilleur contrôle sur la distribution, la tarification et la revente des billets. L'utilisation de contrats intelligents et de NFT permet même de nouvelles sources de revenus telles que les paiements de redevances et le partage des revenus !
(Si vous avez besoin d'informations générales sur l'un de ces termes, la technologie blockchain ou le Web3 en général, consultez cet article sur Apprendre à devenir un développeur Web3 en explorant la pile Web3 ).
La première chose que nous allons faire est de configurer un portefeuille MetaMask et d'y ajouter le réseau de test Sepolia. MetaMask est le portefeuille numérique auto-dépositaire le plus populaire, le plus sécurisé et le plus facile à utiliser au monde.
Tout d'abord, téléchargez l'extension MetaMask . Après avoir installé l'extension, MetaMask configurera le portefeuille pour vous. Dans le processus, vous recevrez une phrase secrète. Conservez-le précieusement et ne le rendez en aucun cas public.
Une fois que vous avez configuré MetaMask, cliquez sur l'onglet Réseau en haut à droite. Vous verrez une option pour afficher/masquer les réseaux de test.
Une fois que vous avez activé les réseaux de test, vous devriez pouvoir voir le réseau de test Sepolia dans le menu déroulant. Nous voulons utiliser le réseau Sepolia afin de pouvoir déployer et tester notre système sans dépenser d'argent réel.
Afin de déployer notre contrat intelligent et d'interagir avec lui, nous aurons besoin d'ETH de test gratuit. Vous pouvez obtenir gratuitement Sepolia ETH à partir du robinet Sepolia .
Une fois que vous avez financé votre portefeuille, vous devriez voir un solde non nul lorsque vous passez au réseau de test Sepolia sur MetaMask.
Comme tous les dapps Ethereum, nous allons construire notre projet en utilisant node et npm. Si vous ne les avez pas installés sur votre machine locale, vous pouvez le faire ici .
Pour vous assurer que tout fonctionne correctement, exécutez la commande suivante :
$ node -v
Si tout se passe bien, vous devriez voir un numéro de version pour Node.
Afin de déployer notre contrat sur le réseau Sepolia, nous aurons besoin d'un compte Infura. Infura nous donne accès aux points de terminaison RPC qui permettent un accès rapide, fiable et facile à la blockchain de notre choix.
Inscrivez-vous pour un compte gratuit . Une fois que vous avez créé votre compte, accédez au tableau de bord et sélectionnez Créer une nouvelle clé .
Pour le réseau, choisissez API Web3 et nommez-le Ticketing System , ou quelque chose de votre choix.
Une fois que vous aurez cliqué sur Créer , Infura générera une clé API pour vous et vous donnera automatiquement des points de terminaison RPC vers Ethereum, Goerli, Sepolia, L2 et L1 non EVM (et leurs réseaux de test correspondants).
Pour ce didacticiel, nous nous intéressons uniquement au point de terminaison Sepolia RPC. Cette URL est de la forme https://sepolia.infura.io/v3/←API KEY→
Configurons un dépôt de projet vide en exécutant les commandes suivantes :
$ mkdir nft-ticketing && cd nft-ticketing $ npm init -y
Nous utiliserons Truffle, un environnement de développement et un cadre de test de classe mondiale pour les contrats intelligents EVM, pour créer et déployer notre contrat intelligent de crypto-monnaie. Installez Truffle en exécutant :
$ npm install —save truffle
Nous pouvons maintenant créer un projet Truffle barebones en exécutant la commande suivante :
$ npx truffle init
Pour vérifier si tout fonctionne correctement, exécutez :
$ npx truffle test
Nous avons maintenant Truffle configuré avec succès. Installons ensuite le package de contrats OpenZeppelin . Ce package nous donnera accès à l'implémentation de base ERC-721 (la norme pour les jetons non fongibles) ainsi qu'à quelques fonctionnalités supplémentaires utiles.
$ npm install @openzeppelin/contracts
Pour permettre à Truffle d'utiliser notre portefeuille MetaMask, de signer des transactions et de payer de l'essence en notre nom, nous aurons besoin d'un autre package appelé hdwalletprovider
. Installez-le en utilisant la commande suivante :
$ npm install @truffle/hdwallet-provider
Enfin, afin de protéger les informations sensibles de notre portefeuille, nous utiliserons le package dotenv
.
$ npm install dotenv
Ouvrez le référentiel du projet dans un éditeur de code (par exemple : VS Code). Dans le dossier contracts
, créez un nouveau fichier nommé NftTicketing.sol
.
Notre contrat de billetterie héritera de toutes les fonctionnalités offertes par l'implémentation ERC721Enumerable
d'OpenZeppelin. Cela inclut les transferts, le suivi des métadonnées, les données de propriété, etc.
Nous allons implémenter les fonctionnalités suivantes à partir de zéro :
Ajoutez le code suivant à 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); } }
Assurez-vous que le contrat se compile correctement en exécutant :
npx truffle compile
Notre contrat est déjà assez complexe, mais il est possible d'ajouter quelques fonctionnalités supplémentaires comme bon vous semble.
Par exemple, vous pouvez mettre en place un mécanisme anti-scalping au sein de votre contrat. Les étapes pour ce faire seraient les suivantes :
Ajoutez l'extrait de code suivant sous le constructeur du contrat :
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; } }
Enfin, modifiez la fonction _ beforeTokenTranfer comme suit :
// 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); }
Compilez à nouveau le contrat en utilisant la commande Truffle ci-dessus.
Créez un nouveau fichier dans le répertoire racine du projet appelé .env
et ajoutez le contenu suivant :
INFURA_API_KEY = "https://sepolia.infura.io/v3/<Your-API-Key>" MNEMONIC = "<Your-MetaMask-Secret-Recovery-Phrase>"
Ensuite, ajoutons des informations sur notre portefeuille, le point de terminaison Infura RPC et le réseau Sepolia à notre fichier de configuration Truffle. Remplacez le contenu de truffle.config.js
par ce qui suit :
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', } } };
Écrivons maintenant un script pour déployer notre contrat sur la blockchain Sepolia.
Dans le dossier migrations
, créez un nouveau fichier appelé 1_deploy_contract.js
et ajoutez le code suivant :
// 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!") };
Nous sommes prêts ! Déployez le contrat en exécutant la commande suivante :
truffle migrate --network sepolia
Si tout se passe bien, vous devriez voir une sortie (contenant l'adresse du contrat) qui ressemble à ceci :
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
Vous pouvez rechercher votre adresse de contrat sur Sepolia etherscan et la voir en direct.
Toutes nos félicitations! Vous avez déployé avec succès le contrat sur Sepolia.
Étape 9 : Interface avec le smart contract
Nous avons notre contrat intelligent ! L'étape suivante consiste à déployer des interfaces qui s'interfacent avec le contrat et permettent à quiconque d'appeler la fonction mint pour faire un don et créer un ticket pour lui-même.
Pour un service de billetterie entièrement fonctionnel, vous aurez généralement besoin des interfaces suivantes :
Construire ces systèmes à partir de zéro est hors de portée de ce didacticiel, mais nous vous laisserons quelques ressources et conseils.
Pour le site Web de frappe frontale, consultez le frontend que j'ai construit dans le didacticiel Merci NFT comme point de départ.
Si vous vérifiez votre contrat sur Etherscan, il vous donnera automatiquement un portail d'administration où vous pourrez appeler n'importe quelle fonction de votre contrat. C'est une bonne première étape avant de décider de créer une solution personnalisée.
Vérifier qu'un portefeuille a un ticket de votre collection est extrêmement simple en utilisant la fonction balanceOf
. Si quelqu'un peut prouver qu'il possède un portefeuille contenant l'un de nos billets, c'est essentiellement la preuve qu'il a un billet. Ceci peut être réalisé en utilisant des signatures numériques.
Un autre indice : une fois que vous avez votre contrat intelligent et votre interface (ou même avant que votre interface soit terminée et que vous vouliez prouver que tout fonctionne), vous pouvez utiliser l' API Infura NFT pour vérifier que votre nouveau NFT existe. L'API Infura NFT est un moyen rapide de remplacer une grande quantité de code lié à NFT par un seul appel d'API.
Par exemple, les informations dont nous avons besoin pour montrer la propriété de notre NFT nous sont facilement accessibles via l'API. Tout ce que nous devons fournir est l'adresse du portefeuille. Le code ressemblerait à ceci :
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));
Exécuter …
$ node <filename>.js
Et vous devriez voir quelque chose comme ça :
{ 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] }, … ] }
Dans ce didacticiel, nous avons déployé un service de billetterie NFT entièrement fonctionnel à l'aide de Truffle , Infura et de l' API Infura NFT .
Ce n'est évidemment pas tout ce dont vous auriez besoin pour perturber Ticketmaster, mais c'est un bon début et une excellente preuve de concept ! Même si vous ne prenez pas ce code et ne démarrez pas votre propre plate-forme de billetterie NFT, j'espère que vous en avez appris un peu plus sur Web3 au cours du processus.
L'image principale de cet article a été générée parle générateur d'images AI de HackerNoon via l'invite "un concert de rock dans un grand stade".