Un caso de uso popular y práctico para las NFT es generar entradas para eventos en vivo. Las cadenas de bloques como Ethereum pueden garantizar la propiedad, el creador y la autenticidad de un artículo digital, resolviendo de manera efectiva el problema de los boletos falsificados. Mientras que los principales jugadores como Ticketmaster luchan por mitigar los revendedores (que intentan desesperadamente controlar quién puede revender entradas, dónde y por cuánto) y el fraude de entradas, web3 ya tiene una solución. La industria de venta de entradas está madura para la disrupción.
En este tutorial, veremos cómo crear una solución de emisión de boletos de este tipo utilizando ConsenSys Truffle , Infura y la API de Infura NFT. Implementaremos un contrato inteligente que actúa como un servicio de emisión de boletos y crea boletos como tokens no fungibles (NFT) ERC-20. También analizaremos algunas arquitecturas de interfaces potenciales que podrían interactuar con el contrato y funcionar juntos como un sistema de emisión de boletos web3 integrado y de pila completa.
¡Vamos a construir!
La arquitectura básica de nuestro sistema está destinada a crear un contrato inteligente que emite nuestros boletos como tokens no fungibles (NFT). Los NFT son perfectos para lo que queremos construir. Son tokens digitales demostrablemente únicos que nos permiten garantizar que cada boleto sea único y no se pueda copiar ni falsificar. Esto no solo garantiza una experiencia de venta de entradas segura para los asistentes al concierto, sino que también permite a los artistas (y organizadores de eventos) un mayor control sobre la distribución, el precio y la reventa de entradas. ¡El uso de contratos inteligentes y NFT incluso permite nuevas fuentes de ingresos, como pagos de regalías e ingresos compartidos!
(Si necesita información general sobre alguno de estos términos, la tecnología de cadena de bloques o web3 en general, consulte este artículo sobre Aprender a convertirse en un desarrollador de Web3 explorando la pila de Web3 ).
Lo primero que vamos a hacer es configurar una billetera MetaMask y agregarle la red de prueba Sepolia. MetaMask es la billetera digital con autocustodia más popular, segura y fácil de usar del mundo.
Primero, descarga la extensión MetaMask . Después de instalar la extensión, MetaMask configurará la billetera por usted. En el proceso, se le dará una frase secreta. Guárdelo en un lugar seguro, y bajo ninguna circunstancia debe hacerlo público.
Una vez que haya configurado MetaMask, haga clic en la pestaña Red en la esquina superior derecha. Verá una opción para mostrar/ocultar redes de prueba.
Una vez que active las redes de prueba, debería poder ver la red de prueba de Sepolia en el menú desplegable. Queremos usar la red Sepolia para poder implementar y probar nuestro sistema sin gastar dinero real.
Para implementar nuestro contrato inteligente e interactuar con él, necesitaremos una prueba gratuita de ETH. Puede obtener Sepolia ETH gratis del faucet de Sepolia .
Una vez que deposite fondos en su billetera, debería ver un saldo distinto de cero cuando cambie a la red de prueba Sepolia en MetaMask.
Como todas las dapps de Ethereum, construiremos nuestro proyecto usando node y npm. En caso de que no los tenga instalados en su máquina local, puede hacerlo aquí .
Para asegurarse de que todo funciona correctamente, ejecute el siguiente comando:
$ node -v
Si todo va bien, debería ver un número de versión para Node.
Para implementar nuestro contrato en la red Sepolia, necesitaremos una cuenta de Infura. Infura nos brinda acceso a puntos finales de RPC que permiten un acceso rápido, confiable y fácil a la cadena de bloques de nuestra elección.
Regístrese para obtener una cuenta gratuita . Una vez que haya creado su cuenta, navegue hasta el tablero y seleccione Crear nueva clave .
Para la red, elija Web3 API y asígnele el nombre Sistema de emisión de boletos , o algo de su elección.
Una vez que haga clic en Crear , Infura generará una clave de API para usted y le proporcionará puntos finales de RPC para Ethereum, Goerli, Sepolia, L2 y no EVM L1 (y sus redes de prueba correspondientes) automáticamente.
Para este tutorial, solo estamos interesados en el extremo RPC de Sepolia. Esta URL tiene el formato https://sepolia.infura.io/v3/←API KEY→
Configuremos un repositorio de proyecto vacío ejecutando los siguientes comandos:
$ mkdir nft-ticketing && cd nft-ticketing $ npm init -y
Usaremos Truffle, un entorno de desarrollo de clase mundial y un marco de prueba para contratos inteligentes EVM, para construir e implementar nuestro contrato inteligente de criptomonedas. Instale Truffle ejecutando:
$ npm install —save truffle
Ahora podemos crear un proyecto básico de Trufa ejecutando el siguiente comando:
$ npx truffle init
Para comprobar si todo funciona correctamente, ejecute:
$ npx truffle test
Ahora tenemos Truffle configurado con éxito. A continuación, instalemos el paquete de contratos de OpenZeppelin . Este paquete nos dará acceso a la implementación básica de ERC-721 (el estándar para tokens no fungibles), así como algunas funcionalidades adicionales útiles.
$ npm install @openzeppelin/contracts
Para permitir que Truffle use nuestra billetera MetaMask, firme transacciones y pague gasolina en nuestro nombre, necesitaremos otro paquete llamado hdwalletprovider
. Instálalo usando el siguiente comando:
$ npm install @truffle/hdwallet-provider
Finalmente, para mantener segura la información confidencial de nuestra billetera, utilizaremos el paquete dotenv
.
$ npm install dotenv
Abra el repositorio del proyecto en un editor de código (por ejemplo: VS Code). En la carpeta contracts
, cree un nuevo archivo llamado NftTicketing.sol
.
Nuestro contrato de venta de entradas heredará toda la funcionalidad que ofrece la implementación ERC721Enumerable
de OpenZeppelin. Esto incluye transferencias, seguimiento de metadatos, datos de propiedad, etc.
Implementaremos las siguientes características desde cero:
Agregue el siguiente código a 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); } }
Asegúrese de que el contrato se esté compilando correctamente ejecutando:
npx truffle compile
Nuestro contrato ya es bastante complejo, pero es posible agregar algunas funciones adicionales si lo considera oportuno.
Por ejemplo, puede implementar un mecanismo anti-scalping dentro de su contrato. Los pasos para hacerlo serían los siguientes:
Agrega el siguiente fragmento debajo del constructor del contrato:
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; } }
Finalmente, modifique la función _ beforeTokenTransfer a lo siguiente:
// 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); }
Compile el contrato una vez más usando el comando Truffle anterior.
Cree un nuevo archivo en el directorio raíz del proyecto llamado .env
y agregue los siguientes contenidos:
INFURA_API_KEY = "https://sepolia.infura.io/v3/<Your-API-Key>" MNEMONIC = "<Your-MetaMask-Secret-Recovery-Phrase>"
A continuación, agreguemos información sobre nuestra billetera, el punto final de Infura RPC y la red Sepolia a nuestro archivo de configuración de Truffle. Reemplace el contenido de truffle.config.js
con lo siguiente:
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', } } };
Ahora escribamos un script para implementar nuestro contrato en la cadena de bloques de Sepolia.
En la carpeta migrations
, cree un nuevo archivo llamado 1_deploy_contract.js
y agregue el siguiente código:
// 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!") };
¡Estamos listos! Implemente el contrato ejecutando el siguiente comando:
truffle migrate --network sepolia
Si todo va bien, debería ver un resultado (que contiene la dirección del contrato) similar a este:
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
Puede buscar la dirección de su contrato en Sepolia etherscan y verla en vivo.
¡Felicidades! Ha implementado correctamente el contrato en Sepolia.
Paso 9: Interfaz con el contrato inteligente
¡Tenemos nuestro contrato inteligente! El siguiente paso es implementar interfaces que interactúen con el contrato y permitan que cualquiera llame a la función de menta para hacer una donación y acuñar un boleto por sí mismos.
Para un servicio de emisión de boletos completamente funcional, normalmente necesitaría las siguientes interfaces:
Construir estos sistemas desde cero está fuera del alcance de este tutorial, pero le dejaremos algunos recursos y consejos.
Para el sitio web de acuñación de frontend, consulte el frontend que construí en el tutorial Thank You NFT como punto de partida.
Si verifica su contrato en Etherscan, automáticamente le dará un portal de administración donde puede llamar a cualquier función en su contrato. Este es un buen primer paso antes de decidirse por crear una solución personalizada.
Verificar que una billetera tiene un boleto de tu colección es extremadamente simple usando la función balanceOf
. Si alguien puede demostrar que posee una billetera que contiene uno de nuestros boletos, es básicamente una prueba de que tiene un boleto. Esto se puede lograr mediante firmas digitales.
Una pista más: una vez que tenga su contrato inteligente y su interfaz (o incluso antes de que su interfaz esté completa y quiera probar que todo funciona), puede usar la API de Infura NFT para verificar que su nuevo NFT existe. La API de Infura NFT es una forma rápida de reemplazar una gran cantidad de código relacionado con NFT con una sola llamada a la API.
Por ejemplo, la información que necesitamos para demostrar la propiedad de nuestro NFT está fácilmente disponible para nosotros a través de la API. Todo lo que necesitamos proporcionar es la dirección de la billetera. El código sería algo como esto:
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));
Ejecutarlo …
$ node <filename>.js
Y deberías ver algo como esto:
{ 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] }, … ] }
En este tutorial, implementamos un servicio de emisión de boletos NFT completamente funcional utilizando Truffle , Infura y la API Infura NFT .
Obviamente, no es todo lo que necesitarías para interrumpir Ticketmaster, ¡pero es un comienzo sólido y una gran prueba de concepto! Incluso si no toma este código y comienza su propia plataforma de emisión de boletos NFT, es de esperar que haya aprendido un poco sobre web3 en el proceso.
La imagen principal de este artículo fue generada porAI Image Generator de HackerNoon a través del mensaje "un concierto de rock en un gran estadio".