{"sources":{"PilotBoardingPass.sol":{"content":"// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.24;\r\n\r\n/**\r\n * @title Pilot Boarding Pass NFT\r\n * @notice Official contract of the Pilot Boarding Pass collection on BNB Chain.\r\n * @dev Verified source. All rights reserved by Pilot Team.\r\n *\r\n * Official Links:\r\n *  - Website: https://pilotagent.org\r\n */\r\n\r\n// Minimal interface for ERC20 recovery\r\ninterface IERC20 {\r\n    function transfer(address to, uint256 amount) external returns (bool);\r\n    function balanceOf(address account) external view returns (uint256);\r\n}\r\n\r\n// ERC165 interface for introspection\r\ninterface IERC165 {\r\n    function supportsInterface(bytes4 interfaceId) external view returns (bool);\r\n}\r\n\r\n// Minimal ERC721 interface for marketplaces\r\ninterface IERC721 is IERC165 {\r\n    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\r\n    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);\r\n    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);\r\n    function balanceOf(address owner) external view returns (uint256 balance);\r\n    function ownerOf(uint256 tokenId) external view returns (address owner);\r\n    function safeTransferFrom(address from, address to, uint256 tokenId) external;\r\n    function transferFrom(address from, address to, uint256 tokenId) external;\r\n    function approve(address to, uint256 tokenId) external;\r\n    function getApproved(uint256 tokenId) external view returns (address operator);\r\n    function setApprovalForAll(address operator, bool _approved) external;\r\n    function isApprovedForAll(address owner, address operator) external view returns (bool);\r\n    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;\r\n}\r\n\r\ninterface IERC721Metadata {\r\n    function name() external view returns (string memory);\r\n    function symbol() external view returns (string memory);\r\n    function tokenURI(uint256 tokenId) external view returns (string memory);\r\n}\r\n\r\ninterface IERC721Receiver {\r\n    function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns (bytes4);\r\n}\r\n\r\n/**\r\n * @title PilotBoardingPass\r\n * @notice Soulbound NFT boarding pass with dynamic miles and metadata\r\n * @dev Flight PX001 - The journey begins here\r\n * Implements ERC-721 with transfer restrictions and ERC-5192 for soulbound tokens\r\n */\r\ncontract PilotBoardingPass is IERC721, IERC721Metadata {\r\n    \r\n    // ============ Structs ============\r\n    \r\n    struct BoardingPass {\r\n        uint256 seatRow;        // 1, 2, 3...\r\n        string seatLetter;      // A, B, C, D, E\r\n        uint256 miles;          // Current miles balance\r\n        uint256 milesEarned;    // Total miles earned\r\n        uint256 milesSpent;     // Total miles spent\r\n        uint256 mintedAt;       // Mint timestamp\r\n        uint256 lastUpdated;    // Last update timestamp\r\n        uint256 bannedUntil;    // Ban expiry timestamp (0 = not banned)\r\n        mapping(bytes32 => string) customText;   // Extensible text fields\r\n        mapping(bytes32 => uint256) customNumber; // Extensible number fields\r\n    }\r\n    \r\n    // ============ State Variables ============\r\n    \r\n    string public override name = \"Pilot Boarding Pass\";\r\n    string public override symbol = \"PX001\";\r\n    string public baseURI = \"\";\r\n    string public staticFilename = \"NFT001.json\";\r\n    bool public individualURI = false; // true: each token has its own URI\r\n    string public contractMetadataURI;\r\n\r\n\r\n    uint256 public totalSupply;\r\n    uint256 public mintPrice = 0; // Initially free\r\n    uint256 public initialMiles = 100; // Bonus miles on mint\r\n    bool public mintingActive = true;\r\n    \r\n    // Flexible tier system\r\n    uint256[] public tierThresholds;\r\n    \r\n    // Ownership and roles\r\n    address public owner;\r\n    mapping(address => bool) public isAdmin;\r\n    mapping(address => bool) public isMilesOperator;\r\n    mapping(address => bool) public isEditor;\r\n    \r\n    // NFT data\r\n    mapping(uint256 => address) private _owners;\r\n    mapping(address => uint256) private _balances;\r\n    \r\n    // Boarding pass data\r\n    mapping(uint256 => BoardingPass) public boardingPasses;\r\n    mapping(address => uint256) public addressToTokenId;\r\n    mapping(address => bool) public hasMinted;\r\n    \r\n    // ============ Events ============\r\n    \r\n    event MilesUpdated(uint256 indexed tokenId, uint256 oldMiles, uint256 newMiles);\r\n    event UserBanned(uint256 indexed tokenId, uint256 until);\r\n    event UserUnbanned(uint256 indexed tokenId);\r\n    event MetadataUpdated(uint256 indexed tokenId, string field);\r\n    event MetadataUpdate(uint256 _tokenId);\r\n    event BatchMetadataUpdate(uint256 _fromTokenId, uint256 _toTokenId);\r\n    event AdminAdded(address indexed admin);\r\n    event AdminRemoved(address indexed admin);\r\n    event MilesOperatorAdded(address indexed operator);\r\n    event MilesOperatorRemoved(address indexed operator);\r\n    event EditorAdded(address indexed editor);\r\n    event EditorRemoved(address indexed editor);\r\n    event MintPriceUpdated(uint256 oldPrice, uint256 newPrice);\r\n    event MintingToggled(bool active);\r\n    event BaseURIUpdated(string newURI);\r\n    event ContractURIUpdated(string newURI);\r\n    event TierThresholdsUpdated(uint256[] thresholds);\r\n    event Locked(uint256 tokenId); // ERC-5192\r\n    \r\n    // ============ Modifiers ============\r\n    \r\n    modifier onlyOwner() {\r\n        require(msg.sender == owner, \"Not owner\");\r\n        _;\r\n    }\r\n    \r\n    modifier onlyAdmin() {\r\n        require(msg.sender == owner || isAdmin[msg.sender], \"Not admin\");\r\n        _;\r\n    }\r\n    \r\n    modifier onlyMilesOperator() {\r\n        require(msg.sender == owner || isAdmin[msg.sender] || isMilesOperator[msg.sender], \"Not miles operator\");\r\n        _;\r\n    }\r\n    \r\n    modifier onlyEditor() {\r\n        require(msg.sender == owner || isAdmin[msg.sender] || isEditor[msg.sender], \"Not editor\");\r\n        _;\r\n    }\r\n    \r\n    modifier tokenExists(uint256 tokenId) {\r\n        require(_owners[tokenId] != address(0), \"Token does not exist\");\r\n        _;\r\n    }\r\n    \r\n    // ============ Constructor ============\r\n    \r\n    constructor() {\r\n        owner = msg.sender;\r\n        isAdmin[msg.sender] = true;\r\n        \r\n        // Initialize default tiers: 0=Economy, 500=Business, 1500=First, 3000=Captain\r\n        tierThresholds.push(0);\r\n        tierThresholds.push(500);\r\n        tierThresholds.push(1500);\r\n        tierThresholds.push(3000);\r\n    }\r\n    \r\n    // ============ Receive Function ============\r\n    \r\n    /**\r\n     * @notice Receive function to handle direct ETH transfers (triggers mint)\r\n     */\r\n    receive() external payable {\r\n        mint();\r\n    }\r\n    \r\n    /**\r\n     * @notice Fallback function\r\n     */\r\n    fallback() external payable {\r\n        revert(\"Function does not exist\");\r\n    }\r\n    \r\n    // ============ Minting Functions ============\r\n    \r\n    /**\r\n     * @notice Mint a boarding pass NFT\r\n     * @dev One per address, soulbound, assigns sequential seat\r\n     */\r\n    function mint() public payable {\r\n        require(mintingActive, \"Minting is not active\");\r\n        require(!hasMinted[msg.sender], \"Already minted\");\r\n        require(msg.value >= mintPrice, \"Insufficient payment\");\r\n        \r\n        uint256 tokenId = totalSupply + 1;\r\n        totalSupply = tokenId;\r\n        \r\n        // Assign seat (1A, 1B, 1C... 1J, 2A, 2B... 2J) - Wide body 3-4-3 config\r\n        uint256 seatRow = ((tokenId - 1) / 10) + 1;\r\n        uint256 seatIndex = (tokenId - 1) % 10;\r\n        string[10] memory letters = [\"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\", \"I\", \"J\"];  // 10 seats\r\n        \r\n        BoardingPass storage pass = boardingPasses[tokenId];\r\n        pass.seatRow = seatRow;\r\n        pass.seatLetter = letters[seatIndex];\r\n        pass.miles = initialMiles;\r\n        pass.milesEarned = initialMiles;\r\n        pass.milesSpent = 0;\r\n        pass.mintedAt = block.timestamp;\r\n        pass.lastUpdated = block.timestamp;\r\n        pass.bannedUntil = 0;\r\n        \r\n        _mint(msg.sender, tokenId);\r\n        hasMinted[msg.sender] = true;\r\n        addressToTokenId[msg.sender] = tokenId;\r\n        \r\n        // Refund excess payment\r\n        if (msg.value > mintPrice) {\r\n            (bool success, ) = msg.sender.call{value: msg.value - mintPrice}(\"\");\r\n            require(success, \"Refund failed\");\r\n        }\r\n    }\r\n    \r\n    /**\r\n     * @notice Admin mint for specific address\r\n     */\r\n    function adminMint(address to) external onlyAdmin {\r\n        require(!hasMinted[to], \"Already minted\");\r\n        \r\n        uint256 tokenId = totalSupply + 1;\r\n        totalSupply = tokenId;\r\n        \r\n        // Assign seat (1A, 1B, 1C... 1J, 2A, 2B... 2J) - Wide body 3-4-3 config\r\n        uint256 seatRow = ((tokenId - 1) / 10) + 1;\r\n        uint256 seatIndex = (tokenId - 1) % 10;\r\n        string[10] memory letters = [\"A\", \"B\", \"C\", \"D\", \"E\", \"F\", \"G\", \"H\", \"I\", \"J\"];  // 10 seats\r\n        \r\n        BoardingPass storage pass = boardingPasses[tokenId];\r\n        pass.seatRow = seatRow;\r\n        pass.seatLetter = letters[seatIndex];\r\n        pass.miles = initialMiles;\r\n        pass.milesEarned = initialMiles;\r\n        pass.milesSpent = 0;\r\n        pass.mintedAt = block.timestamp;\r\n        pass.lastUpdated = block.timestamp;\r\n        pass.bannedUntil = 0;\r\n        \r\n        _mint(to, tokenId);\r\n        hasMinted[to] = true;\r\n        addressToTokenId[to] = tokenId;\r\n    }\r\n    \r\n    // ============ Miles Management ============\r\n    \r\n    /**\r\n     * @notice Add miles to a boarding pass\r\n     */\r\n    function addMiles(uint256 tokenId, uint256 amount) external onlyMilesOperator tokenExists(tokenId) {\r\n        BoardingPass storage pass = boardingPasses[tokenId];\r\n        uint256 oldMiles = pass.miles;\r\n        \r\n        pass.miles += amount;\r\n        pass.milesEarned += amount;\r\n        pass.lastUpdated = block.timestamp;\r\n        \r\n        emit MilesUpdated(tokenId, oldMiles, pass.miles);\r\n    }\r\n    \r\n    /**\r\n     * @notice Deduct miles from a boarding pass\r\n     */\r\n    function deductMiles(uint256 tokenId, uint256 amount) external onlyMilesOperator tokenExists(tokenId) {\r\n        BoardingPass storage pass = boardingPasses[tokenId];\r\n        require(pass.miles >= amount, \"Insufficient miles\");\r\n        \r\n        uint256 oldMiles = pass.miles;\r\n        \r\n        pass.miles -= amount;\r\n        pass.milesSpent += amount;\r\n        pass.lastUpdated = block.timestamp;\r\n        \r\n        emit MilesUpdated(tokenId, oldMiles, pass.miles);\r\n    }\r\n    \r\n    /**\r\n     * @notice Set miles (absolute value)\r\n     */\r\n    function setMiles(uint256 tokenId, uint256 newMiles) external onlyMilesOperator tokenExists(tokenId) {\r\n        BoardingPass storage pass = boardingPasses[tokenId];\r\n        uint256 oldMiles = pass.miles;\r\n        \r\n        pass.miles = newMiles;\r\n        pass.lastUpdated = block.timestamp;\r\n        \r\n        // Adjust earned/spent totals\r\n        if (newMiles > oldMiles) {\r\n            pass.milesEarned += (newMiles - oldMiles);\r\n        } else if (newMiles < oldMiles) {\r\n            pass.milesSpent += (oldMiles - newMiles);\r\n        }\r\n        \r\n        emit MilesUpdated(tokenId, oldMiles, pass.miles);\r\n    }\r\n    \r\n    /**\r\n     * @notice Check if token is locked (ERC-5192)\r\n     * @dev All tokens are permanently soulbound\r\n     * @param tokenId Token ID to check\r\n     * @return Always returns true for existing tokens\r\n     */\r\n    function locked(uint256 tokenId) external view returns (bool) {\r\n        require(_owners[tokenId] != address(0), \"Token does not exist\");\r\n        return true;\r\n    }\r\n    \r\n    /**\r\n     * @notice Get user's current tier based on miles\r\n     * @param user Address of the user\r\n     * @return Tier index (0=first tier, 1=second tier, etc.)\r\n     */\r\n    function getUserTier(address user) public view returns (uint256) {\r\n        require(hasMinted[user], \"No NFT minted\");\r\n        uint256 tokenId = addressToTokenId[user];\r\n        uint256 miles = boardingPasses[tokenId].milesEarned;\r\n        \r\n        // Iterate backwards through tiers to find the highest qualifying tier\r\n        for (uint256 i = tierThresholds.length; i > 0; i--) {\r\n            if (miles >= tierThresholds[i - 1]) {\r\n                return i - 1;\r\n            }\r\n        }\r\n        return 0;\r\n    }\r\n    \r\n    /**\r\n     * @notice Get user's tier by token ID\r\n     * @param tokenId Token ID\r\n     * @return Tier index\r\n     */\r\n    function getTierByTokenId(uint256 tokenId) public view tokenExists(tokenId) returns (uint256) {\r\n        uint256 miles = boardingPasses[tokenId].milesEarned;\r\n        \r\n        for (uint256 i = tierThresholds.length; i > 0; i--) {\r\n            if (miles >= tierThresholds[i - 1]) {\r\n                return i - 1;\r\n            }\r\n        }\r\n        return 0;\r\n    }\r\n\r\n    function getSeatString(uint256 tokenId) public view returns (string memory) {\r\n        BoardingPass storage pass = boardingPasses[tokenId];\r\n        return string(abi.encodePacked(_toString(pass.seatRow), pass.seatLetter));\r\n        // Returns \"1A\", \"2C\", etc.\r\n    }\r\n    \r\n    /**\r\n     * @notice Get all tier thresholds\r\n     * @return Array of tier thresholds\r\n     */\r\n    function getTierThresholds() public view returns (uint256[] memory) {\r\n        return tierThresholds;\r\n    }\r\n    \r\n    /**\r\n     * @notice Get number of tiers\r\n     * @return Number of tiers\r\n     */\r\n    function getTierCount() public view returns (uint256) {\r\n        return tierThresholds.length;\r\n    }\r\n    \r\n    // ============ User Metadata Management ============\r\n    \r\n    /**\r\n     * @notice Update user text metadata\r\n     * @param tokenId Token ID\r\n     * @param key Field identifier (use keccak256 hash for common fields like \"name\", \"discord\", etc.)\r\n     * @param value Text value to store\r\n     */\r\n    function setCustomText(uint256 tokenId, bytes32 key, string memory value) \r\n        external \r\n        onlyEditor \r\n        tokenExists(tokenId) \r\n    {\r\n        boardingPasses[tokenId].customText[key] = value;\r\n        emit MetadataUpdated(tokenId, string(abi.encodePacked(key)));\r\n    }\r\n    \r\n    /**\r\n     * @notice Update user number metadata\r\n     * @param tokenId Token ID  \r\n     * @param key Field identifier\r\n     * @param value Number value to store\r\n     */\r\n    function setCustomNumber(uint256 tokenId, bytes32 key, uint256 value) \r\n        external \r\n        onlyEditor \r\n        tokenExists(tokenId) \r\n    {\r\n        boardingPasses[tokenId].customNumber[key] = value;\r\n        emit MetadataUpdated(tokenId, string(abi.encodePacked(key)));\r\n    }\r\n    \r\n    /**\r\n     * @notice Get custom text metadata\r\n     */\r\n    function getCustomText(uint256 tokenId, bytes32 key) external view returns (string memory) {\r\n        return boardingPasses[tokenId].customText[key];\r\n    }\r\n    \r\n    /**\r\n     * @notice Get custom number metadata\r\n     */\r\n    function getCustomNumber(uint256 tokenId, bytes32 key) external view returns (uint256) {\r\n        return boardingPasses[tokenId].customNumber[key];\r\n    }\r\n    \r\n    // ============ Ban Management ============\r\n    \r\n    /**\r\n     * @notice Ban a user until a specific timestamp\r\n     */\r\n    function banUser(uint256 tokenId, uint256 until) external onlyAdmin tokenExists(tokenId) {\r\n        require(until > block.timestamp, \"Ban time must be in future\");\r\n        boardingPasses[tokenId].bannedUntil = until;\r\n        emit UserBanned(tokenId, until);\r\n    }\r\n    \r\n    /**\r\n     * @notice Unban a user\r\n     */\r\n    function unbanUser(uint256 tokenId) external onlyAdmin tokenExists(tokenId) {\r\n        boardingPasses[tokenId].bannedUntil = 0;\r\n        emit UserUnbanned(tokenId);\r\n    }\r\n    \r\n    /**\r\n     * @notice Check if user is currently banned\r\n     */\r\n    function isBanned(uint256 tokenId) external view returns (bool) {\r\n        return boardingPasses[tokenId].bannedUntil > block.timestamp;\r\n    }\r\n    \r\n    // ============ Admin Functions ============\r\n    \r\n    /**\r\n     * @notice Toggle minting on/off\r\n     */\r\n    function toggleMinting() external onlyAdmin {\r\n        mintingActive = !mintingActive;\r\n        emit MintingToggled(mintingActive);\r\n    }\r\n    \r\n    /**\r\n     * @notice Set mint price\r\n     */\r\n    function setMintPrice(uint256 newPrice) external onlyAdmin {\r\n        uint256 oldPrice = mintPrice;\r\n        mintPrice = newPrice;\r\n        emit MintPriceUpdated(oldPrice, newPrice);\r\n    }\r\n    \r\n    /**\r\n     * @notice Set initial miles for new mints\r\n     */\r\n    function setInitialMiles(uint256 newInitialMiles) external onlyAdmin {\r\n        initialMiles = newInitialMiles;\r\n    }\r\n    \r\n    /**\r\n     * @notice Update base URI for metadata\r\n     */\r\n    function setBaseURI(string memory newURI) external onlyAdmin {\r\n        baseURI = newURI;\r\n        emit BaseURIUpdated(newURI);\r\n        // Notify all tokens updated\r\n        if (totalSupply > 0) {\r\n            emit BatchMetadataUpdate(1, totalSupply);\r\n        }\r\n    }\r\n\r\n    function setIndividualURI(bool enabled) external onlyAdmin {\r\n        individualURI = enabled;\r\n        // Notify all tokens updated (1 to totalSupply)\r\n        if (totalSupply > 0) {\r\n            emit BatchMetadataUpdate(1, totalSupply);\r\n        }\r\n    }\r\n\r\n    function setStaticFilename(string memory filename) external onlyAdmin {\r\n        staticFilename = filename;\r\n        // Only affects tokens if in static mode\r\n        if (!individualURI && totalSupply > 0) {\r\n            emit BatchMetadataUpdate(1, totalSupply);\r\n        }\r\n    }\r\n\r\n    function setContractURI(string memory uri) external onlyAdmin {\r\n        contractMetadataURI = uri;\r\n        emit ContractURIUpdated(uri); // Optional but good practice\r\n    }\r\n    \r\n    /**\r\n     * @notice Update tier thresholds\r\n     * @param newThresholds Array of new thresholds (must be in ascending order)\r\n     */\r\n    function setTierThresholds(uint256[] memory newThresholds) external onlyAdmin {\r\n        require(newThresholds.length > 0, \"Need at least one tier\");\r\n        require(newThresholds[0] == 0, \"First tier must start at 0\");\r\n        \r\n        // Verify ascending order\r\n        for (uint256 i = 1; i < newThresholds.length; i++) {\r\n            require(newThresholds[i] > newThresholds[i - 1], \"Thresholds must be in ascending order\");\r\n        }\r\n        \r\n        // Clear existing thresholds\r\n        delete tierThresholds;\r\n        \r\n        // Set new thresholds\r\n        for (uint256 i = 0; i < newThresholds.length; i++) {\r\n            tierThresholds.push(newThresholds[i]);\r\n        }\r\n        \r\n        emit TierThresholdsUpdated(newThresholds);\r\n    }\r\n    \r\n    // ============ Role Management ============\r\n    \r\n    function addAdmin(address admin) external onlyAdmin {\r\n        isAdmin[admin] = true;\r\n        emit AdminAdded(admin);\r\n    }\r\n    \r\n    function removeAdmin(address admin) external onlyOwner {\r\n        isAdmin[admin] = false;\r\n        emit AdminRemoved(admin);\r\n    }\r\n    \r\n    function addMilesOperator(address operator) external onlyAdmin {\r\n        isMilesOperator[operator] = true;\r\n        emit MilesOperatorAdded(operator);\r\n    }\r\n    \r\n    function removeMilesOperator(address operator) external onlyAdmin {\r\n        isMilesOperator[operator] = false;\r\n        emit MilesOperatorRemoved(operator);\r\n    }\r\n    \r\n    function addEditor(address editor) external onlyAdmin {\r\n        isEditor[editor] = true;\r\n        emit EditorAdded(editor);\r\n    }\r\n    \r\n    function removeEditor(address editor) external onlyAdmin {\r\n        isEditor[editor] = false;\r\n        emit EditorRemoved(editor);\r\n    }\r\n    \r\n    function transferOwnership(address newOwner) external onlyOwner {\r\n        require(newOwner != address(0), \"Invalid address\");\r\n        owner = newOwner;\r\n    }\r\n\r\n    /**\r\n    * @notice Check all roles for an address\r\n    * @param account Address to check\r\n    * @return isOwner_ Whether the address is the owner\r\n    * @return isAdmin_ Whether the address is an admin\r\n    * @return isMilesOperator_ Whether the address is a miles operator\r\n    * @return isEditor_ Whether the address is an editor\r\n    */\r\n    function getRoles(address account) external view returns (\r\n        bool isOwner_,\r\n        bool isAdmin_,\r\n        bool isMilesOperator_,\r\n        bool isEditor_\r\n    ) {\r\n        return (\r\n            account == owner,\r\n            isAdmin[account],\r\n            isMilesOperator[account],\r\n            isEditor[account]\r\n        );\r\n    }\r\n    \r\n    // ============ Emergency Functions ============\r\n    \r\n    /**\r\n     * @notice Withdraw ETH from contract\r\n     */\r\n    function withdrawETH() external onlyOwner {\r\n        uint256 balance = address(this).balance;\r\n        require(balance > 0, \"No ETH to withdraw\");\r\n        (bool success, ) = owner.call{value: balance}(\"\");\r\n        require(success, \"Withdrawal failed\");\r\n    }\r\n    \r\n    /**\r\n     * @notice Recover accidentally sent ERC20 tokens\r\n     * @dev Uses low-level call to handle ALL tokens (standard and non-standard like USDT)\r\n     */\r\n    function recoverERC20(address token, uint256 amount) external onlyOwner {\r\n        require(token != address(0), \"Invalid token\");\r\n        \r\n        // Use low-level call to handle tokens that don't return bool (like USDT)\r\n        (bool success, bytes memory data) = token.call(\r\n            abi.encodeWithSelector(IERC20.transfer.selector, owner, amount)\r\n        );\r\n        \r\n        // Check success: either call succeeded with no return data, or returned true\r\n        require(\r\n            success && (data.length == 0 || abi.decode(data, (bool))),\r\n            \"Token transfer failed\"\r\n        );\r\n    }\r\n    \r\n    // ============ View Functions ============\r\n    \r\n    /**\r\n     * @notice Get full boarding pass data\r\n     */\r\n    function getBoardingPass(uint256 tokenId) external view returns (\r\n        string memory seat,\r\n        uint256 miles,\r\n        uint256 milesEarned,\r\n        uint256 milesSpent,\r\n        uint256 tier,\r\n        uint256 mintedAt,\r\n        uint256 lastUpdated,\r\n        bool banned,\r\n        uint256 bannedUntil\r\n    ) {\r\n        require(_owners[tokenId] != address(0), \"Token does not exist\");\r\n        BoardingPass storage pass = boardingPasses[tokenId];\r\n        uint256 currentTier = getTierByTokenId(tokenId);\r\n        string memory _seat = getSeatString(tokenId);\r\n        \r\n        return (\r\n            _seat,\r\n            pass.miles,\r\n            pass.milesEarned,\r\n            pass.milesSpent,\r\n            currentTier,\r\n            pass.mintedAt,\r\n            pass.lastUpdated,\r\n            pass.bannedUntil > block.timestamp,\r\n            pass.bannedUntil\r\n        );\r\n    }\r\n    \r\n    /**\r\n     * @notice Get token ID for an address\r\n     */\r\n    function getTokenIdByAddress(address addr) external view returns (uint256) {\r\n        require(hasMinted[addr], \"Address has not minted\");\r\n        return addressToTokenId[addr];\r\n    }\r\n    \r\n    // ============ Soulbound Implementation ============\r\n    \r\n    /**\r\n     * @notice Override transfer functions to make tokens soulbound\r\n     */\r\n    function _beforeTokenTransfer(address from, address to, uint256 tokenId) internal view {\r\n        require(from == address(0) || to == address(0), \"Soulbound: transfers disabled\");\r\n    }\r\n    \r\n    function transferFrom(address from, address to, uint256 tokenId) public override {\r\n        _beforeTokenTransfer(from, to, tokenId);\r\n        revert(\"Soulbound: transfers disabled\");\r\n    }\r\n    \r\n    function safeTransferFrom(address from, address to, uint256 tokenId) public override {\r\n        _beforeTokenTransfer(from, to, tokenId);\r\n        revert(\"Soulbound: transfers disabled\");\r\n    }\r\n    \r\n    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public override {\r\n        _beforeTokenTransfer(from, to, tokenId);\r\n        revert(\"Soulbound: transfers disabled\");\r\n    }\r\n    \r\n    // ============ ERC721 Implementation ============\r\n    \r\n    function _mint(address to, uint256 tokenId) internal {\r\n        require(to != address(0), \"Cannot mint to zero address\");\r\n        require(_owners[tokenId] == address(0), \"Token already exists\");\r\n        \r\n        _balances[to] += 1;\r\n        _owners[tokenId] = to;\r\n        \r\n        emit Transfer(address(0), to, tokenId);\r\n        emit Locked(tokenId); // ERC-5192: Token is permanently locked\r\n    }\r\n    \r\n    function balanceOf(address _owner) public view override returns (uint256) {\r\n        require(_owner != address(0), \"Invalid address\");\r\n        return _balances[_owner];\r\n    }\r\n    \r\n    function ownerOf(uint256 tokenId) public view override returns (address) {\r\n        address _owner = _owners[tokenId];\r\n        require(_owner != address(0), \"Token does not exist\");\r\n        return _owner;\r\n    }\r\n    \r\n    function approve(address to, uint256 tokenId) public override {\r\n        revert(\"Soulbound: approvals disabled\");\r\n    }\r\n    \r\n    function getApproved(uint256 tokenId) public view override returns (address) {\r\n        require(_owners[tokenId] != address(0), \"Token does not exist\");\r\n        return address(0); // Always return zero address for soulbound\r\n    }\r\n    \r\n    function setApprovalForAll(address operator, bool approved) public override {\r\n        revert(\"Soulbound: approvals disabled\");\r\n    }\r\n    \r\n    function isApprovedForAll(address _owner, address _operator) public view override returns (bool) {\r\n        return false; // Always false for soulbound\r\n    }\r\n    /*\r\n    function tokenURI(uint256 tokenId) public view override returns (string memory) {\r\n        require(_owners[tokenId] != address(0), \"Token does not exist\");\r\n        \r\n        if (bytes(baseURI).length == 0) {\r\n            return \"\";\r\n        }\r\n        \r\n        return string(abi.encodePacked(baseURI, _toString(tokenId), \".json\"));\r\n    }*/\r\n\r\n    function tokenURI(uint256 tokenId) public view override returns (string memory) {\r\n        require(_owners[tokenId] != address(0), \"Token does not exist\");\r\n        if (bytes(baseURI).length == 0) return \"\";\r\n        \r\n        if (individualURI) {\r\n            return string(abi.encodePacked(baseURI, _toString(tokenId), \".json\"));\r\n        } else {\r\n            return string(abi.encodePacked(baseURI, staticFilename));\r\n        }\r\n    }\r\n\r\n    function contractURI() external view returns (string memory) {\r\n        return contractMetadataURI;\r\n    }\r\n    \r\n    function supportsInterface(bytes4 interfaceId) public pure override returns (bool) {\r\n        return\r\n            interfaceId == type(IERC721).interfaceId ||\r\n            interfaceId == type(IERC721Metadata).interfaceId ||\r\n            interfaceId == 0xb45a3c0e || // ERC-5192 (Soulbound)\r\n            interfaceId == 0x49064906 || // ERC-4906 (Metadata Update)\r\n            interfaceId == 0x01ffc9a7; // ERC165\r\n    }\r\n    \r\n    /**\r\n     * @notice Get token by index (for enumeration)\r\n     */\r\n    function tokenByIndex(uint256 index) external view returns (uint256) {\r\n        require(index < totalSupply, \"Index out of bounds\");\r\n        return index + 1; // Token IDs start at 1\r\n    }\r\n    \r\n    /**\r\n     * @notice Get token of owner by index\r\n     * @dev Since each address can only have one token, index must be 0\r\n     */\r\n    function tokenOfOwnerByIndex(address _owner, uint256 index) external view returns (uint256) {\r\n        require(index == 0, \"Index out of bounds\");\r\n        require(hasMinted[_owner], \"Owner has no tokens\");\r\n        return addressToTokenId[_owner];\r\n    }\r\n    \r\n    // ============ Utility Functions ============\r\n    \r\n    function _toString(uint256 value) internal pure returns (string memory) {\r\n        if (value == 0) {\r\n            return \"0\";\r\n        }\r\n        uint256 temp = value;\r\n        uint256 digits;\r\n        while (temp != 0) {\r\n            digits++;\r\n            temp /= 10;\r\n        }\r\n        bytes memory buffer = new bytes(digits);\r\n        while (value != 0) {\r\n            digits -= 1;\r\n            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));\r\n            value /= 10;\r\n        }\r\n        return string(buffer);\r\n    }\r\n}"}},"abi":[{"type":"constructor","inputs":[],"stateMutability":"nonpayable"},{"name":"AdminAdded","type":"event","inputs":[{"name":"admin","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"name":"AdminRemoved","type":"event","inputs":[{"name":"admin","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"name":"Approval","type":"event","inputs":[{"name":"owner","type":"address","indexed":true,"internalType":"address"},{"name":"approved","type":"address","indexed":true,"internalType":"address"},{"name":"tokenId","type":"uint256","indexed":true,"internalType":"uint256"}],"anonymous":false},{"name":"ApprovalForAll","type":"event","inputs":[{"name":"owner","type":"address","indexed":true,"internalType":"address"},{"name":"operator","type":"address","indexed":true,"internalType":"address"},{"name":"approved","type":"bool","indexed":false,"internalType":"bool"}],"anonymous":false},{"name":"BaseURIUpdated","type":"event","inputs":[{"name":"newURI","type":"string","indexed":false,"internalType":"string"}],"anonymous":false},{"name":"BatchMetadataUpdate","type":"event","inputs":[{"name":"_fromTokenId","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"_toTokenId","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"ContractURIUpdated","type":"event","inputs":[{"name":"newURI","type":"string","indexed":false,"internalType":"string"}],"anonymous":false},{"name":"EditorAdded","type":"event","inputs":[{"name":"editor","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"name":"EditorRemoved","type":"event","inputs":[{"name":"editor","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"name":"Locked","type":"event","inputs":[{"name":"tokenId","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"MetadataUpdate","type":"event","inputs":[{"name":"_tokenId","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"MetadataUpdated","type":"event","inputs":[{"name":"tokenId","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"field","type":"string","indexed":false,"internalType":"string"}],"anonymous":false},{"name":"MilesOperatorAdded","type":"event","inputs":[{"name":"operator","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"name":"MilesOperatorRemoved","type":"event","inputs":[{"name":"operator","type":"address","indexed":true,"internalType":"address"}],"anonymous":false},{"name":"MilesUpdated","type":"event","inputs":[{"name":"tokenId","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"oldMiles","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"newMiles","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"MintPriceUpdated","type":"event","inputs":[{"name":"oldPrice","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"newPrice","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"MintingToggled","type":"event","inputs":[{"name":"active","type":"bool","indexed":false,"internalType":"bool"}],"anonymous":false},{"name":"TierThresholdsUpdated","type":"event","inputs":[{"name":"thresholds","type":"uint256[]","indexed":false,"internalType":"uint256[]"}],"anonymous":false},{"name":"Transfer","type":"event","inputs":[{"name":"from","type":"address","indexed":true,"internalType":"address"},{"name":"to","type":"address","indexed":true,"internalType":"address"},{"name":"tokenId","type":"uint256","indexed":true,"internalType":"uint256"}],"anonymous":false},{"name":"UserBanned","type":"event","inputs":[{"name":"tokenId","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"until","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"UserUnbanned","type":"event","inputs":[{"name":"tokenId","type":"uint256","indexed":true,"internalType":"uint256"}],"anonymous":false},{"type":"fallback","stateMutability":"payable"},{"name":"addAdmin","type":"function","inputs":[{"name":"admin","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"addEditor","type":"function","inputs":[{"name":"editor","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"addMiles","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"},{"name":"amount","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"addMilesOperator","type":"function","inputs":[{"name":"operator","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"addressToTokenId","type":"function","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"adminMint","type":"function","inputs":[{"name":"to","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"approve","type":"function","inputs":[{"name":"to","type":"address","internalType":"address"},{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"balanceOf","type":"function","inputs":[{"name":"_owner","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"banUser","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"},{"name":"until","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"baseURI","type":"function","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"boardingPasses","type":"function","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"seatRow","type":"uint256","internalType":"uint256"},{"name":"seatLetter","type":"string","internalType":"string"},{"name":"miles","type":"uint256","internalType":"uint256"},{"name":"milesEarned","type":"uint256","internalType":"uint256"},{"name":"milesSpent","type":"uint256","internalType":"uint256"},{"name":"mintedAt","type":"uint256","internalType":"uint256"},{"name":"lastUpdated","type":"uint256","internalType":"uint256"},{"name":"bannedUntil","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"contractMetadataURI","type":"function","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"contractURI","type":"function","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"deductMiles","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"},{"name":"amount","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"getApproved","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"getBoardingPass","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"seat","type":"string","internalType":"string"},{"name":"miles","type":"uint256","internalType":"uint256"},{"name":"milesEarned","type":"uint256","internalType":"uint256"},{"name":"milesSpent","type":"uint256","internalType":"uint256"},{"name":"tier","type":"uint256","internalType":"uint256"},{"name":"mintedAt","type":"uint256","internalType":"uint256"},{"name":"lastUpdated","type":"uint256","internalType":"uint256"},{"name":"banned","type":"bool","internalType":"bool"},{"name":"bannedUntil","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"getCustomNumber","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"},{"name":"key","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"getCustomText","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"},{"name":"key","type":"bytes32","internalType":"bytes32"}],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"getRoles","type":"function","inputs":[{"name":"account","type":"address","internalType":"address"}],"outputs":[{"name":"isOwner_","type":"bool","internalType":"bool"},{"name":"isAdmin_","type":"bool","internalType":"bool"},{"name":"isMilesOperator_","type":"bool","internalType":"bool"},{"name":"isEditor_","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"getSeatString","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"getTierByTokenId","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"getTierCount","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"getTierThresholds","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256[]","internalType":"uint256[]"}],"stateMutability":"view"},{"name":"getTokenIdByAddress","type":"function","inputs":[{"name":"addr","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"getUserTier","type":"function","inputs":[{"name":"user","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"hasMinted","type":"function","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"individualURI","type":"function","inputs":[],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"initialMiles","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"isAdmin","type":"function","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"isApprovedForAll","type":"function","inputs":[{"name":"_owner","type":"address","internalType":"address"},{"name":"_operator","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"isBanned","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"isEditor","type":"function","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"isMilesOperator","type":"function","inputs":[{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"locked","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"mint","type":"function","inputs":[],"outputs":[],"stateMutability":"payable"},{"name":"mintPrice","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"mintingActive","type":"function","inputs":[],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"name","type":"function","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"owner","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"ownerOf","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"recoverERC20","type":"function","inputs":[{"name":"token","type":"address","internalType":"address"},{"name":"amount","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"removeAdmin","type":"function","inputs":[{"name":"admin","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"removeEditor","type":"function","inputs":[{"name":"editor","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"removeMilesOperator","type":"function","inputs":[{"name":"operator","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"safeTransferFrom","type":"function","inputs":[{"name":"from","type":"address","internalType":"address"},{"name":"to","type":"address","internalType":"address"},{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"safeTransferFrom","type":"function","inputs":[{"name":"from","type":"address","internalType":"address"},{"name":"to","type":"address","internalType":"address"},{"name":"tokenId","type":"uint256","internalType":"uint256"},{"name":"data","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setApprovalForAll","type":"function","inputs":[{"name":"operator","type":"address","internalType":"address"},{"name":"approved","type":"bool","internalType":"bool"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setBaseURI","type":"function","inputs":[{"name":"newURI","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setContractURI","type":"function","inputs":[{"name":"uri","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setCustomNumber","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"},{"name":"key","type":"bytes32","internalType":"bytes32"},{"name":"value","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setCustomText","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"},{"name":"key","type":"bytes32","internalType":"bytes32"},{"name":"value","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setIndividualURI","type":"function","inputs":[{"name":"enabled","type":"bool","internalType":"bool"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setInitialMiles","type":"function","inputs":[{"name":"newInitialMiles","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setMiles","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"},{"name":"newMiles","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setMintPrice","type":"function","inputs":[{"name":"newPrice","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setStaticFilename","type":"function","inputs":[{"name":"filename","type":"string","internalType":"string"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setTierThresholds","type":"function","inputs":[{"name":"newThresholds","type":"uint256[]","internalType":"uint256[]"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"staticFilename","type":"function","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"supportsInterface","type":"function","inputs":[{"name":"interfaceId","type":"bytes4","internalType":"bytes4"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"name":"symbol","type":"function","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"tierThresholds","type":"function","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"toggleMinting","type":"function","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"name":"tokenByIndex","type":"function","inputs":[{"name":"index","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"tokenOfOwnerByIndex","type":"function","inputs":[{"name":"_owner","type":"address","internalType":"address"},{"name":"index","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"tokenURI","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"totalSupply","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"transferFrom","type":"function","inputs":[{"name":"from","type":"address","internalType":"address"},{"name":"to","type":"address","internalType":"address"},{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"transferOwnership","type":"function","inputs":[{"name":"newOwner","type":"address","internalType":"address"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"unbanUser","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"withdrawETH","type":"function","inputs":[],"outputs":[],"stateMutability":"nonpayable"},{"type":"receive","stateMutability":"payable"}],"matchId":"29821886","creationMatch":"match","runtimeMatch":"match","verifiedAt":"2026-05-26T04:08:04Z","match":"match","chainId":"56","address":"0xc267E47F1Ba3B6B2BCbA58f16298c92CaCAF7952"}