paint-brush
Web3 티켓팅 시스템을 구축하여 Ticketmaster를 혼란에 빠뜨리자by@MichaelB
2,147
2,147

Web3 티켓팅 시스템을 구축하여 Ticketmaster를 혼란에 빠뜨리자

Michael17m2023/04/29
Read on Terminal Reader
Read this story w/o Javascript

NFT의 인기 있고 실용적인 사용 사례는 라이브 이벤트 티켓을 생성하는 것입니다. 우리는 티켓팅 서비스 역할을 하고 ERC-20 대체 불가능한 토큰(NFT)으로 티켓을 생성하는 스마트 계약을 배포할 것입니다. 스마트 계약과 N FT를 사용하면 로열티 지불 및 수익 공유와 같은 새로운 수익원도 가능해집니다.
featured image - Web3 티켓팅 시스템을 구축하여 Ticketmaster를 혼란에 빠뜨리자
Michael HackerNoon profile picture
0-item
1-item
2-item

NFT의 인기 있고 실용적인 사용 사례는 라이브 이벤트 티켓을 생성하는 것입니다. 이더리움 과 같은 블록체인은 디지털 아이템의 소유권, 작성자, 진위 여부를 보장하여 위조 티켓 문제를 효과적으로 해결할 수 있습니다. Ticketmaster 와 같은 주요 업체는 스캘퍼(티켓을 재판매할 수 있는 사람, 장소 및 금액을 통제하기 위해 필사적으로 노력함)와 티켓 사기를 완화하기 위해 고군분투하고 있지만 web3 에는 이미 솔루션이 있습니다. 티켓 산업은 혼란에 빠질 준비가 되어 있습니다.


이 튜토리얼에서는 ConsenSys Truffle , Infura 및 Infura NFT API를 사용하여 이러한 티켓팅 솔루션을 만드는 방법을 살펴보겠습니다. 우리는 티켓팅 서비스 역할을 하고 ERC-20 대체 불가능한 토큰(NFT)으로 티켓을 생성하는 스마트 계약을 배포할 것입니다. 또한 계약과 인터페이스하고 통합된 전체 스택 web3 티켓팅 시스템으로 함께 작동할 수 있는 잠재적인 프런트엔드의 몇 가지 아키텍처를 살펴보겠습니다.


건물을 짓자!

Ethereum에서 NFT 티켓팅 시스템 만들기

우리 시스템의 기본 아키텍처는 티켓을 대체 불가능한 토큰(NFT)으로 발행하는 스마트 계약을 생성하기 위한 것입니다. NFT는 우리가 만들고 싶은 것에 완벽 합니다. 이는 모든 티켓이 고유하고 복사되거나 위조될 수 없음을 보장할 수 있는 고유한 디지털 토큰입니다. 이는 콘서트 참석자들에게 안전한 티켓팅 경험을 보장할 뿐만 아니라 아티스트(및 이벤트 주최자)가 티켓 배포, 가격 책정 및 재판매에 대한 더 큰 통제권을 갖도록 해줍니다. 스마트 계약 및 NFT를 사용하면 로열티 지불 및 수익 공유와 같은 새로운 수익원도 가능합니다!


(이러한 용어, 블록체인 기술 또는 일반적인 web3에 대한 배경 정보가 필요한 경우 Web3 스택 탐색을 통해 Web3 개발자가 되는 방법 배우기에 대한 이 기사를 확인하세요.)

1단계: 메타마스크 설치

가장 먼저 할 일은 MetaMask 지갑을 설정하고 여기에 Sepolia 테스트 네트워크를 추가하는 것입니다. MetaMask는 세계에서 가장 인기 있고 안전하며 사용하기 쉬운 자체 관리형 디지털 지갑입니다.

먼저 MetaMask 확장 프로그램을 다운로드하세요 . 확장 기능을 설치하면 MetaMask가 지갑을 설정해 줍니다. 이 과정에서 비밀 문구를 받게 됩니다. 안전하게 보관하고 어떠한 경우에도 공개해서는 안 됩니다.


MetaMask를 설정한 후 오른쪽 상단의 네트워크 탭을 클릭하세요. 테스트 네트워크를 표시하거나 숨기는 옵션이 표시됩니다.


테스트 네트워크를 켜면 드롭다운 메뉴에서 Sepolia 테스트 네트워크를 볼 수 있습니다. 우리는 실제 비용을 지출하지 않고도 시스템을 배포하고 테스트할 수 있도록 Sepolia 네트워크를 사용하고 싶습니다.

2단계: 테스트 ETH 받기

스마트 계약을 배포하고 상호 작용하려면 무료 테스트 ETH가 필요합니다. Seplia Faucet 에서 무료로 Sepolia ETH를 얻을 수 있습니다.


지갑에 자금을 입금한 후 MetaMask에서 Sepolia 테스트 네트워크로 전환하면 잔액이 0이 아닌 것으로 표시되어야 합니다.



3단계: NPM 및 노드 설치

모든 Ethereum dapp과 마찬가지로 우리는 node와 npm을 사용하여 프로젝트를 구축할 것입니다. 로컬 컴퓨터에 이러한 프로그램이 설치되어 있지 않은 경우 여기에서 설치할 수 있습니다.


모든 것이 올바르게 작동하는지 확인하려면 다음 명령을 실행하십시오.

 $ node -v


