NFT'ler için popüler ve pratik bir kullanım örneği, canlı etkinliklere bilet oluşturmaktır. Ethereum gibi blok zincirleri, dijital bir öğenin sahipliğini, yaratıcısını ve orijinalliğini garanti ederek sahte bilet sorununu etkili bir şekilde çözebilir. Ticketmaster gibi büyük oyuncular, (biletleri kimin, nerede ve ne kadara yeniden satabileceğini çaresizce kontrol etmeye çalışırken) ve bilet dolandırıcılıklarını hafifletmeye çalışırken web3'ün zaten bir çözümü var. Bilet endüstrisi aksamaya hazır.
Bu eğitimde ConsenSys Truffle , Infura ve Infura NFT API'sini kullanarak böyle bir biletleme çözümünün nasıl oluşturulacağına bakacağız. Biletleme hizmeti görevi gören ve ERC-20 misli olmayan tokenler (NFT'ler) olarak biletler oluşturan akıllı bir sözleşme uygulayacağız. Ayrıca sözleşmeyle arayüz oluşturabilecek ve birlikte entegre, tam yığınlı bir web3 biletleme sistemi olarak işlev görebilecek birkaç potansiyel ön uç mimarisini de inceleyeceğiz.
Haydi inşaata başlayalım!
Sistemimizin temel mimarisi, biletlerimizi değiştirilemez tokenler (NFT'ler) olarak düzenleyen akıllı bir sözleşme oluşturmayı amaçlamaktadır. NFT'ler oluşturmak istediğimiz şey için mükemmeldir . Bunlar, her biletin benzersiz olduğundan ve kopyalanamayacağından veya taklit edilemeyeceğinden emin olmamızı sağlayan benzersiz dijital belirteçlerdir. Bu yalnızca konsere gidenler için güvenli bir bilet deneyimi sağlamakla kalmıyor, aynı zamanda sanatçılara (ve etkinlik organizatörlerine) bilet dağıtımı, fiyatlandırma ve yeniden satış üzerinde daha fazla kontrol sahibi olma olanağı sağlıyor. Akıllı sözleşmelerin ve NFT'lerin kullanılması, telif hakkı ödemeleri ve gelir paylaşımı gibi yeni gelir akışlarına bile olanak tanır!
(Bu terimlerden herhangi biri, blockchain teknolojisi veya genel olarak web3 hakkında arka plan bilgisine ihtiyacınız varsa, Web3 Stack k'yi Keşfederek Web3 Geliştiricisi Olmayı Öğrenmek hakkındaki bu makaleye göz atın).
Yapacağımız ilk şey bir MetaMask cüzdanı kurup ona Sepolia test ağını eklemek olacak. MetaMask, dünyanın en popüler, güvenli ve kullanımı kolay, kişisel saklama dijital cüzdanıdır.
Öncelikle MetaMask uzantısını indirin . Uzantıyı yükledikten sonra MetaMask sizin için cüzdanı ayarlayacaktır. Bu süreçte size gizli bir cümle verilecek. Bunu güvende tutun ve hiçbir durumda kamuya açıklamamalısınız.
MetaMask'ı kurduktan sonra sağ üstteki Ağ sekmesine tıklayın. Test ağlarını gösterme/gizleme seçeneğini göreceksiniz.
Test ağlarını açtığınızda açılır menüde Sepolia test ağını görebilmeniz gerekir. Herhangi bir gerçek para harcamadan sistemimizi dağıtabilmek ve test edebilmek için Sepolia ağını kullanmak istiyoruz.
Akıllı sözleşmemizi dağıtmak ve onunla etkileşime geçmek için bir miktar ücretsiz test ETH'ye ihtiyacımız olacak. Sepolia musluğundan ücretsiz Sepolia ETH elde edebilirsiniz.
Cüzdanınıza para yatırdıktan sonra MetaMask'ta Sepolia test ağına geçtiğinizde sıfırdan farklı bir bakiye görmelisiniz.
Tüm Ethereum dapp'leri gibi projemizi de node ve npm kullanarak oluşturacağız. Bunların yerel makinenizde yüklü olmaması durumunda, bunu buradan yapabilirsiniz.
Her şeyin doğru çalıştığından emin olmak için aşağıdaki komutu çalıştırın:
$ node -v
Her şey yolunda giderse Node.js için bir sürüm numarası görmelisiniz.
Sözleşmemizi Sepolia ağına dağıtmak için bir Infura hesabına ihtiyacımız olacak. Infura, tercih ettiğimiz blok zincirine hızlı, güvenilir ve kolay erişim sağlayan RPC uç noktalarına erişmemizi sağlıyor.
Ücretsiz bir hesap için kaydolun . Hesabınızı oluşturduktan sonra kontrol paneline gidin ve Yeni Anahtar Oluştur'u seçin.
Ağ için, Web3 API'yi seçin ve buna Biletleme Sistemi veya sizin seçtiğiniz bir ad verin.
Oluştur'a tıkladığınızda Infura sizin için bir API anahtarı oluşturacak ve otomatik olarak Ethereum, Goerli, Sepolia, L2'ler ve EVM olmayan L1'lere (ve bunlara karşılık gelen test ağlarına) RPC uç noktaları verecektir.
Bu eğitimde yalnızca Sepolia RPC uç noktasıyla ilgileniyoruz. Bu URL şu biçimdedir : https://sepolia.infura.io/v3/←API ANAHTARI→
Aşağıdaki komutları çalıştırarak boş bir proje deposu oluşturalım:
$ mkdir nft-ticketing && cd nft-ticketing $ npm init -y
Kripto para birimi akıllı sözleşmemizi oluşturmak ve dağıtmak için EVM akıllı sözleşmeleri için birinci sınıf bir geliştirme ortamı ve test çerçevesi olan Truffle'ı kullanacağız. Truffle'ı aşağıdakileri çalıştırarak yükleyin:
$ npm install —save truffle
Artık aşağıdaki komutu çalıştırarak bir barebone Truffle projesi oluşturabiliriz:
$ npx truffle init
Her şeyin düzgün çalışıp çalışmadığını kontrol etmek için şunu çalıştırın:
$ npx truffle test
Artık Truffle'ı başarıyla yapılandırdık. Şimdi OpenZeppelin sözleşme paketini kuralım. Bu paket bize ERC-721 temel uygulamasına (değişimi mümkün olmayan tokenlar için standart) ve birkaç yararlı ek işlevselliğe erişim sağlayacak.
$ npm install @openzeppelin/contracts
Truffle'ın MetaMask cüzdanımızı kullanmasına, işlemleri imzalamasına ve bizim adımıza gaz ödemesine izin vermek için hdwalletprovider
adı verilen başka bir pakete ihtiyacımız olacak. Aşağıdaki komutu kullanarak yükleyin:
$ npm install @truffle/hdwallet-provider
Son olarak hassas cüzdan bilgilerimizi güvende tutmak için dotenv
paketini kullanacağız.
$ npm install dotenv
Proje havuzunu bir kod düzenleyicide açın (örneğin: VS Code). contracts
klasöründe NftTicketing.sol
adında yeni bir dosya oluşturun.
Biletleme sözleşmemiz, OpenZeppelin'in ERC721Enumerable
uygulamasının sunduğu tüm işlevleri devralacaktır. Buna aktarımlar, meta veri izleme, sahiplik verileri vb. dahildir.
Aşağıdaki özellikleri sıfırdan uygulayacağız:
NftTicketing.sol
aşağıdaki kodu ekleyin.
//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); } }
Aşağıdakileri çalıştırarak sözleşmenin doğru şekilde derlendiğinden emin olun:
npx truffle compile
Sözleşmemiz zaten oldukça karmaşık, ancak uygun gördüğünüz bazı ekstra özellikler eklemek mümkün.
Örneğin, sözleşmenizde kafa derisinin soyulmasını önleyen bir mekanizma uygulayabilirsiniz. Bunu yapmak için gereken adımlar aşağıdaki gibi olacaktır:
Sözleşmenin yapıcısının altına aşağıdaki pasajı ekleyin:
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; } }
Son olarak, _ beforeTokenTranfer işlevini aşağıdaki şekilde değiştirin:
// 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); }
Yukarıdaki Truffle komutunu kullanarak sözleşmeyi bir kez daha derleyin.
Projenin kök dizininde .env
adında yeni bir dosya oluşturun ve aşağıdaki içerikleri ekleyin:
INFURA_API_KEY = "https://sepolia.infura.io/v3/<Your-API-Key>" MNEMONIC = "<Your-MetaMask-Secret-Recovery-Phrase>"
Daha sonra Truffle yapılandırma dosyamıza cüzdanımız, Infura RPC uç noktası ve Sepolia ağı hakkındaki bilgileri ekleyelim. truffle.config.js
içeriğini aşağıdakiyle değiştirin:
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', } } };
Şimdi sözleşmemizi Sepolia blok zincirine dağıtmak için bir komut dosyası yazalım.
migrations
klasöründe 1_deploy_contract.js
adında yeni bir dosya oluşturun ve aşağıdaki kodu ekleyin:
// 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!") };
Hepimiz hazırız! Aşağıdaki komutu çalıştırarak sözleşmeyi dağıtın:
truffle migrate --network sepolia
Her şey yolunda giderse, şuna benzer bir çıktı (sözleşme adresini içeren) görmelisiniz:
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
Sözleşme adresinizi Sepolia etherscan üzerinden aratıp canlı olarak görebilirsiniz.
Tebrikler! Sözleşmeyi Sepolia'ya başarıyla dağıttınız.
Adım 9: Akıllı sözleşmeyle arayüz
Akıllı sözleşmemiz var! Bir sonraki adım, sözleşmeyle arayüz oluşturan ön uçları dağıtmak ve herkesin bağış yapmak ve kendileri için bir bilet basmak için darphane işlevini çağırmasına izin vermektir.
Tamamen işlevsel bir biletleme hizmeti için genellikle aşağıdaki ön uçlara ihtiyacınız olacaktır:
Bu sistemleri sıfırdan oluşturmak bu eğitimin kapsamı dışındadır, ancak size birkaç kaynak ve ipucu bırakacağız.
Ön uç basımı web sitesi için, başlangıç noktası olarak Teşekkür Ederim NFT eğitiminde oluşturduğum ön uca göz atın.
Etherscan'de sözleşmenizi doğrularsanız, otomatik olarak size sözleşmenizdeki herhangi bir işlevi arayabileceğiniz bir yönetici portalı verecektir. Bu, özel bir çözüm oluşturmaya karar vermeden önce iyi bir ilk adımdır.
balanceOf
işlevini kullanarak bir cüzdanın koleksiyonunuzdan bir bilete sahip olduğunu doğrulamak son derece basittir. Birisi bizim biletlerimizden birinin bulunduğu bir cüzdana sahip olduğunu kanıtlayabilirse, bu aslında onun bir bilete sahip olduğunun kanıtıdır. Bu, dijital imzalar kullanılarak gerçekleştirilebilir.
Bir ipucu daha: Akıllı sözleşmenizi ve ön ucunuzu aldıktan sonra (veya hatta ön ucunuz tamamlanmadan ve her şeyin işe yaradığını kanıtlamak istediğinizde), yeni NFT'nizin var olduğunu doğrulamak için Infura NFT API'sini kullanabilirsiniz. Infura NFT API, NFT ile ilgili birçok kodu tek bir API çağrısıyla değiştirmenin hızlı bir yoludur.
Örneğin, NFT'mizin sahipliğini göstermek için ihtiyaç duyduğumuz bilgilere API aracılığıyla kolaylıkla ulaşabiliyoruz. Sağlamamız gereken tek şey cüzdan adresidir. Kod şuna benzer:
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));
Çalıştır…
$ node <filename>.js
Ve şöyle bir şey görmelisiniz:
{ 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] }, … ] }
Bu eğitimde Truffle , Infura ve Infura NFT API'sini kullanarak tamamen işlevsel bir NFT biletleme hizmetini devreye aldık.
Tabii ki Ticketmaster'ı sekteye uğratmak için ihtiyaç duyacağınız her şey bu değil; ama sağlam bir başlangıç ve konseptin harika bir kanıtı! Bu kodu alıp kendi NFT biletleme platformunuzu kurmasanız bile bu süreçte web3 hakkında biraz bilgi sahibi olduğunuzu umarız.
Bu makalenin ana görseli HackerNoon'unAI Image Generator'ı tarafından "büyük bir stadyumda bir rock konseri" istemi aracılığıyla oluşturuldu.