박주니 개발 정리

공연 티켓 nft 배포 (nft 마켓 기준) 본문

블록체인

공연 티켓 nft 배포 (nft 마켓 기준)

박주니 2023. 3. 17. 11:38
728x90
반응형

적용 이유) 공연 티켓 nft 배포 발행 기준으로만 만들고나서 예를 들어 티켓을 5개를 erc721기준으로 한번에 tokenId 5개 기준으로 좌석이랑 qrcode를 넣는거까지는 되었고 확인도 되었지만 구매자가 구매를 하기 위해서는 그것에 따른 abi 추가도 이루어져야한다는 것을 알게 되었습니다. 그래서 nft 마켓 기준으로 만든다고 가정했을 때 솔리디티 리뉴얼한 것을 올리겠습니다. 그 이후에 내용은 공연티켓 nft 배포(발행 기준)과 동일합니다. 

 

remix에 적용할 performanceNFT

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";

contract PerformanceNFT is ERC721 {
    using ECDSA for bytes32;

    struct Ticket {
        uint256 tokenId;
        string seatNumber;
        string qrCode;
    }

    Ticket[] public tickets;
    mapping(uint256 => bool) private _tokenExists;
    mapping(uint256 => uint256) public ticketPrices;
    address private _admin;

    // DebugLog 이벤트 추가
    event DebugLog(string message, address value1, uint256 value2);

    constructor(string memory name, string memory symbol) ERC721(name, symbol) {
        _admin = msg.sender;
    }

    modifier onlyAdmin() {
        require(msg.sender == _admin, "Only admin can call this function");
        _;
    }

    function mintTickets(
        string[] memory seatNumbers,
        string[] memory qrCodes,
        uint256[] memory prices
    ) public onlyAdmin {
        require(
            seatNumbers.length == qrCodes.length &&
                seatNumbers.length == prices.length,
            "Arrays must have same length"
        );
        for (uint256 i = 0; i < seatNumbers.length; i++) {
            require(!_tokenExists[tickets.length], "Token already exists");
            Ticket memory newTicket = Ticket({
                tokenId: tickets.length,
                seatNumber: seatNumbers[i],
                qrCode: qrCodes[i]
            });
            tickets.push(newTicket);
            ticketPrices[newTicket.tokenId] = prices[i];
            _safeMint(msg.sender, newTicket.tokenId);
            _tokenExists[newTicket.tokenId] = true;
        }
    }

    function getSeatNumber(uint256 tokenId) public view returns (string memory) {
        require(_exists(tokenId), "Token does not exist");
        return tickets[tokenId].seatNumber;
    }

    function getQrCode(uint256 tokenId) public view returns (string memory) {
        require(_exists(tokenId), "Token does not exist");
        return tickets[tokenId].qrCode;
    }

function purchaseNFT(
    address buyer,
    uint256 tokenId,
    bytes calldata signature
) external payable {
    emit DebugLog("Function called", buyer, tokenId);

    require(_exists(tokenId), "Token does not exist");
    emit DebugLog("Token exists", buyer, tokenId);

    uint256 price = ticketPrices[tokenId];
    require(msg.value >= price, "Insufficient funds");
    emit DebugLog("Funds sufficient", buyer, tokenId);

    address seller = ownerOf(tokenId);
    bytes32 message = keccak256(
        abi.encodePacked(
            seller,
            tokenId,
            address(this)
        )
    ).toEthSignedMessageHash();
    address signer = message.recover(signature);
    require(signer == seller, "Invalid signature");
    emit DebugLog("Signature valid", buyer, tokenId);

    _transfer(seller, buyer, tokenId);
    payable(seller).transfer(price);

    if (msg.value > price) {
        payable(buyer).transfer(msg.value - price);
    }

    emit DebugLog("Purchase successful", buyer, tokenId);
}
}

추가 설명) 여기서 가장 주의해야할 부분은 signature 입니다. purchaseNFT class를 보면 bytes32 message = keccak256(...)으로 된 부분을 확인하실 수 있습니다. 이부분에서 현재 keccak256을 사용했기 때문에 발행자가 signature을 만들때에도 사용하는 방법도 달라지게 됩니다. 자세한 내용은 적용 방법에서 다시 설명하겠습니다. 

 

signature 구성

    bytes32 message = keccak256(
        abi.encodePacked(
            seller,
            tokenId,
            address(this)
        )
    ).toEthSignedMessageHash();

 여기서 signature 구성을 보면 seller, tokenId, address(스마트컨트렉트 주소)로 되어있는 것을 볼 수 있습니다. 만약 순서가 지켜지지 않으면 purchaseNFT를 동작했을 때 evm 에러가 나오는 것을 볼 수 있습니다. 물론 evm 에러에는 가스문제나 여러 문제가 있지만 제가 이것을 동작할 때에는 signature 구성에 문제를 파악하지 못해서 일어난 일이였기에 주의해주시길 바랍니다. 이 내용을 강조한 이유는 적용하는 부분에 다시 설명드리겠습니다. 

 

 

728x90
반응형
Comments