모든 것이 순조롭게 진행되면 Node.js의 버전 번호가 표시됩니다.

4단계: Infura 계정에 가입하세요

계약을 Sepolia 네트워크에 배포하려면 Infura 계정이 필요합니다. Infura는 우리가 선택한 블록체인에 빠르고 안정적이며 쉽게 액세스할 수 있는 RPC 엔드포인트에 대한 액세스를 제공합니다.


무료 계정에 가입하세요 . 계정을 만든 후 대시보드 로 이동하여 새 키 만들기를 선택하세요.



네트워크의 경우 Web3 API를 선택하고 이름을 Ticketing System 또는 원하는 대로 지정합니다.


Create 를 클릭하면 Infura가 API 키를 생성하고 Ethereum, Goerli, Seplia, 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을 사용하여 암호화폐 스마트 계약을 구축하고 배포할 것입니다. 다음을 실행하여 Truffle을 설치합니다.

 $ npm install —save truffle


이제 다음 명령을 실행하여 베어본 Truffle 프로젝트를 생성할 수 있습니다.

 $ npx truffle init


모든 것이 제대로 작동하는지 확인하려면 다음을 실행하세요.

 $ npx truffle test


이제 Truffle이 성공적으로 구성되었습니다. 다음으로 OpenZeppelin 계약 패키지를 설치해 보겠습니다. 이 패키지를 사용하면 ERC-721 기본 구현(대체 불가능한 토큰의 표준)과 몇 가지 유용한 추가 기능에 액세스할 수 있습니다.

 $ npm install @openzeppelin/contracts


Truffle이 MetaMask 지갑을 사용하고, 거래에 서명하고, 가스를 지불할 수 있도록 하려면 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', } } };

8단계: NFT 스마트 계약 배포

이제 계약을 Sepalia 블록체인에 배포하는 스크립트를 작성해 보겠습니다.


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에서 계약 주소를 검색하여 실시간으로 보실 수 있습니다.

축하해요! 계약을 Sepalia에 성공적으로 배포했습니다.


9단계: 스마트 계약과의 인터페이스


우리는 스마트 계약을 맺었습니다! 다음 단계는 컨트랙트와 인터페이스하는 프런트엔드를 배포하고 누구나 민트 기능을 호출하여 기부하고 티켓을 발행할 수 있도록 하는 것입니다.


완전한 기능의 티켓팅 서비스를 위해서는 일반적으로 다음 프런트엔드가 필요합니다.

  1. 일반 사용자가 티켓을 지불하고 발행할 수 있는 (훌륭한 사용자 경험을 갖춘) 웹사이트입니다.
  2. 소유자가 티켓 예약 및 에어드롭, 가격 업데이트, 관리 역할을 다른 지갑으로 이전, 판매 수익 인출, 판매 개시 및 종료 등을 할 수 있는 관리 포털입니다.
  3. 개인이 온라인과 IRL 모두에서 특정 티켓을 가지고 있는지 확인하는 도구입니다.


이러한 시스템을 처음부터 구축하는 것은 이 튜토리얼의 범위를 벗어나지만 몇 가지 리소스와 팁을 남겨드리겠습니다.

  1. 프론트엔드 채굴 웹사이트의 경우, Thank You NFT 튜토리얼에서 제가 구축한 프론트엔드를 출발점으로 확인하세요.

  2. Etherscan에서 계약을 확인하면 계약의 모든 기능을 호출할 수 있는 관리 포털이 자동으로 제공됩니다. 이는 맞춤형 솔루션 구축을 결정하기 전의 좋은 첫 번째 단계입니다.

  3. 지갑에 컬렉션의 티켓이 있는지 확인하는 것은 balanceOf 기능을 사용하여 매우 간단합니다. 누군가가 우리 티켓 중 하나가 들어 있는 지갑을 소유하고 있음을 증명할 수 있다면 이는 기본적으로 티켓이 있다는 증거입니다. 이는 디지털 서명을 사용하여 달성할 수 있습니다.


Infura NFT API를 사용한 검증

힌트 하나 더: 스마트 계약과 프런트엔드가 있으면(또는 프런트엔드가 완성되어 모든 것이 작동하는지 확인하기 전) Infura NFT API를 사용하여 새로운 NFT가 존재하는지 확인할 수 있습니다. Infura NFT API는 많은 NFT 관련 코드를 단일 API 호출로 대체하는 빠른 방법입니다.


예를 들어, NFT의 소유권을 보여주기 위해 필요한 정보는 API를 통해 쉽게 사용할 수 있습니다. 우리가 제공해야 할 것은 지갑 주소뿐입니다. 코드는 다음과 같습니다.

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

결론

이 튜토리얼에서는 Truffle , Infura 및 Infura NFT API 를 사용하여 완전한 기능을 갖춘 NFT 티켓팅 서비스를 배포했습니다.


분명히 Ticketmaster를 혼란에 빠뜨리는 데 필요한 전부는 아니지만 확실한 시작이자 훌륭한 개념 증명입니다! 이 코드를 사용하지 않고 자신만의 NFT 티켓팅 플랫폼을 시작하더라도 그 과정에서 web3에 대해 조금 배웠기를 바랍니다.


이 기사의 리드 이미지는 HackerNoon의AI 이미지 생성기 로 "큰 경기장의 록 콘서트"라는 메시지를 통해 생성되었습니다.