{"sources":{"fly.sol":{"content":"// SPDX-License-Identifier: MIT\r\npragma solidity ^0.8.24;\r\n\r\ninterface IAgentId {\r\n    function handleOf(address owner) external view returns (string memory);\r\n}\r\n\r\ninterface IERC20Like {\r\n    function balanceOf(address account) external view returns (uint256);\r\n    function transfer(address to, uint256 amount) external returns (bool);\r\n    function transferFrom(address from, address to, uint256 amount) external returns (bool);\r\n}\r\n\r\n/// @title FlywheelProtocol\r\n/// @notice Tiered mint / delayed sell-back / dynamic refund machine on BSC.\r\n///         Each position is a non-transferable ERC-721 NFT carrying tier + mint time.\r\n///         Tiers 1 and 2 open together. Tier 1 is capped at 5 per wallet;\r\n///         tier 2 follows the global 10-position wallet cap. Tier 3+\r\n///         opens only after tier 2 sells out, then one sequential tier at a time.\r\n///         No owner controls. Marketing wallet only receives the 10% fee.\r\ncontract FlywheelProtocol {\r\n    string public constant name = unicode\"飞轮币\";\r\n    string public constant symbol = \"FlyWheel\";\r\n\r\n    uint256 public constant CHAIN_ID = 56;\r\n    uint256 public constant TIER_COUNT = 50;\r\n    uint256 public constant MAX_WALLET_HOLDINGS = 10;\r\n    uint256 public constant MAX_TIER1_PER_WALLET = 5;\r\n    uint256 public constant MARKETING_FEE_BPS = 1_000;\r\n    uint256 public constant MAX_REFUND_RATIO_BPS = 8_000;\r\n    uint256 public constant ALPHA_BPS = 16_000;\r\n    uint256 public constant REFUND_LOCK = 24 hours;\r\n\r\n    address public constant admin = 0x000000000000000000000000000000000000dEaD;\r\n    address public constant marketingWallet = 0xD5bbE9819Eb21bedf5C2150131825e0f5bfbe78c;\r\n    address public constant agentRegistry = 0x60B9262456dE011F7d15a54CdD8c48bAaa8B022F;\r\n    address public constant usdt = 0x55d398326f99059fF775485246999027B3197955;\r\n\r\n    struct Position {\r\n        uint8 tier;\r\n        uint40 mintedAt;\r\n    }\r\n\r\n    /// @dev tiers are 1-indexed. Index 0 is unused.\r\n    uint256[51] public tierSupply;\r\n    uint256[51] public tierPrice;\r\n    uint256[51] public tierMinted;\r\n    uint256[51] public tierOutstanding;\r\n    uint256[51] public tierSettlePrice;\r\n    bool[51] public tierSellOpen;\r\n\r\n    /// @notice True until tier 2 sells out. While true, tiers 1 and 2 mint in parallel.\r\n    bool public earlyPhase = true;\r\n    /// @notice During earlyPhase this stays 1. After tier 2 completes it tracks the open sequential tier (3+).\r\n    uint256 public currentTier = 1;\r\n    uint256 public lockedSettlementFunds;\r\n    uint256 public totalLiveRefundablePrincipal;\r\n    uint256 public totalSupply;\r\n\r\n    mapping(uint256 => Position) private _positionOf;\r\n    mapping(uint256 => address) private _ownerOf;\r\n    mapping(address => uint256[]) private _ownedTokens;\r\n    mapping(uint256 => uint256) private _ownedIndex;\r\n    uint256 private _nextTokenId = 1;\r\n\r\n    uint256 private _status;\r\n\r\n    error WrongChain();\r\n    error ZeroAddress();\r\n    error NoAgentId();\r\n    error InvalidCount();\r\n    error SoldOut();\r\n    error WalletLimit();\r\n    error TierClosed();\r\n    error TierNotOpen();\r\n    error InsufficientPositions();\r\n    error LockActive();\r\n    error SellNotOpen();\r\n    error NoFreeCash();\r\n    error InsufficientLocked();\r\n    error UsdtFailed();\r\n    error Reentrancy();\r\n    error NonTransferable();\r\n    error InvalidToken();\r\n\r\n    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);\r\n    event Minted(address indexed user, uint256 indexed tier, uint256 count, uint256 cost);\r\n    event TierCompleted(uint256 indexed tier, uint256 unlockedTier, uint256 settlePrice, uint256 lockedAdded);\r\n    event SoldBack(address indexed user, uint256 indexed tier, uint256 count, uint256 payout);\r\n    event Refunded(address indexed user, uint256 indexed tier, uint256 count, uint256 refundAmount, uint256 ratioBps);\r\n    event EarlyPhaseEnded(uint256 nextTier);\r\n\r\n    modifier nonReentrant() {\r\n        if (_status == 2) revert Reentrancy();\r\n        _status = 2;\r\n        _;\r\n        _status = 1;\r\n    }\r\n\r\n    constructor() {\r\n        if (block.chainid != CHAIN_ID) revert WrongChain();\r\n        if (marketingWallet == address(0) || marketingWallet == admin) revert ZeroAddress();\r\n\r\n        _bootstrapTiers();\r\n        _status = 1;\r\n    }\r\n\r\n    // -------------------------------------------------------------------------\r\n    // ERC-721 (soulbound / non-transferable position NFTs)\r\n    // -------------------------------------------------------------------------\r\n\r\n    function supportsInterface(bytes4 interfaceId) external pure returns (bool) {\r\n        return interfaceId == 0x01ffc9a7 // ERC165\r\n            || interfaceId == 0x80ac58cd // ERC721\r\n            || interfaceId == 0x5b5e139f; // ERC721Metadata\r\n    }\r\n\r\n    function balanceOf(address owner) public view returns (uint256) {\r\n        if (owner == address(0)) revert ZeroAddress();\r\n        return _ownedTokens[owner].length;\r\n    }\r\n\r\n    function ownerOf(uint256 tokenId) public view returns (address) {\r\n        address owner = _ownerOf[tokenId];\r\n        if (owner == address(0)) revert InvalidToken();\r\n        return owner;\r\n    }\r\n\r\n    function tokenURI(uint256 tokenId) external view returns (string memory) {\r\n        Position memory p = _positionOf[tokenId];\r\n        if (_ownerOf[tokenId] == address(0)) revert InvalidToken();\r\n        return string(\r\n            abi.encodePacked(\r\n                \"data:application/json,{\",\r\n                \"%22name%22:%22FlyWheel%20%23\",\r\n                _toString(tokenId),\r\n                \"%22,%22description%22:%22Flywheel%20non-transferable%20position%20NFT%22,\",\r\n                \"%22attributes%22:[{%22trait_type%22:%22tier%22,%22value%22:\",\r\n                _toString(p.tier),\r\n                \"},{%22trait_type%22:%22mintedAt%22,%22value%22:\",\r\n                _toString(p.mintedAt),\r\n                \"}]}\"\r\n            )\r\n        );\r\n    }\r\n\r\n    function getApproved(uint256) external pure returns (address) {\r\n        return address(0);\r\n    }\r\n\r\n    function isApprovedForAll(address, address) external pure returns (bool) {\r\n        return false;\r\n    }\r\n\r\n    function approve(address, uint256) external pure {\r\n        revert NonTransferable();\r\n    }\r\n\r\n    function setApprovalForAll(address, bool) external pure {\r\n        revert NonTransferable();\r\n    }\r\n\r\n    function transferFrom(address, address, uint256) external pure {\r\n        revert NonTransferable();\r\n    }\r\n\r\n    function safeTransferFrom(address, address, uint256) external pure {\r\n        revert NonTransferable();\r\n    }\r\n\r\n    function safeTransferFrom(address, address, uint256, bytes calldata) external pure {\r\n        revert NonTransferable();\r\n    }\r\n\r\n    function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256) {\r\n        if (index >= _ownedTokens[owner].length) revert InvalidToken();\r\n        return _ownedTokens[owner][index];\r\n    }\r\n\r\n    function positionOf(uint256 tokenId) external view returns (uint8 tier, uint40 mintedAt, address owner) {\r\n        owner = _ownerOf[tokenId];\r\n        if (owner == address(0)) revert InvalidToken();\r\n        Position memory p = _positionOf[tokenId];\r\n        return (p.tier, p.mintedAt, owner);\r\n    }\r\n\r\n    /// @notice Mint `count` NFT positions of `tier`. Agent ID required.\r\n    ///         Tiers 1 and 2 are open together. Tier 1 max 5 per wallet;\r\n    ///         tier 2 uses the global 10-position cap. Tier 3+ opens only\r\n    ///         after tier 2 is sold out, then one tier at a time.\r\n    function mint(uint256 tier, uint256 count) external nonReentrant {\r\n        if (tier == 0 || tier > TIER_COUNT || count == 0 || count > MAX_WALLET_HOLDINGS) revert InvalidCount();\r\n        if (bytes(IAgentId(agentRegistry).handleOf(msg.sender)).length == 0) revert NoAgentId();\r\n        if (!_isMintOpen(tier)) revert TierNotOpen();\r\n        if (tierMinted[tier] + count > tierSupply[tier]) revert TierClosed();\r\n        if (_ownedTokens[msg.sender].length + count > MAX_WALLET_HOLDINGS) revert WalletLimit();\r\n        if (tier == 1 && _countTier(msg.sender, 1) + count > MAX_TIER1_PER_WALLET) revert WalletLimit();\r\n\r\n        uint256 price = tierPrice[tier];\r\n        uint256 totalCost = count * price;\r\n        tierMinted[tier] += count;\r\n        tierOutstanding[tier] += count;\r\n        _mintPositions(msg.sender, uint8(tier), count);\r\n\r\n        if (totalCost > 0) {\r\n            uint256 marketingCut = (totalCost * MARKETING_FEE_BPS) / 10_000;\r\n            uint256 vaultCut = totalCost - marketingCut;\r\n            _usdtTransferFrom(msg.sender, marketingWallet, marketingCut);\r\n            _usdtTransferFrom(msg.sender, address(this), vaultCut);\r\n            totalLiveRefundablePrincipal += vaultCut;\r\n        }\r\n\r\n        emit Minted(msg.sender, tier, count, totalCost);\r\n\r\n        if (tierMinted[tier] == tierSupply[tier]) {\r\n            _onTierCompleted(tier);\r\n            _afterTierFilled(tier);\r\n        }\r\n    }\r\n\r\n    /// @notice Whether `tier` can be minted right now.\r\n    function mintOpen(uint256 tier) external view returns (bool) {\r\n        if (tier == 0 || tier > TIER_COUNT) return false;\r\n        if (tierMinted[tier] >= tierSupply[tier]) return false;\r\n        return _isMintOpen(tier);\r\n    }\r\n\r\n    /// @notice Sell NFT positions from an unlocked historical tier at the locked settle price.\r\n    function sellBack(uint256 tier, uint256 count) external nonReentrant {\r\n        if (tier == 0 || tier > TIER_COUNT || count == 0) revert InvalidCount();\r\n        if (!tierSellOpen[tier]) revert SellNotOpen();\r\n        if (_countTier(msg.sender, uint8(tier)) < count) revert InsufficientPositions();\r\n\r\n        uint256 settlePrice = tierSettlePrice[tier];\r\n        uint256 totalPayout = count * settlePrice;\r\n        if (lockedSettlementFunds < totalPayout) revert InsufficientLocked();\r\n        if (IERC20Like(usdt).balanceOf(address(this)) < totalPayout) revert InsufficientLocked();\r\n\r\n        _burnPositions(msg.sender, uint8(tier), count, 0, false);\r\n        tierOutstanding[tier] -= count;\r\n        lockedSettlementFunds -= totalPayout;\r\n\r\n        _usdtTransfer(msg.sender, totalPayout);\r\n        emit SoldBack(msg.sender, tier, count, totalPayout);\r\n    }\r\n\r\n    /// @notice Dynamic refund for NFT positions older than 24h.\r\n    ///         Available even after sell-back unlock; user may choose sellBack or refund.\r\n    function dynamicRefund(uint256 tier, uint256 count) external nonReentrant {\r\n        if (tier == 0 || tier > TIER_COUNT || count == 0) revert InvalidCount();\r\n        if (_countTier(msg.sender, uint8(tier)) < count) revert InsufficientPositions();\r\n\r\n        uint256 price = tierPrice[tier];\r\n        uint256 faceValue = count * price;\r\n        uint256 netPrincipal = (faceValue * (10_000 - MARKETING_FEE_BPS)) / 10_000;\r\n        bool sellOpen = tierSellOpen[tier];\r\n\r\n        // After unlock, principal already left the live refund bucket and sits in locked funds.\r\n        // Release this share of the lock first so it becomes free cash for the refund payout.\r\n        if (sellOpen) {\r\n            uint256 lockedCut = count * tierSettlePrice[tier];\r\n            if (lockedSettlementFunds >= lockedCut) {\r\n                lockedSettlementFunds -= lockedCut;\r\n            } else {\r\n                lockedSettlementFunds = 0;\r\n            }\r\n        }\r\n\r\n        uint256 cash = freeCash();\r\n        if (faceValue > 0 && cash == 0) revert NoFreeCash();\r\n\r\n        uint256 ratioBps = 0;\r\n        uint256 refundAmount = 0;\r\n        if (faceValue > 0) {\r\n            uint256 live = totalLiveRefundablePrincipal;\r\n            if (!sellOpen) {\r\n                if (live == 0) revert NoFreeCash();\r\n                ratioBps = (cash * ALPHA_BPS) / live;\r\n            } else if (live > 0) {\r\n                ratioBps = (cash * ALPHA_BPS) / live;\r\n            } else {\r\n                ratioBps = MAX_REFUND_RATIO_BPS;\r\n            }\r\n            if (ratioBps > MAX_REFUND_RATIO_BPS) ratioBps = MAX_REFUND_RATIO_BPS;\r\n            refundAmount = (faceValue * ratioBps) / 10_000;\r\n            if (refundAmount > cash) refundAmount = cash;\r\n        }\r\n\r\n        _burnPositions(msg.sender, uint8(tier), count, REFUND_LOCK, true);\r\n        tierOutstanding[tier] -= count;\r\n\r\n        // Only unpaid (not-yet-unlocked) principal still lives in the refund bucket.\r\n        if (!sellOpen && netPrincipal > 0) {\r\n            if (totalLiveRefundablePrincipal >= netPrincipal) {\r\n                totalLiveRefundablePrincipal -= netPrincipal;\r\n            } else {\r\n                totalLiveRefundablePrincipal = 0;\r\n            }\r\n        }\r\n\r\n        if (refundAmount > 0) _usdtTransfer(msg.sender, refundAmount);\r\n        emit Refunded(msg.sender, tier, count, refundAmount, ratioBps);\r\n    }\r\n\r\n    function freeCash() public view returns (uint256) {\r\n        uint256 bal = IERC20Like(usdt).balanceOf(address(this));\r\n        if (bal <= lockedSettlementFunds) return 0;\r\n        return bal - lockedSettlementFunds;\r\n    }\r\n\r\n    function currentRefundRatioBps() external view returns (uint256) {\r\n        uint256 cash = freeCash();\r\n        uint256 live = totalLiveRefundablePrincipal;\r\n        if (cash == 0 || live == 0) return 0;\r\n        uint256 ratio = (cash * ALPHA_BPS) / live;\r\n        if (ratio > MAX_REFUND_RATIO_BPS) return MAX_REFUND_RATIO_BPS;\r\n        return ratio;\r\n    }\r\n\r\n    function positionCount(address user) external view returns (uint256) {\r\n        return _ownedTokens[user].length;\r\n    }\r\n\r\n    function positionAt(address user, uint256 index) external view returns (uint256 tokenId, uint8 tier, uint40 mintedAt) {\r\n        if (index >= _ownedTokens[user].length) revert InvalidToken();\r\n        tokenId = _ownedTokens[user][index];\r\n        Position memory p = _positionOf[tokenId];\r\n        return (tokenId, p.tier, p.mintedAt);\r\n    }\r\n\r\n    function tierBalance(address user, uint256 tier) external view returns (uint256) {\r\n        return _countTier(user, uint8(tier));\r\n    }\r\n\r\n    function refundPreview(address user, uint256 tier, uint256 count)\r\n        external\r\n        view\r\n        returns (uint256 ratioBps, uint256 faceValue, uint256 refundAmount, uint256 matureCount)\r\n    {\r\n        if (tier == 0 || tier > TIER_COUNT || count == 0) {\r\n            return (0, 0, 0, 0);\r\n        }\r\n        matureCount = _countMature(user, uint8(tier), REFUND_LOCK);\r\n        if (matureCount < count) count = matureCount;\r\n        if (count == 0) return (currentRefundRatioBpsSafe(), 0, 0, 0);\r\n\r\n        faceValue = count * tierPrice[tier];\r\n        uint256 cash = freeCash();\r\n        if (tierSellOpen[tier]) {\r\n            uint256 lockedCut = count * tierSettlePrice[tier];\r\n            uint256 bal = IERC20Like(usdt).balanceOf(address(this));\r\n            uint256 lockedAfter = lockedSettlementFunds >= lockedCut ? lockedSettlementFunds - lockedCut : 0;\r\n            cash = bal > lockedAfter ? bal - lockedAfter : 0;\r\n        }\r\n        ratioBps = currentRefundRatioBpsSafe();\r\n        if (tierSellOpen[tier] && totalLiveRefundablePrincipal == 0) ratioBps = MAX_REFUND_RATIO_BPS;\r\n        if (faceValue == 0) return (ratioBps, 0, 0, matureCount);\r\n        refundAmount = (faceValue * ratioBps) / 10_000;\r\n        if (refundAmount > cash) refundAmount = cash;\r\n        return (ratioBps, faceValue, refundAmount, matureCount);\r\n    }\r\n\r\n    function _onTierCompleted(uint256 completedTier) internal {\r\n        if (completedTier < 4) {\r\n            emit TierCompleted(completedTier, 0, 0, 0);\r\n            return;\r\n        }\r\n\r\n        uint256 targetTier = completedTier - 3;\r\n        uint256 settlePrice = tierPrice[completedTier - 1];\r\n        uint256 outstanding = tierOutstanding[targetTier];\r\n        uint256 obligation = outstanding * settlePrice;\r\n\r\n        if (obligation > 0) lockedSettlementFunds += obligation;\r\n\r\n        uint256 targetFace = outstanding * tierPrice[targetTier];\r\n        uint256 targetNet = (targetFace * (10_000 - MARKETING_FEE_BPS)) / 10_000;\r\n        if (targetNet > 0) {\r\n            if (totalLiveRefundablePrincipal >= targetNet) {\r\n                totalLiveRefundablePrincipal -= targetNet;\r\n            } else {\r\n                totalLiveRefundablePrincipal = 0;\r\n            }\r\n        }\r\n\r\n        tierSettlePrice[targetTier] = settlePrice;\r\n        tierSellOpen[targetTier] = true;\r\n\r\n        emit TierCompleted(completedTier, targetTier, settlePrice, obligation);\r\n    }\r\n\r\n    function _afterTierFilled(uint256 completedTier) internal {\r\n        if (completedTier == 2) {\r\n            earlyPhase = false;\r\n            currentTier = _openSequentialTier();\r\n            emit EarlyPhaseEnded(currentTier);\r\n            return;\r\n        }\r\n        if (completedTier >= 3) {\r\n            currentTier = completedTier + 1;\r\n        }\r\n    }\r\n\r\n    function _isMintOpen(uint256 tier) internal view returns (bool) {\r\n        if (tier == 1 || tier == 2) {\r\n            return tierMinted[tier] < tierSupply[tier];\r\n        }\r\n        if (tierMinted[2] < tierSupply[2]) return false;\r\n        if (tierMinted[tier - 1] < tierSupply[tier - 1]) return false;\r\n        return tier == _openSequentialTier();\r\n    }\r\n\r\n    function _openSequentialTier() internal view returns (uint256) {\r\n        if (tierMinted[2] < tierSupply[2]) return 0;\r\n        for (uint256 i = 3; i <= TIER_COUNT; ++i) {\r\n            if (tierMinted[i] < tierSupply[i]) return i;\r\n        }\r\n        return TIER_COUNT + 1;\r\n    }\r\n\r\n    function currentRefundRatioBpsSafe() internal view returns (uint256) {\r\n        uint256 cash = freeCash();\r\n        uint256 live = totalLiveRefundablePrincipal;\r\n        if (cash == 0 || live == 0) return 0;\r\n        uint256 ratio = (cash * ALPHA_BPS) / live;\r\n        if (ratio > MAX_REFUND_RATIO_BPS) return MAX_REFUND_RATIO_BPS;\r\n        return ratio;\r\n    }\r\n\r\n    function _mintPositions(address user, uint8 tier, uint256 count) internal {\r\n        uint40 stamped = uint40(block.timestamp);\r\n        for (uint256 i; i < count; ++i) {\r\n            uint256 tokenId = _nextTokenId;\r\n            unchecked {\r\n                ++_nextTokenId;\r\n                ++totalSupply;\r\n            }\r\n            _positionOf[tokenId] = Position({tier: tier, mintedAt: stamped});\r\n            _ownerOf[tokenId] = user;\r\n            _ownedIndex[tokenId] = _ownedTokens[user].length;\r\n            _ownedTokens[user].push(tokenId);\r\n            emit Transfer(address(0), user, tokenId);\r\n        }\r\n    }\r\n\r\n    function _burnPositions(address user, uint8 tier, uint256 count, uint256 minAge, bool requireMature)\r\n        internal\r\n    {\r\n        uint256[] storage owned = _ownedTokens[user];\r\n        uint256 removed;\r\n        uint256 nowTs = block.timestamp;\r\n\r\n        while (removed < count) {\r\n            uint256 found = type(uint256).max;\r\n            uint40 bestAt = type(uint40).max;\r\n\r\n            for (uint256 i; i < owned.length; ++i) {\r\n                uint256 tokenId = owned[i];\r\n                Position memory p = _positionOf[tokenId];\r\n                if (p.tier != tier) continue;\r\n                if (requireMature && nowTs < uint256(p.mintedAt) + minAge) continue;\r\n                if (requireMature) {\r\n                    if (p.mintedAt < bestAt) {\r\n                        bestAt = p.mintedAt;\r\n                        found = i;\r\n                    }\r\n                } else {\r\n                    found = i;\r\n                    break;\r\n                }\r\n            }\r\n\r\n            if (found == type(uint256).max) {\r\n                if (requireMature) revert LockActive();\r\n                revert InsufficientPositions();\r\n            }\r\n\r\n            _burnAt(user, found);\r\n            unchecked {\r\n                ++removed;\r\n            }\r\n        }\r\n    }\r\n\r\n    function _burnAt(address user, uint256 index) internal {\r\n        uint256[] storage owned = _ownedTokens[user];\r\n        uint256 tokenId = owned[index];\r\n        uint256 last = owned.length - 1;\r\n        if (index != last) {\r\n            uint256 moved = owned[last];\r\n            owned[index] = moved;\r\n            _ownedIndex[moved] = index;\r\n        }\r\n        owned.pop();\r\n        delete _ownedIndex[tokenId];\r\n        delete _ownerOf[tokenId];\r\n        delete _positionOf[tokenId];\r\n        unchecked {\r\n            --totalSupply;\r\n        }\r\n        emit Transfer(user, address(0), tokenId);\r\n    }\r\n\r\n    function _countTier(address user, uint8 tier) internal view returns (uint256 n) {\r\n        uint256[] storage owned = _ownedTokens[user];\r\n        for (uint256 i; i < owned.length; ++i) {\r\n            if (_positionOf[owned[i]].tier == tier) {\r\n                unchecked {\r\n                    ++n;\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    function _countMature(address user, uint8 tier, uint256 minAge) internal view returns (uint256 n) {\r\n        uint256[] storage owned = _ownedTokens[user];\r\n        uint256 nowTs = block.timestamp;\r\n        for (uint256 i; i < owned.length; ++i) {\r\n            Position memory p = _positionOf[owned[i]];\r\n            if (p.tier == tier && nowTs >= uint256(p.mintedAt) + minAge) {\r\n                unchecked {\r\n                    ++n;\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    function _toString(uint256 value) internal pure returns (string memory) {\r\n        if (value == 0) return \"0\";\r\n        uint256 temp = value;\r\n        uint256 digits;\r\n        while (temp != 0) {\r\n            unchecked {\r\n                ++digits;\r\n                temp /= 10;\r\n            }\r\n        }\r\n        bytes memory buffer = new bytes(digits);\r\n        while (value != 0) {\r\n            unchecked {\r\n                --digits;\r\n            }\r\n            buffer[digits] = bytes1(uint8(48 + (value % 10)));\r\n            value /= 10;\r\n        }\r\n        return string(buffer);\r\n    }\r\n\r\n    function _usdtTransferFrom(address from, address to, uint256 amount) internal {\r\n        if (amount == 0) return;\r\n        _usdtCall(abi.encodeWithSelector(IERC20Like.transferFrom.selector, from, to, amount));\r\n    }\r\n\r\n    function _usdtTransfer(address to, uint256 amount) internal {\r\n        if (amount == 0) return;\r\n        _usdtCall(abi.encodeWithSelector(IERC20Like.transfer.selector, to, amount));\r\n    }\r\n\r\n    function _usdtCall(bytes memory data) internal {\r\n        (bool ok, bytes memory ret) = usdt.call(data);\r\n        if (!ok || (ret.length > 0 && !abi.decode(ret, (bool)))) revert UsdtFailed();\r\n    }\r\n\r\n    function _bootstrapTiers() internal {\r\n        tierSupply[1] = 1_200;\r\n        tierPrice[1] = 0;\r\n        tierSupply[2] = 1_600;\r\n        tierPrice[2] = 0.08 ether;\r\n        tierSupply[3] = 2_200;\r\n        tierPrice[3] = 0.15 ether;\r\n        tierSupply[4] = 3_000;\r\n        tierPrice[4] = 0.19 ether;\r\n        tierSupply[5] = 4_000;\r\n        tierPrice[5] = 0.27 ether;\r\n        tierSupply[6] = 5_200;\r\n        tierPrice[6] = 0.34 ether;\r\n        tierSupply[7] = 6_600;\r\n        tierPrice[7] = 0.48 ether;\r\n        tierSupply[8] = 8_000;\r\n        tierPrice[8] = 0.6 ether;\r\n        tierSupply[9] = 9_000;\r\n        tierPrice[9] = 0.84 ether;\r\n        tierSupply[10] = 10_000;\r\n        tierPrice[10] = 1.04 ether;\r\n        tierSupply[11] = 10_500;\r\n        tierPrice[11] = 1.44 ether;\r\n        tierSupply[12] = 11_000;\r\n        tierPrice[12] = 1.76 ether;\r\n        tierSupply[13] = 11_500;\r\n        tierPrice[13] = 2.4 ether;\r\n        tierSupply[14] = 12_000;\r\n        tierPrice[14] = 2.9 ether;\r\n        tierSupply[15] = 12_500;\r\n        tierPrice[15] = 3.9 ether;\r\n        tierSupply[16] = 13_000;\r\n        tierPrice[16] = 4.65 ether;\r\n        tierSupply[17] = 13_500;\r\n        tierPrice[17] = 6.15 ether;\r\n        tierSupply[18] = 14_000;\r\n        tierPrice[18] = 7.2 ether;\r\n        tierSupply[19] = 14_500;\r\n        tierPrice[19] = 9.35 ether;\r\n        tierSupply[20] = 15_000;\r\n        tierPrice[20] = 10.75 ether;\r\n        tierSupply[21] = 15_600;\r\n        tierPrice[21] = 13.75 ether;\r\n        tierSupply[22] = 16_200;\r\n        tierPrice[22] = 15.5 ether;\r\n        tierSupply[23] = 16_900;\r\n        tierPrice[23] = 19.55 ether;\r\n        tierSupply[24] = 17_600;\r\n        tierPrice[24] = 21.65 ether;\r\n        tierSupply[25] = 18_400;\r\n        tierPrice[25] = 26.8 ether;\r\n        tierSupply[26] = 19_200;\r\n        tierPrice[26] = 29.2 ether;\r\n        tierSupply[27] = 20_000;\r\n        tierPrice[27] = 35.6 ether;\r\n        tierSupply[28] = 21_000;\r\n        tierPrice[28] = 38.2 ether;\r\n        tierSupply[29] = 22_000;\r\n        tierPrice[29] = 46.2 ether;\r\n        tierSupply[30] = 22_000;\r\n        tierPrice[30] = 49.2 ether;\r\n        tierSupply[31] = 22_500;\r\n        tierPrice[31] = 58.7 ether;\r\n        tierSupply[32] = 23_000;\r\n        tierPrice[32] = 62 ether;\r\n        tierSupply[33] = 23_500;\r\n        tierPrice[33] = 73.4 ether;\r\n        tierSupply[34] = 24_000;\r\n        tierPrice[34] = 77 ether;\r\n        tierSupply[35] = 24_500;\r\n        tierPrice[35] = 90.3 ether;\r\n        tierSupply[36] = 24_500;\r\n        tierPrice[36] = 94.2 ether;\r\n        tierSupply[37] = 25_000;\r\n        tierPrice[37] = 109.8 ether;\r\n        tierSupply[38] = 25_000;\r\n        tierPrice[38] = 114 ether;\r\n        tierSupply[39] = 25_000;\r\n        tierPrice[39] = 132.3 ether;\r\n        tierSupply[40] = 25_000;\r\n        tierPrice[40] = 136.8 ether;\r\n        tierSupply[41] = 25_000;\r\n        tierPrice[41] = 158.1 ether;\r\n        tierSupply[42] = 25_000;\r\n        tierPrice[42] = 163 ether;\r\n        tierSupply[43] = 25_000;\r\n        tierPrice[43] = 188 ether;\r\n        tierSupply[44] = 25_000;\r\n        tierPrice[44] = 193 ether;\r\n        tierSupply[45] = 25_000;\r\n        tierPrice[45] = 222 ether;\r\n        tierSupply[46] = 25_000;\r\n        tierPrice[46] = 227 ether;\r\n        tierSupply[47] = 25_000;\r\n        tierPrice[47] = 260 ether;\r\n        tierSupply[48] = 25_000;\r\n        tierPrice[48] = 265 ether;\r\n        tierSupply[49] = 25_000;\r\n        tierPrice[49] = 302 ether;\r\n        tierSupply[50] = 25_000;\r\n        tierPrice[50] = 307 ether;\r\n    }\r\n}\r\n"}},"abi":[{"type":"constructor","inputs":[],"stateMutability":"nonpayable"},{"name":"InsufficientLocked","type":"error","inputs":[]},{"name":"InsufficientPositions","type":"error","inputs":[]},{"name":"InvalidCount","type":"error","inputs":[]},{"name":"InvalidToken","type":"error","inputs":[]},{"name":"LockActive","type":"error","inputs":[]},{"name":"NoAgentId","type":"error","inputs":[]},{"name":"NoFreeCash","type":"error","inputs":[]},{"name":"NonTransferable","type":"error","inputs":[]},{"name":"Reentrancy","type":"error","inputs":[]},{"name":"SellNotOpen","type":"error","inputs":[]},{"name":"SoldOut","type":"error","inputs":[]},{"name":"TierClosed","type":"error","inputs":[]},{"name":"TierNotOpen","type":"error","inputs":[]},{"name":"UsdtFailed","type":"error","inputs":[]},{"name":"WalletLimit","type":"error","inputs":[]},{"name":"WrongChain","type":"error","inputs":[]},{"name":"ZeroAddress","type":"error","inputs":[]},{"name":"EarlyPhaseEnded","type":"event","inputs":[{"name":"nextTier","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"Minted","type":"event","inputs":[{"name":"user","type":"address","indexed":true,"internalType":"address"},{"name":"tier","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"count","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"cost","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"Refunded","type":"event","inputs":[{"name":"user","type":"address","indexed":true,"internalType":"address"},{"name":"tier","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"count","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"refundAmount","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"ratioBps","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"SoldBack","type":"event","inputs":[{"name":"user","type":"address","indexed":true,"internalType":"address"},{"name":"tier","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"count","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"payout","type":"uint256","indexed":false,"internalType":"uint256"}],"anonymous":false},{"name":"TierCompleted","type":"event","inputs":[{"name":"tier","type":"uint256","indexed":true,"internalType":"uint256"},{"name":"unlockedTier","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"settlePrice","type":"uint256","indexed":false,"internalType":"uint256"},{"name":"lockedAdded","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":"ALPHA_BPS","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"CHAIN_ID","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"MARKETING_FEE_BPS","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"MAX_REFUND_RATIO_BPS","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"MAX_TIER1_PER_WALLET","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"MAX_WALLET_HOLDINGS","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"REFUND_LOCK","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"TIER_COUNT","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"admin","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"agentRegistry","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"approve","type":"function","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"pure"},{"name":"balanceOf","type":"function","inputs":[{"name":"owner","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"currentRefundRatioBps","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"currentTier","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"dynamicRefund","type":"function","inputs":[{"name":"tier","type":"uint256","internalType":"uint256"},{"name":"count","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"earlyPhase","type":"function","inputs":[],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"freeCash","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"getApproved","type":"function","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"pure"},{"name":"isApprovedForAll","type":"function","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"pure"},{"name":"lockedSettlementFunds","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"marketingWallet","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"mint","type":"function","inputs":[{"name":"tier","type":"uint256","internalType":"uint256"},{"name":"count","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"mintOpen","type":"function","inputs":[{"name":"tier","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"name","type":"function","inputs":[],"outputs":[{"name":"","type":"string","internalType":"string"}],"stateMutability":"view"},{"name":"ownerOf","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"positionAt","type":"function","inputs":[{"name":"user","type":"address","internalType":"address"},{"name":"index","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"},{"name":"tier","type":"uint8","internalType":"uint8"},{"name":"mintedAt","type":"uint40","internalType":"uint40"}],"stateMutability":"view"},{"name":"positionCount","type":"function","inputs":[{"name":"user","type":"address","internalType":"address"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"positionOf","type":"function","inputs":[{"name":"tokenId","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"tier","type":"uint8","internalType":"uint8"},{"name":"mintedAt","type":"uint40","internalType":"uint40"},{"name":"owner","type":"address","internalType":"address"}],"stateMutability":"view"},{"name":"refundPreview","type":"function","inputs":[{"name":"user","type":"address","internalType":"address"},{"name":"tier","type":"uint256","internalType":"uint256"},{"name":"count","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"ratioBps","type":"uint256","internalType":"uint256"},{"name":"faceValue","type":"uint256","internalType":"uint256"},{"name":"refundAmount","type":"uint256","internalType":"uint256"},{"name":"matureCount","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"safeTransferFrom","type":"function","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"address","internalType":"address"},{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"pure"},{"name":"safeTransferFrom","type":"function","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"address","internalType":"address"},{"name":"","type":"uint256","internalType":"uint256"},{"name":"","type":"bytes","internalType":"bytes"}],"outputs":[],"stateMutability":"pure"},{"name":"sellBack","type":"function","inputs":[{"name":"tier","type":"uint256","internalType":"uint256"},{"name":"count","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"nonpayable"},{"name":"setApprovalForAll","type":"function","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"bool","internalType":"bool"}],"outputs":[],"stateMutability":"pure"},{"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":"tierBalance","type":"function","inputs":[{"name":"user","type":"address","internalType":"address"},{"name":"tier","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"tierMinted","type":"function","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"tierOutstanding","type":"function","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"tierPrice","type":"function","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"tierSellOpen","type":"function","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"bool","internalType":"bool"}],"stateMutability":"view"},{"name":"tierSettlePrice","type":"function","inputs":[{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"tierSupply","type":"function","inputs":[{"name":"","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":"totalLiveRefundablePrincipal","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"totalSupply","type":"function","inputs":[],"outputs":[{"name":"","type":"uint256","internalType":"uint256"}],"stateMutability":"view"},{"name":"transferFrom","type":"function","inputs":[{"name":"","type":"address","internalType":"address"},{"name":"","type":"address","internalType":"address"},{"name":"","type":"uint256","internalType":"uint256"}],"outputs":[],"stateMutability":"pure"},{"name":"usdt","type":"function","inputs":[],"outputs":[{"name":"","type":"address","internalType":"address"}],"stateMutability":"view"}],"matchId":"58351584","creationMatch":"exact_match","runtimeMatch":"exact_match","verifiedAt":"2026-11-04T10:14:54Z","match":"exact_match","chainId":"56","address":"0xe4c75B0D134Fa0ca3d95FA0A4e0d366E80e84311"}