false
true
0

Contract Address Details

0x0E04D1CaC6212447447ad66A5e57a8910425975F

Contract Name
PerpetualBitcoinVault
Creator
0xbad37b–e7b055 at 0xa0c63a–d123ea
Balance
0 PLS ( )
Tokens
Fetching tokens...
Transactions
189 Transactions
Transfers
2,058 Transfers
Gas Used
188,139,472
Last Balance Update
26339599
Warning! Contract bytecode has been changed and doesn't match the verified one. Therefore, interaction with this smart contract may be risky.
This contract has been partially verified via Sourcify. View contract in Sourcify repository
Contract name:
PerpetualBitcoinVault




Optimization enabled
true
Compiler version
v0.8.24+commit.e11b9ed9




Optimization runs
1
EVM Version
shanghai




Verified at
2026-04-09T19:29:09.518927Z

Constructor Arguments

000000000000000000000000b47fa3fda09e61a68a8089e1f4d0f44bd993e6b900000000000000000000000018e89dfc638a61ec010fb269f0c7289d71555d69000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb81400000000000000000000000097b4fec5214e99fc92452614f98c89db55584afe000000000000000000000000c4f586c1ad85e33276e70ea3b39dfa2291f75db40000000000000000000000000deed1486bc52aa0d3e6f8849cec5add6598a1620000000000000000000000009526b745052d259add5dc79bcdc61d0edc68f84b000000000000000000000000165c3410fc91ef562c50559f7d2289febed552d9

Arg [0] (address) : 0xb47fa3fda09e61a68a8089e1f4d0f44bd993e6b9
Arg [1] (address) : 0x18e89dfc638a61ec010fb269f0c7289d71555d69
Arg [2] (address) : 0xa0044761afc6d07cd16b46d8859a948e0e9cb814
Arg [3] (address) : 0x97b4fec5214e99fc92452614f98c89db55584afe
Arg [4] (address) : 0xc4f586c1ad85e33276e70ea3b39dfa2291f75db4
Arg [5] (address) : 0x0deed1486bc52aa0d3e6f8849cec5add6598a162
Arg [6] (address) : 0x9526b745052d259add5dc79bcdc61d0edc68f84b
Arg [7] (address) : 0x165c3410fc91ef562c50559f7d2289febed552d9

              

contracts/Vault.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

/**
 * @title PerpetualBitcoinVault
 * @notice Immutable vault: 21M PB, tracking, unlocks. NO ADMIN. NO UPGRADES.
 * Immutable once full deployment is complete.
 * Phase transition automatic. Unlock math deterministic (1.5555× multiplier).
 */

// By 0xBE369

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC1155/IERC1155.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

// INTERFACES

interface IUniswapV2Pair {
    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
    function token0() external view returns (address);
    function token1() external view returns (address);
    function totalSupply() external view returns (uint256);
    function transfer(address to, uint256 value) external returns (bool);
    function burn(address to, address senderOrigin) external returns (uint256 amount0, uint256 amount1);
}

interface IPBToken is IERC20 {
    function setPairAddress(address _pair) external;
}

interface IPBcToken is IERC20 {
}

interface IPBtNFT is IERC721 {
    function mint(
        address to,
        uint256 tokenId,
        uint256 buyPrice,
        uint256 allocatedPB,
        uint256 allocatedPBc
    ) external;
    function setRecoveryAddress(uint256 tokenId, address recoveryAddress) external;
    function setInheritanceAddress(uint256 tokenId, address inheritanceAddress) external;
    function activateRecovery(uint256 tokenId) external;
    function activateInheritance(uint256 tokenId) external;
    function recordUnlock(uint256 tokenId, uint256 unlockedAmount, uint256 unlockPrice) external;
    function recordDust(uint256 tokenId, uint256 dustAmount) external;
    function burnTracker(uint256 tokenId) external;
}

interface IPBrBadge is IERC1155 {
    function mint(address recipient, uint256 pbtId, string calldata message) external;
    function burn(address holder, uint256 pbtId) external;
}

interface IPBiBadge is IERC1155 {
    function mint(address recipient, uint256 pbtId, string calldata message) external;
    function burn(address holder, uint256 pbtId) external;
}

interface IPriceInterface {
    function getCurrentPrice() external view returns (uint256);
}

interface IPulseXRouter {
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
    function addLiquidity(
        address tokenA,
        address tokenB,
        uint amountADesired,
        uint amountBDesired,
        uint amountAMin,
        uint amountBMin,
        address to,
        uint deadline
    ) external returns (uint amountA, uint amountB, uint liquidity);
}

// ============================================================================
// CONTRACTS
// ============================================================================

contract PerpetualBitcoinVault is ReentrancyGuard {

    // Custom errors (saves ~3500 bytes vs require strings)
    error ZeroAddress();
    error AlreadyLocked();
    error AlreadySet();
    error InvalidAmount();
    error InvalidPrice();
    error Unauthorized();
    error InsufficientBalance();
    error TransferFailed();
    error SlippageTooLow();
    error PasswordTooShort();
    error PasswordMismatch();
    error PriceBelowTrigger();
    error DuplicateUnlockId();
    error NotVLockPosition();
    error BonusAlreadyClaimed();
    error NotExist();

    // Immutable state (set at deployment, locked forever)
    // 2 circular dependency addresses locked via lockImmutableReferences()

    address public immutable DEPLOYER;          // Deployer address (for lockImmutableReferences)
    address public immutable PB_TOKEN;          // Liquid ERC20 token
    address public immutable PBC_TOKEN;         // Locked ERC20 claim token
    address public immutable PBT_TOKEN;         // Tracker ERC721 NFT
    address public immutable PBR_TOKEN;         // Recovery ERC1155 badge
    address public immutable PBI_TOKEN;         // Inheritance ERC1155 badge
    address public immutable USDL_TOKEN;        // USDL stablecoin
    address public immutable PRICE_FEED;        // PulseX price feed
    address public immutable PULSEX_ROUTER;     // PulseX router for swaps
    
    // CIRCULAR DEPENDENCY SOLUTION: These 3 addresses set via lockImmutableReferences()
    address public PULSEX_PAIR;                 // PB/USDL pair (created during seedInitialLP)
    address public LAUNCH_CONVERTER;            // One-time presale converter
    address public LP_REMOVER;                  // PBRemoveUserLP satellite (LP removal middleware)

    // Supply constants (immutable logic, not state)
    uint256 public constant PB_TOTAL_SUPPLY = 21_000_000e18;
    uint256 public constant PBC_TOTAL_SUPPLY = 21_000_000e18;
    uint256 public constant PRESALE_TOTAL = 555 * 10_000e18;     // 5.55M
    uint256 public constant FOUNDER_TOTAL = 37 * 10_000e18;      // 370k
    uint256 public constant LP_ALLOCATION = 1_800_000e18;        // 1.8M
    uint256 public constant REMAINING_OPERATIONAL = 13_650_000e18; // 13.65M

    // Unlock mechanics (immutable formulas)
    uint256 public constant UNLOCK_MULTIPLIER = 15555; // 1.5555× (fixed point: 10000 = 1.0000)
    uint256 public constant UNLOCK_DIVISOR = 10000;
    uint256 public constant TRANCHE_FRACTION = 3; // 1/3 per unlock
    uint256 public constant DUST_THRESHOLD = 15e0; // 15 wei (satoshi equivalent)
    uint256 public constant DUST_CHECK_THRESHOLD = 10e0; // 10 wei for final validation
    
    // NET BUY LP/AMM split (Distribution Phase only)
    // 36.9% of net USDL → LP (paired with vault excess PB, no selling)
    // 63.1% of net USDL → AMM buy (pushes price UP, enables convergent netting)
    uint256 public constant LP_CONTRIBUTION_BPS = 3690; // 36.9% → LP (basis points: 10000 = 100%)
    uint256 public constant AMM_BUY_BPS = 6310;         // 63.1% → AMM buy (10000 - 3690)

    // Sequential netting: O(K) unlock ordering via off-chain indexer
    // No on-chain heap. unlockIds[] passed via calldata as candidate hints and sanitized live.
    uint256 public constant MAX_UNLOCK_PER_BUY = 500;  // Safety ceiling per buy (frontend self-limits to ~250)
    uint256 public constant HARVEST_INTERVAL = 55;      // Harvest LP fees every 55th buy (gas optimization)

    // ========================================================================
    // MUTABLE STATE (Changes during protocol operation)
    // ========================================================================

    // Vault balances
    uint256 public vaultPBBalance = PB_TOTAL_SUPPLY;     // All 21M PB at genesis
    uint256 public vaultPBcBalance = PBC_TOTAL_SUPPLY;   // All 21M PBc at genesis
    uint256 public vaultLPTokenBalance;                  // LP tokens held by Vault for fee harvesting
    uint256 public totalUSDLDistributed;                 // Total USDL paid to users from unlock triggers
    uint256 public buyCount;                             // Buy counter for harvest interval

    // Phase tracking
    bool public isDistributionPhase = true;              // Starts in Distribution
    bool public phaseTransitionExecuted = false;         // One-way transition flag
    bool public immutableReferencesLocked = false;       // One-time lock flag for circular dependencies

    // PBt tracking
    uint256 public pbtIdCounter = 0;                     // Incremental PBt ID generator
    mapping(uint256 => PBtData) public pbtRegistry;      // PBt ID → metadata
    mapping(address => uint256[]) public userPBtIds;     // User → array of owned PBt IDs

    // Active position counter (replaces heap.length for monitoring)
    uint256 public activePositionCount;                  // Incremented on mint, decremented on dust burn

    // Recovery & Inheritance (immutable once set)
    mapping(uint256 => RecoveryData) public recoveryRegistry;
    mapping(uint256 => InheritanceData) public inheritanceRegistry;

    // VLock tracking
    mapping(uint256 => bool) public isVLockPosition;
    mapping(uint256 => bool) public vlockBonusClaimed;

    // ========================================================================
    // DATA STRUCTURES
    // ========================================================================

    /**
     * @notice PBt metadata: Records each purchase and its unlock progression
     */
    struct PBtData {
        uint256 buyPrice;              // Entry price (used for unlock triggers)
        uint256 pbAmount;              // Total PB allocated (3.69% + 96.31%)
        uint256 pbcLocked;             // Current locked amount (decreases with unlocks)
        uint256 nextUnlockIndex;       // Which unlock is next (0, 1, 2, 3...)
        uint256 nextTriggerPrice;      // Pre-computed next trigger price
        uint256 mintBlock;             // Block number when purchased
        address holder;                // Wallet address (binding non-transferable)
        address payoutAddress;         // Active payout address (defaults to holder, changes if recovery/inheritance activated)
    }


    /**
     * @notice Recovery address data (dual-sig activation)
     */
    struct RecoveryData {
        address recoveryAddress;       // Fallback payout address
        bytes32 passwordHash;          // Hashed password for activation
        bool activated;                // One-time use flag
    }

    /**
     * @notice Inheritance address data (dual-sig activation, overrides recovery)
     */
    struct InheritanceData {
        address inheritanceAddress;    // Succession planning address
        bytes32 passwordHash;          // Hashed password for activation
        bool activated;                // One-time use flag
    }


    // ========================================================================
    // EVENTS
    // ========================================================================

    event PresaleAllocated(
        address indexed user,
        uint256 indexed pbtId,
        uint256 pbAmount,
        uint256 buyPrice
    );

    event UnlockTriggered(uint256 indexed pbtId, uint256 unlockIndex, uint256 pbUnlocked, uint256 usdlProceeds, address payoutAddress, uint256 newTriggerPrice, uint256 remainingPBcLocked);
    event PBtBurned(uint256 indexed pbtId, uint256 remainingDust);
    event PhaseTransitioned(bool wasDistribution, uint256 vaultPBBalance, uint256 totalOutstandingPBc);
    event RecoveryAddressSet(uint256 indexed pbtId, address indexed recoveryAddress);
    event RecoveryActivated(uint256 indexed pbtId, address indexed recoveryAddress);
    event InheritanceAddressSet(uint256 indexed pbtId, address indexed inheritanceAddress);
    event InheritanceActivated(uint256 indexed pbtId, address indexed inheritanceAddress);
    event LPContributed(uint256 pbAmount, uint256 usdlAmount);
    event FinalLPContribution(uint256 pbAmount, uint256 usdlAmount);
    event ImmutableReferencesLocked(
        address indexed launchConverter,
        address indexed pulsexPair,
        address indexed lpRemover
    );

    /// Emitted per netted unlock during buy (no AMM sell)
    event UnlockNetted(
        uint256 indexed pbtId,
        uint256 unlockIndex,
        uint256 pbcSettled,
        uint256 usdlPaid,
        address payoutAddress,
        uint256 settlementPrice,
        uint256 newTriggerPrice,
        uint256 remainingPBcLocked
    );

    /// Emitted for each buy with full netting details
    event BuyWithNetting(
        address indexed buyer,
        address indexed recipient,
        uint256 indexed pbtId,
        uint256 usdlIn,
        uint256 totalPBOut,
        uint256 nettedPB,
        uint256 ammPB,
        uint256 lpPB,
        uint256 lpUSDL,
        uint256 unlocksNetted
    );

    // ========================================================================
    // CONSTRUCTOR (Immutable initialization)
    // ========================================================================

    /**
     * @notice Deploy Vault with immutable references
     * @dev CIRCULAR DEPENDENCY SOLUTION: LaunchConverter and PulsexPair
     *      are set to address(0) initially and locked via lockImmutableReferences() after deployment.
     *      This solves the circular dependency where Vault needs their addresses but they need Vault's.
     * 
     * @param _pbToken PB liquid token address
     * @param _pbcToken PBc locked claim token address
     * @param _pbtToken PBt tracker NFT address
     * @param _pbrToken PBr recovery badge address
     * @param _pbiToken PBi inheritance badge address
     * @param _usdlToken USDL stablecoin address
     * @param _priceFeed PulseXInterface price feed address
     * @param _pulsexRouter PulseX router address
     */
    constructor(
        address _pbToken,
        address _pbcToken,
        address _pbtToken,
        address _pbrToken,
        address _pbiToken,
        address _usdlToken,
        address _priceFeed,
        address _pulsexRouter
    ) {
        DEPLOYER = msg.sender;
        // Validate all addresses are non-zero (except circular dependency addresses)
        if (_pbToken == address(0)) revert ZeroAddress();
        if (_pbcToken == address(0)) revert ZeroAddress();
        if (_pbtToken == address(0)) revert ZeroAddress();
        if (_pbrToken == address(0)) revert ZeroAddress();
        if (_pbiToken == address(0)) revert ZeroAddress();
        if (_usdlToken == address(0)) revert ZeroAddress();
        if (_priceFeed == address(0)) revert ZeroAddress();
        if (_pulsexRouter == address(0)) revert ZeroAddress();
        // Note: PULSEX_PAIR, LAUNCH_CONVERTER set via lockImmutableReferences()

        // Set immutable references
        PB_TOKEN = _pbToken;
        PBC_TOKEN = _pbcToken;
        PBT_TOKEN = _pbtToken;
        PBR_TOKEN = _pbrToken;
        PBI_TOKEN = _pbiToken;
        USDL_TOKEN = _usdlToken;
        PRICE_FEED = _priceFeed;
        PULSEX_ROUTER = _pulsexRouter;
        
        // PULSEX_PAIR, LAUNCH_CONVERTER remain address(0)
        // Will be set via lockImmutableReferences() after deployment
    }

    /**
     * @notice Receive native PLS from PulseX router swaps
     */
    receive() external payable {}

    // ========================================================================
    // SET ONCE - Immutable Initialization (Called exactly once at deployment)
    // ========================================================================

    /**
     * @notice Lock immutable contract references permanently (ONE-TIME ONLY)
     * @dev CIRCULAR DEPENDENCY SOLUTION: Vault needs LaunchConverter address,
     *      but it needs Vault address. This function breaks the circular dependency by
     *      allowing these 2 addresses to be set after deployment, then locked forever.
     *      
     *      Once locked, these addresses become effectively immutable and can NEVER be changed.
     *      This is the ONLY exception to the immutable-by-default architecture.
     *      
     * @param _launchConverter LaunchConverter contract address
     * @param _pulsexPair PB/USDL PulseX pair address (created during seedInitialLP)
     */
    function lockImmutableReferences(
        address _launchConverter,
        address _pulsexPair,
        address _lpRemover
    ) external {
        if (msg.sender != DEPLOYER) revert Unauthorized();
        if (immutableReferencesLocked) revert AlreadyLocked();
        if (LAUNCH_CONVERTER != address(0)) revert AlreadySet();
        if (PULSEX_PAIR != address(0)) revert AlreadySet();
        
        if (_launchConverter == address(0)) revert ZeroAddress();
        if (_pulsexPair == address(0)) revert ZeroAddress();
        if (_lpRemover == address(0)) revert ZeroAddress();
        
        LAUNCH_CONVERTER = _launchConverter;
        PULSEX_PAIR = _pulsexPair;
        LP_REMOVER = _lpRemover;
        
        // SECURITY: Tell PB about the pair so it can enforce vault-only withdrawals
        IPBToken(PB_TOKEN).setPairAddress(_pulsexPair);
        
        immutableReferencesLocked = true;
        
        emit ImmutableReferencesLocked(_launchConverter, _pulsexPair, _lpRemover);
    }



    /// === SET ONCE (Deployment) ===
    /// Seed initial LP only once, then immutable forever
    /**
     * @notice Seed initial PB/USDL LP at target price (0.05555 USDL per PB, one-time)
     */
    function seedInitialLP(
        uint256 usdlAmount,
        uint256 minPB,
        uint256 minUSDL,
        uint256 deadline
    ) external nonReentrant onlyLaunchConverter returns (uint256 amountA, uint256 amountB, uint256 liquidity) {
        if (initialLPSeeded) revert AlreadySet();
        if (usdlAmount == 0) revert InvalidAmount();

        // Vault must already hold the USDL
        uint256 usdlBal = IERC20(USDL_TOKEN).balanceOf(address(this));
        if (usdlBal < usdlAmount) revert InsufficientBalance();

        // Compute required PB so price = INITIAL_TARGET_PRICE (USDL per PB scaled 1e18)
        // pbRequired = usdlAmount * 1e18 / INITIAL_TARGET_PRICE
        uint256 pbRequired = (usdlAmount * 1e18) / INITIAL_TARGET_PRICE;
        if (pbRequired == 0) revert InvalidAmount();
        if (vaultPBBalance < pbRequired) revert InsufficientBalance();

        // Add slippage protection validation (5% tolerance)
        if (minPB < pbRequired * 95 / 100) revert SlippageTooLow();
        if (minUSDL < usdlAmount * 95 / 100) revert SlippageTooLow();

        // Approve router to pull tokens
        IERC20(USDL_TOKEN).approve(PULSEX_ROUTER, usdlAmount);
        IERC20(PB_TOKEN).approve(PULSEX_ROUTER, pbRequired);

        // Call router.addLiquidity (UniswapV2-style)
        (amountA, amountB, liquidity) = IPulseXRouter(PULSEX_ROUTER).addLiquidity(
            PB_TOKEN,
            USDL_TOKEN,
            pbRequired,
            usdlAmount,
            minPB,
            minUSDL,
            address(this),
            deadline
        );

        // Adjust Vault accounting: remove the PB used for LP from vaultPBBalance
        _deductVaultPB(amountA);

        initialLPSeeded = true;
        vaultLPTokenBalance = vaultLPTokenBalance + liquidity;

        // Set K-tracking baseline for fee extraction
        // K per LP only grows from trading fees, so this captures the starting point
        {
            (uint112 r0, uint112 r1,) = IUniswapV2Pair(PULSEX_PAIR).getReserves();
            uint256 lpTotal = IUniswapV2Pair(PULSEX_PAIR).totalSupply();
            if (lpTotal > 0) {
                lastKPerLP = (_sqrt(uint256(r0) * uint256(r1)) * 1e18) / lpTotal;
            }
        }

        emit InitialLPSeeded(amountB, amountA, liquidity);
        return (amountA, amountB, liquidity);
    }

    // ========================================================================
    // PHASE 0: PRESALE ALLOCATION
    // ========================================================================

    /**
     * @notice Allocate presale purchase to user
     *
     * @param user Recipient wallet
     * @param pbAmount Total PB for this block (variable based on entry price)
     * @param entryPrice Entry price recorded in presale NFT (0.0369 → 0.05352)
     */
    function allocatePresaleUser(
        address user,
        uint256 pbAmount,
        uint256 entryPrice
    ) external nonReentrant onlyLaunchConverter {
        if (user == address(0)) revert ZeroAddress();
        if (pbAmount == 0) revert InvalidAmount();
        if (entryPrice == 0) revert InvalidPrice();

        (uint256 liquidPB, uint256 lockedPBc) = _computeSplit(pbAmount);

        // Step 1: Transfer 3.69% liquid PB to user
        _transferPBFromVault(user, liquidPB);

        // Step 2: Transfer 96.31% locked PBc to user
        _transferPBcFromVault(user, lockedPBc);

        // Step 3: Create and mint PBt tracker
        uint256 pbtId = _mintPBt(user, pbAmount, lockedPBc, entryPrice);

        // Check phase transition
        _checkPhaseTransition();

        emit PresaleAllocated(user, pbtId, pbAmount, entryPrice);
    }

    // ========================================================================
    // PHASE 2: UNLOCK EXECUTION (Permissionless — anyone can trigger)
    // ========================================================================

    /**
     * @notice Execute unlock when price reaches trigger (permissionless)
     */
    function executeUnlock(uint256 pbtId) external nonReentrant {
        _executeUnlock(pbtId, false);
    }

    /// Internal unlock executor
    /// @param skipPriceCheck When true, skip PRICE_FEED verification (used by interleaved loop
    ///        which already bought to push price to trigger — avoids rounding revert).
    function _executeUnlock(uint256 pbtId, bool skipPriceCheck) internal {
        PBtData storage pbt = pbtRegistry[pbtId];
        if (pbt.holder == address(0)) revert NotExist();
        uint256 unlockIndex = pbt.nextUnlockIndex;

        // SECURITY: Verify market price meets or exceeds the unlock trigger
        // Skipped when called from interleaved buy loop (price already pushed by AMM buy)
        if (!skipPriceCheck) {
            uint256 currentPrice = IPriceInterface(PRICE_FEED).getCurrentPrice();
            if (currentPrice < pbt.nextTriggerPrice) revert PriceBelowTrigger();
        }

        // Check if dust threshold reached (< 15 sat before 1/3 calculation)
        bool isDust = pbt.pbcLocked < DUST_THRESHOLD;

        uint256 unlockedPBc;
        if (isDust) {
            // Sell all remaining dust
            unlockedPBc = pbt.pbcLocked;
            pbt.pbcLocked = 0;
        } else {
            // Normal unlock: sell 1/3 of remaining
            unlockedPBc = pbt.pbcLocked / TRANCHE_FRACTION;
            pbt.pbcLocked = pbt.pbcLocked - unlockedPBc;
        }

        // Determine payout address (inheritance > recovery > holder)
        address payoutAddress = pbt.holder;
        if (inheritanceRegistry[pbtId].activated) {
            payoutAddress = inheritanceRegistry[pbtId].inheritanceAddress;
        } else if (recoveryRegistry[pbtId].activated) {
            payoutAddress = recoveryRegistry[pbtId].recoveryAddress;
        }

        // Transfer user's PBc back to Vault (no burning - supply stays 21M)
        // PBc contract allows Vault to transfer from any holder to itself (no approval needed)
        IERC20(PBC_TOKEN).transferFrom(pbt.holder, address(this), unlockedPBc);
        _addVaultPBc(unlockedPBc);

        // Sell PB to AMM and get USDL proceeds
        uint256 usdlProceeds = _sellPBtoAMM(unlockedPBc);
        _deductVaultPB(unlockedPBc);

        // Send USDL proceeds directly to payout address
        if (!IERC20(USDL_TOKEN).transfer(payoutAddress, usdlProceeds)) revert TransferFailed();
        
        // Track total USDL distributed
        totalUSDLDistributed = totalUSDLDistributed + usdlProceeds;

        // Record on PBt metadata (for frontend queries) — before trigger bump
        if (isDust) {
            IPBtNFT(PBT_TOKEN).recordDust(pbtId, unlockedPBc);
        } else {
            IPBtNFT(PBT_TOKEN).recordUnlock(pbtId, unlockedPBc, pbt.nextTriggerPrice);
        }

        // Increment unlock index if not dust
        if (!isDust) {
            pbt.nextUnlockIndex++;
            pbt.nextTriggerPrice = computeNextTriggerPrice(pbt.buyPrice, pbt.nextUnlockIndex);
        }

        // Burn PBt if dust cleared
        if (isDust || pbt.pbcLocked < DUST_CHECK_THRESHOLD) {
            _movePBtOwnership(pbtId, pbt.holder, address(0));
            pbt.holder = address(0);
            pbt.payoutAddress = address(0);
            IPBtNFT(PBT_TOKEN).burnTracker(pbtId);
            if (activePositionCount > 0) activePositionCount--;
            emit PBtBurned(pbtId, pbt.pbcLocked);
        }

        // Check phase transition
        _checkPhaseTransition();

        emit UnlockTriggered(pbtId, unlockIndex, unlockedPBc, usdlProceeds, payoutAddress, pbt.nextTriggerPrice, pbt.pbcLocked);
    }

    // ========================================================================
    // RECOVERY & INHERITANCE (Dual-signature succession planning)
    // ========================================================================

    /**
     * @notice Set recovery address (fallback if wallet hacked, one-time setup)
     */
    function setRecoveryAddress(
        uint256 pbtId,
        address recoveryAddr,
        bytes32 passwordHash,
        string calldata message
    ) external nonReentrant {
        _setSuccessionAddress(pbtId, recoveryAddr, passwordHash, message, false);
    }

    /**
     * @notice Activate recovery address (dual-sig with password)
     * Called by recovery address holder with password to redirect payouts
     *
     * @param pbtId Tracker NFT ID
     * @param password Plaintext password (hashed and verified)
     */
    function activateRecovery(uint256 pbtId, string calldata password) external nonReentrant {
        _activateSuccession(pbtId, password, false);
    }

    /**
     * @notice Set inheritance address (succession planning, overrides recovery)
     * Can only be set once per PBt
     *
     * @param pbtId Tracker NFT ID
     * @param inheritanceAddr Successor payout address
     * @param passwordHash Hashed password for activation
     */
    function setInheritanceAddress(
        uint256 pbtId,
        address inheritanceAddr,
        bytes32 passwordHash,
        string calldata message
    ) external nonReentrant {
        _setSuccessionAddress(pbtId, inheritanceAddr, passwordHash, message, true);
    }

    /**
     * @notice Activate inheritance address (dual-sig with password)
     * Called by inheritance address holder with password to redirect payouts
     * OVERRIDES recovery address if both are set
     *
     * @param pbtId Tracker NFT ID
     * @param password Plaintext password (hashed and verified)
     */
    function activateInheritance(uint256 pbtId, string calldata password) external nonReentrant {
        _activateSuccession(pbtId, password, true);
    }

    function _setSuccessionAddress(
        uint256 pbtId,
        address successorAddr,
        bytes32 passwordHash,
        string calldata message,
        bool isInheritance
    ) internal {
        PBtData storage pbt = pbtRegistry[pbtId];
        if (msg.sender != pbt.holder) revert Unauthorized();
        if (successorAddr == address(0)) revert ZeroAddress();
        if (passwordHash == bytes32(0)) revert InvalidAmount();

        if (isInheritance) {
            InheritanceData storage inheritance = inheritanceRegistry[pbtId];
            if (inheritance.activated) revert AlreadySet();
            if (inheritance.inheritanceAddress != address(0)) revert AlreadySet();

            inheritanceRegistry[pbtId] = InheritanceData({
                inheritanceAddress: successorAddr,
                passwordHash: passwordHash,
                activated: false
            });

            IPBiBadge(PBI_TOKEN).mint(successorAddr, pbtId, message);
            IPBtNFT(PBT_TOKEN).setInheritanceAddress(pbtId, successorAddr);
            emit InheritanceAddressSet(pbtId, successorAddr);
        } else {
            RecoveryData storage recovery = recoveryRegistry[pbtId];
            if (recovery.activated) revert AlreadySet();
            if (recovery.recoveryAddress != address(0)) revert AlreadySet();

            recoveryRegistry[pbtId] = RecoveryData({
                recoveryAddress: successorAddr,
                passwordHash: passwordHash,
                activated: false
            });

            IPBrBadge(PBR_TOKEN).mint(successorAddr, pbtId, message);
            IPBtNFT(PBT_TOKEN).setRecoveryAddress(pbtId, successorAddr);
            emit RecoveryAddressSet(pbtId, successorAddr);
        }
    }

    function _activateSuccession(uint256 pbtId, string calldata password, bool isInheritance) internal {
        address newHolder;

        if (isInheritance) {
            InheritanceData storage inheritance = inheritanceRegistry[pbtId];
            if (msg.sender != inheritance.inheritanceAddress) revert Unauthorized();
            if (inheritance.activated) revert AlreadySet();
            if (bytes(password).length < 11) revert PasswordTooShort();
            if (keccak256(abi.encodePacked(password)) != inheritance.passwordHash) revert PasswordMismatch();
            inheritance.activated = true;
            newHolder = inheritance.inheritanceAddress;
        } else {
            RecoveryData storage recovery = recoveryRegistry[pbtId];
            if (msg.sender != recovery.recoveryAddress) revert Unauthorized();
            if (recovery.activated) revert AlreadySet();
            if (bytes(password).length < 11) revert PasswordTooShort();
            if (keccak256(abi.encodePacked(password)) != recovery.passwordHash) revert PasswordMismatch();
            recovery.activated = true;
            newHolder = recovery.recoveryAddress;
        }

        PBtData storage pbt = pbtRegistry[pbtId];
        address oldHolder = pbt.holder;
        pbt.payoutAddress = newHolder;
        pbt.holder = newHolder;

        _movePBtOwnership(pbtId, oldHolder, newHolder);

        uint256 pbcAmount = pbt.pbcLocked;
        IERC20(PBC_TOKEN).transferFrom(oldHolder, address(this), pbcAmount);
        _addVaultPBc(pbcAmount);
        _transferPBcFromVault(newHolder, pbcAmount);

        if (isInheritance) {
            IPBtNFT(PBT_TOKEN).activateInheritance(pbtId);
            IPBiBadge(PBI_TOKEN).burn(msg.sender, pbtId);
            emit InheritanceActivated(pbtId, newHolder);
        } else {
            IPBtNFT(PBT_TOKEN).activateRecovery(pbtId);
            IPBrBadge(PBR_TOKEN).burn(msg.sender, pbtId);
            emit RecoveryActivated(pbtId, newHolder);
        }
    }

    // ========================================================================
    // INTERNAL FUNCTIONS (Shared helpers)
    // - Marked helpers below are used across multiple flows and should be
    //   reviewed carefully before modification.
    // ========================================================================

    /// Move PBt ownership tracking (userPBtIds only - NFT transfer handled by IPBtNFT)
    function _movePBtOwnership(uint256 pbtId, address oldHolder, address newHolder) internal {
        if (oldHolder != address(0)) {
            uint256[] storage ids = userPBtIds[oldHolder];
            for (uint256 i = 0; i < ids.length; i++) {
                if (ids[i] == pbtId) {
                    ids[i] = ids[ids.length - 1];
                    ids.pop();
                    break;
                }
            }
        }

        if (newHolder != address(0)) {
            userPBtIds[newHolder].push(pbtId);
        }
    }

    /// Compute 3.69% liquid / 96.31% locked split
    function _computeSplit(uint256 pbAmount) internal pure returns (uint256 liquidPB, uint256 lockedPBc) {
        liquidPB = (pbAmount * 369) / 10_000;
        lockedPBc = pbAmount - liquidPB;
    }

    /// Vault balance accounting helpers
    function _addVaultPB(uint256 amount) internal { vaultPBBalance += amount; }
    function _addVaultPBc(uint256 amount) internal { vaultPBcBalance += amount; }
    function _deductVaultPB(uint256 amount) internal {
        if (vaultPBBalance < amount) revert InsufficientBalance();
        vaultPBBalance -= amount;
    }
    function _deductVaultPBc(uint256 amount) internal {
        if (vaultPBcBalance < amount) revert InsufficientBalance();
        vaultPBcBalance -= amount;
    }

    /// Transfer PB from vault with accounting
    function _transferPBFromVault(address recipient, uint256 amount) internal {
        _deductVaultPB(amount);
        if (!IERC20(PB_TOKEN).transfer(recipient, amount)) revert TransferFailed();
    }

    /// Transfer PBc from vault with accounting
    function _transferPBcFromVault(address recipient, uint256 amount) internal {
        _deductVaultPBc(amount);
        if (!IERC20(PBC_TOKEN).transfer(recipient, amount)) revert TransferFailed();
    }

    /// Mint PBt tracker NFT with buy data
    function _mintPBt(
        address holder,
        uint256 pbAmount,
        uint256 lockedPBc,
        uint256 currentPrice
    ) internal returns (uint256 pbtId) {
        pbtId = ++pbtIdCounter;
        pbtRegistry[pbtId] = PBtData({
            buyPrice: currentPrice,
            pbAmount: pbAmount,
            pbcLocked: lockedPBc,
            nextUnlockIndex: 0,
            nextTriggerPrice: computeNextTriggerPrice(currentPrice, 0),
            mintBlock: block.number,
            holder: holder,
            payoutAddress: holder
        });
        userPBtIds[holder].push(pbtId);
        activePositionCount++;

        IPBtNFT(PBT_TOKEN).mint(holder, pbtId, currentPrice, pbAmount, lockedPBc);
    }

    /// Sell PB on AMM → returns USDL proceeds.
    /// Centralized: approvals, router calls, slippage protection.
    function _sellPBtoAMM(uint256 pbAmount) internal returns (uint256) {
        if (pbAmount == 0) revert InvalidAmount();

        // Compute minOut using actual AMM curve math (not linear price estimate)
        // This correctly accounts for price impact on any pool depth.
        (uint256 reservePB, uint256 reserveUSDL) = _getPairReserves();
        uint256 expectedOut = _getAmountOut(pbAmount, reservePB, reserveUSDL);
        uint256 minOut = (expectedOut * 95) / 100; // 5% tolerance for rounding only

        // Approve PulseX router
        IERC20(PB_TOKEN).approve(PULSEX_ROUTER, pbAmount);

        // Execute swap: PB → USDL
        address[] memory path = new address[](2);
        path[0] = PB_TOKEN;
        path[1] = USDL_TOKEN;

        uint[] memory amounts = IPulseXRouter(PULSEX_ROUTER).swapExactTokensForTokens(
            pbAmount,
            minOut, // Minimum USDL output with slippage protection
            path,
            address(this), // Vault receives USDL
            block.timestamp + 3600
        );

        return amounts[1];
    }

    /// Auto-transition Distribution → Final when vault PB <= outstanding PBc
    function _checkPhaseTransition() internal {
        if (isDistributionPhase && !phaseTransitionExecuted) {
            uint256 outstandingPBc = totalOutstandingPBc();
            if (vaultPBBalance <= outstandingPBc) {
                isDistributionPhase = false;
                phaseTransitionExecuted = true;
                emit PhaseTransitioned(true, vaultPBBalance, outstandingPBc);
            }
        }
    }

    // ========================================================================
    // NETTING ENGINE — Sequential Scanner (replaces heap-based scanner)
    // ========================================================================

    /// UniV2 getAmountOut: how many tokensOut for a given tokensIn swap?
    /// Used in sequential netting scan for virtual AMM math.
    function _getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut)
        internal pure returns (uint256)
    {
        uint256 amountInWithFee = amountIn * 997;
        return (amountInWithFee * reserveOut) / (reserveIn * 1000 + amountInWithFee);
    }

    /// How much USDL to swap into the pair to push price to targetPrice?
    /// UniV2 price = reserveUSDL / reservePB. Derived from constant-product with 0.3% fee.
    /// Returns 0 if current price already >= target.
    function _computeUSDLForPrice(
        uint256 reservePB, uint256 reserveUSDL, uint256 targetPrice
    ) internal pure returns (uint256 usdlNeeded) {
        // Current price = reserveUSDL * 1e18 / reservePB
        // Target: (reserveUSDL + usdlIn * 0.997) / (reservePB - pbOut) = targetPrice / 1e18
        // From constant product: (reserveUSDL + usdlIn*0.997) * (reservePB - pbOut) = reserveUSDL * reservePB
        // Solve for usdlIn that achieves targetPrice.
        //
        // After swap: newReserveUSDL = reserveUSDL + usdlIn
        //             newReservePB   = reservePB - pbOut
        // Price = newReserveUSDL / newReservePB = targetPrice / 1e18
        // From constant product (with fee): newReservePB = (reservePB * reserveUSDL * 1000) / (reserveUSDL * 1000 + usdlIn * 997)
        //                                   newReserveUSDL = reserveUSDL + usdlIn
        // So: (reserveUSDL + usdlIn) / newReservePB = targetPrice / 1e18
        // Substituting newReservePB:
        // (reserveUSDL + usdlIn) * (reserveUSDL * 1000 + usdlIn * 997) = targetPrice * reservePB * reserveUSDL * 1000 / 1e18
        //
        // This is quadratic in usdlIn. For simplicity and gas efficiency, use sqrt approach:
        // Let k = reservePB * reserveUSDL (constant product pre-fee)
        // After fee: effective k with fee adjustment
        //
        // Simpler derivation: we want newPrice = targetPrice/1e18
        // newReserveUSDL = sqrt(k * newPrice) approximately
        // usdlIn = newReserveUSDL - reserveUSDL (adjusted for fee)
        //
        // Exact formula for UniV2 with 0.3% fee:
        // newReservePB = reservePB * reserveUSDL * 1000 / (reserveUSDL * 1000 + usdlIn * 997)
        // newReserveUSDL = reserveUSDL + usdlIn
        // newReserveUSDL * 1e18 / newReservePB = targetPrice
        //
        // Substituting: (reserveUSDL + usdlIn) * (reserveUSDL * 1000 + usdlIn * 997) * 1e18
        //             = targetPrice * reservePB * reserveUSDL * 1000
        //
        // Let x = usdlIn, R_u = reserveUSDL, R_p = reservePB, T = targetPrice
        // (R_u + x)(R_u * 1000 + x * 997) = T * R_p * R_u * 1000 / 1e18
        // 997*x^2 + (R_u*997 + R_u*1000)*x + R_u^2*1000 = T*R_p*R_u*1000/1e18
        // 997*x^2 + R_u*1997*x + R_u^2*1000 - T*R_p*R_u*1000/1e18 = 0
        //
        // Use quadratic formula: x = (-b + sqrt(b^2 - 4ac)) / 2a
        // where a = 997, b = R_u * 1997, c = R_u^2 * 1000 - T * R_p * R_u * 1000 / 1e18

        uint256 currentPrice = (reserveUSDL * 1e18) / reservePB;
        if (currentPrice >= targetPrice) return 0;

        // a = 997
        // b = reserveUSDL * 1997
        // c = reserveUSDL^2 * 1000 - targetPrice * reservePB * reserveUSDL * 1000 / 1e18
        // Note: c is negative when targetPrice > currentPrice (which is our case),
        // so |c| = targetPrice * reservePB * reserveUSDL * 1000 / 1e18 - reserveUSDL^2 * 1000

        uint256 b = reserveUSDL * 1997;
        uint256 absC = (targetPrice * reservePB * reserveUSDL * 1000 / 1e18) - (reserveUSDL * reserveUSDL * 1000);

        // discriminant = b^2 + 4*a*|c| (because c is negative, -4ac = +4a|c|)
        uint256 discriminant = b * b + 4 * 997 * absC;
        uint256 sqrtDisc = _sqrt(discriminant);

        // x = (-b + sqrtDisc) / (2 * 997) — take positive root
        usdlNeeded = (sqrtDisc - b) / (2 * 997);

        // Add 1 wei to ensure we reach or exceed target price (rounding)
        usdlNeeded += 1;
    }

    /// Netting scan result — returned by _sequentialNettingScan
    struct NettingScanResult {
        uint256 pbtId;
        uint256 unlockPBc;       // PBc credited to buyer side
        uint256 fullTranche;     // Full tranche removed from holder side
        uint256 triggerPrice;
        uint256 unlockUSDL;      // USDL owed to holder
        address payoutAddress;
        bool isDust;
        bool isPartial;
    }

    struct PartialTriggerInfo {
        uint256 pbtId;
        uint256 unsettledPBc;
        uint256 unsettledOwed;
        uint256 triggerPrice;
        address payoutAddress;
    }

    struct PendingPartialSettlement {
        uint256 pbtId;
        uint256 fullTranche;
        uint256 buyerPBc;
        uint256 holderUSDL;
        uint256 triggerPrice;
        address payoutAddress;
        bool isDust;
        bool exists;
    }

    struct BuyExecutionSummary {
        uint256 preBuyPrice;
        uint256 nettedPBc;
        uint256 compensationPB;
        uint256 lpUSDLUsed;
        uint256 ammPB;
        uint256 nettedCount;
    }

    function _resolvePayoutAddress(uint256 pbtId, address defaultPayout) internal view returns (address payoutAddr) {
        payoutAddr = defaultPayout;
        if (inheritanceRegistry[pbtId].activated) {
            payoutAddr = inheritanceRegistry[pbtId].inheritanceAddress;
        } else if (recoveryRegistry[pbtId].activated) {
            payoutAddr = recoveryRegistry[pbtId].recoveryAddress;
        }
    }

    /// Sanitize caller hint IDs against live state.
    /// Keeps only live positions, removes duplicates, and sorts by live trigger asc then pbtId asc.
    function _sanitizeUnlockHints(uint256[] calldata unlockIds)
        internal
        view
        returns (uint256[] memory liveIds, uint256 liveCount)
    {
        uint256 len = unlockIds.length;
        liveIds = new uint256[](len);
        uint256[] memory liveTriggers = new uint256[](len);

        for (uint256 i = 0; i < len; i++) {
            uint256 id = unlockIds[i];
            PBtData storage pbt = pbtRegistry[id];
            if (pbt.holder == address(0) || pbt.pbcLocked == 0) continue;

            bool duplicate = false;
            for (uint256 j = 0; j < liveCount; j++) {
                if (liveIds[j] == id) {
                    duplicate = true;
                    break;
                }
            }
            if (duplicate) continue;

            uint256 triggerPrice = pbt.nextTriggerPrice;
            uint256 insertAt = liveCount;

            while (insertAt > 0) {
                uint256 prevId = liveIds[insertAt - 1];
                uint256 prevTrigger = liveTriggers[insertAt - 1];
                if (prevTrigger < triggerPrice) break;
                if (prevTrigger == triggerPrice && prevId < id) break;

                liveIds[insertAt] = prevId;
                liveTriggers[insertAt] = prevTrigger;
                insertAt--;
            }

            liveIds[insertAt] = id;
            liveTriggers[insertAt] = triggerPrice;
            liveCount++;
        }
    }

    /// Sequential netting scan: walk unlockIds one-by-one, tracking virtual AMM price.
    /// Returns which positions can be netted off-AMM within buyer's budget.
    /// Virtual reserves model the price path without touching the real AMM.
    /// Budget = usdlAmount. Each trigger costs: vBuy (push virtual price) + settlement (USDL to holder).
    /// Virtual price only goes up during the scan — absorbed PBc never touches the AMM here.
    function _sequentialNettingScan(
        uint256 usdlAmount,
        uint256 reservePB,
        uint256 reserveUSDL,
        uint256[] memory unlockIds,
        uint256 unlockCount
    ) internal view returns (
        NettingScanResult[] memory nettedResults,
        uint256 nettedCount,
        uint256 totalNettedSettlement,
        uint256 totalVBuy,
        uint256 nettingStopIdx,
        PartialTriggerInfo memory partialTrigger,
        bool hasPartial
    ) {
        uint256 len = unlockCount;
        nettedResults = new NettingScanResult[](len);
        nettingStopIdx = len; // default: all netted (overwritten if we stop early)

        uint256 vReservePB = reservePB;
        uint256 vReserveUSDL = reserveUSDL;
        uint256 usdlBudget = usdlAmount;

        for (uint256 i = 0; i < len; i++) {
            uint256 id = unlockIds[i];
            PBtData storage pbt = pbtRegistry[id];

            // Skip invalid positions
            if (pbt.holder == address(0) || pbt.pbcLocked == 0) continue;

            uint256 triggerPrice = pbt.nextTriggerPrice;
            uint256 vBuy = _computeUSDLForPrice(vReservePB, vReserveUSDL, triggerPrice);
            bool isDust = pbt.pbcLocked < DUST_THRESHOLD;
            uint256 tranche = isDust ? pbt.pbcLocked : pbt.pbcLocked / TRANCHE_FRACTION;
            uint256 settlement = (tranche * triggerPrice) / 1e18;

            if (usdlBudget < vBuy || usdlBudget - vBuy < settlement) {
                if (usdlBudget >= vBuy) {
                    nettingStopIdx = i + 1;

                    if (vBuy > 0) {
                        uint256 partialPbBought = _getAmountOut(vBuy, vReserveUSDL, vReservePB);
                        vReserveUSDL += vBuy;
                        vReservePB -= partialPbBought;
                        usdlBudget -= vBuy;
                        totalVBuy += vBuy;
                    }

                    uint256 partialPayment = usdlBudget;
                    uint256 partialPBc = settlement > 0 ? (tranche * partialPayment) / settlement : 0;
                    uint256 unsettledPBc = tranche - partialPBc;
                    uint256 unsettledOwed = settlement - partialPayment;
                    address partialPayout = _resolvePayoutAddress(id, pbt.payoutAddress);

                    usdlBudget = 0;
                    totalNettedSettlement += partialPayment;

                    nettedResults[nettedCount] = NettingScanResult({
                        pbtId: id,
                        unlockPBc: partialPBc,
                        fullTranche: tranche,
                        triggerPrice: triggerPrice,
                        unlockUSDL: partialPayment,
                        payoutAddress: partialPayout,
                        isDust: isDust,
                        isPartial: true
                    });
                    nettedCount++;

                    partialTrigger = PartialTriggerInfo({
                        pbtId: id,
                        unsettledPBc: unsettledPBc,
                        unsettledOwed: unsettledOwed,
                        triggerPrice: triggerPrice,
                        payoutAddress: partialPayout
                    });
                    hasPartial = true;
                } else {
                    nettingStopIdx = i;
                }
                break;
            }

            usdlBudget -= (vBuy + settlement);
            totalVBuy += vBuy;
            totalNettedSettlement += settlement;

            if (vBuy > 0) {
                uint256 pbBought = _getAmountOut(vBuy, vReserveUSDL, vReservePB);
                vReserveUSDL += vBuy;
                vReservePB -= pbBought;
            }

            address payoutAddr = _resolvePayoutAddress(id, pbt.payoutAddress);

            nettedResults[nettedCount] = NettingScanResult({
                pbtId: id,
                unlockPBc: tranche,
                fullTranche: tranche,
                triggerPrice: triggerPrice,
                unlockUSDL: settlement,
                payoutAddress: payoutAddr,
                isDust: isDust,
                isPartial: false
            });
            nettedCount++;
        }
    }

    /// Settle a single netted position: PBc → vault, USDL → holder, update pbtRegistry.
    /// Called during Phase A for each netted position.
    function _settleNettedPosition(NettingScanResult memory r) internal {
        PBtData storage pbt = pbtRegistry[r.pbtId];
        uint256 pbcFromHolder = r.unlockPBc;

        // PBc: holder → vault
        IERC20(PBC_TOKEN).transferFrom(pbt.holder, address(this), pbcFromHolder);
        _addVaultPBc(pbcFromHolder);

        // USDL: vault → payout address
        if (!IERC20(USDL_TOKEN).transfer(r.payoutAddress, r.unlockUSDL)) revert TransferFailed();

        // Update position state
        pbt.pbcLocked -= pbcFromHolder;
        totalUSDLDistributed += r.unlockUSDL;

        uint256 unlockIndex = pbt.nextUnlockIndex;

        // Record on PBt metadata
        if (r.isDust) {
            IPBtNFT(PBT_TOKEN).recordDust(r.pbtId, r.unlockPBc);
        } else {
            IPBtNFT(PBT_TOKEN).recordUnlock(r.pbtId, r.unlockPBc, r.triggerPrice);
        }

        // Update trigger + index (if not dust)
        if (!r.isDust) {
            pbt.nextUnlockIndex++;
            pbt.nextTriggerPrice = computeNextTriggerPrice(pbt.buyPrice, pbt.nextUnlockIndex);
        }

        // Burn PBt if dust cleared
        if (r.isDust || pbt.pbcLocked < DUST_CHECK_THRESHOLD) {
            _movePBtOwnership(r.pbtId, pbt.holder, address(0));
            pbt.holder = address(0);
            pbt.payoutAddress = address(0);
            IPBtNFT(PBT_TOKEN).burnTracker(r.pbtId);
            activePositionCount--;
            emit PBtBurned(r.pbtId, pbt.pbcLocked);
        }

        emit UnlockNetted(r.pbtId, unlockIndex, r.unlockPBc, r.unlockUSDL, r.payoutAddress, r.triggerPrice, pbt.nextTriggerPrice, pbt.pbcLocked);
    }

    function _beginPartialSettlement(NettingScanResult memory r) internal returns (PendingPartialSettlement memory pending) {
        PBtData storage pbt = pbtRegistry[r.pbtId];

        IERC20(PBC_TOKEN).transferFrom(pbt.holder, address(this), r.fullTranche);
        _addVaultPBc(r.fullTranche);

        if (!IERC20(USDL_TOKEN).transfer(r.payoutAddress, r.unlockUSDL)) revert TransferFailed();
        totalUSDLDistributed += r.unlockUSDL;

        pending = PendingPartialSettlement({
            pbtId: r.pbtId,
            fullTranche: r.fullTranche,
            buyerPBc: r.unlockPBc,
            holderUSDL: r.unlockUSDL,
            triggerPrice: r.triggerPrice,
            payoutAddress: r.payoutAddress,
            isDust: r.isDust,
            exists: true
        });
    }

    function _finalizePendingPartialSettlement(PendingPartialSettlement memory pending) internal {
        PBtData storage pbt = pbtRegistry[pending.pbtId];
        uint256 unlockIndex = pbt.nextUnlockIndex;

        pbt.pbcLocked -= pending.fullTranche;

        if (pending.isDust) {
            IPBtNFT(PBT_TOKEN).recordDust(pending.pbtId, pending.fullTranche);
        } else {
            IPBtNFT(PBT_TOKEN).recordUnlock(pending.pbtId, pending.fullTranche, pending.triggerPrice);
        }

        if (!pending.isDust) {
            pbt.nextUnlockIndex++;
            pbt.nextTriggerPrice = computeNextTriggerPrice(pbt.buyPrice, pbt.nextUnlockIndex);
        }

        if (pending.isDust || pbt.pbcLocked < DUST_CHECK_THRESHOLD) {
            _movePBtOwnership(pending.pbtId, pbt.holder, address(0));
            pbt.holder = address(0);
            pbt.payoutAddress = address(0);
            IPBtNFT(PBT_TOKEN).burnTracker(pending.pbtId);
            activePositionCount--;
            emit PBtBurned(pending.pbtId, pbt.pbcLocked);
        }

        emit UnlockNetted(
            pending.pbtId,
            unlockIndex,
            pending.fullTranche,
            pending.holderUSDL,
            pending.payoutAddress,
            pending.triggerPrice,
            pbt.nextTriggerPrice,
            pbt.pbcLocked
        );
    }

    /// Execute the final signed AMM settlement after internal netting is determined.
    function _executeNetAMMPhase(
        uint256 usdlAmount,
        uint256 totalVBuy,
        uint256 totalNettedSettlement,
        PartialTriggerInfo memory partialTrigger,
        bool hasPartial,
        uint256[] memory unlockIds,
        uint256 unlockCount,
        uint256 nettingStopIdx
    ) internal returns (uint256 internalPBc, uint256 additionalHolderUSDL, uint256 compensationPB, uint256 lpUSDLUsed, uint256 ammPB) {
        uint256 leftover = usdlAmount - totalNettedSettlement - totalVBuy;
        uint256 ammBuyAmount = totalVBuy + leftover;
        uint256 unsettledOwed = 0;
        uint256 unsettledPBc = 0;
        address partialHolderAddr = address(0);
        uint256 reservePB;
        uint256 reserveUSDL;

        if (hasPartial) {
            unsettledOwed = partialTrigger.unsettledOwed;
            unsettledPBc = partialTrigger.unsettledPBc;
            partialHolderAddr = partialTrigger.payoutAddress;
        }

        if (unsettledOwed > 0 && ammBuyAmount > 0) {
            uint256 internalUSDL = ammBuyAmount < unsettledOwed ? ammBuyAmount : unsettledOwed;
            internalPBc = (unsettledPBc * internalUSDL) / unsettledOwed;

            if (!IERC20(USDL_TOKEN).transfer(partialHolderAddr, internalUSDL)) revert TransferFailed();
            totalUSDLDistributed += internalUSDL;
            additionalHolderUSDL += internalUSDL;

            unsettledPBc -= internalPBc;
            unsettledOwed -= internalUSDL;
            ammBuyAmount -= internalUSDL;
        }

        if (unsettledOwed > 0) {
            if (unsettledPBc > 0) {
                uint256 usdlProceeds = _sellPBtoAMM(unsettledPBc);
                _deductVaultPB(unsettledPBc);

                if (!IERC20(USDL_TOKEN).transfer(partialHolderAddr, usdlProceeds)) revert TransferFailed();
                totalUSDLDistributed += usdlProceeds;
                additionalHolderUSDL += usdlProceeds;
            }
            return (internalPBc, additionalHolderUSDL, 0, 0, 0);
        }

        // LP contribution (distribution phase only)
        bool lpActive = isDistributionPhase && vaultPBBalance > totalOutstandingPBc();
        if (lpActive && ammBuyAmount > 0) {
            uint256 usdlForLP = (ammBuyAmount * LP_CONTRIBUTION_BPS) / 10000;
            if (usdlForLP > 0) {
                (reservePB, reserveUSDL) = _getPairReserves();
                (compensationPB, lpUSDLUsed) = _directLPContribution(usdlForLP, reservePB, reserveUSDL);
                ammBuyAmount -= lpUSDLUsed;
            }
        }

        if (ammBuyAmount > 0) {
            uint256 pbFromFinal = _executeBuyOnPulseX(ammBuyAmount, 1);
            _addVaultPB(pbFromFinal);
            ammPB += pbFromFinal;

            (reservePB, reserveUSDL) = _getPairReserves();
            for (uint256 i = nettingStopIdx; i < unlockCount; i++) {
                uint256 id = unlockIds[i];
                PBtData storage pbt = pbtRegistry[id];
                if (pbt.holder == address(0) || pbt.pbcLocked == 0) continue;

                uint256 currentPrice = (reserveUSDL * 1e18) / reservePB;
                if (currentPrice < pbt.nextTriggerPrice) break;

                _executeUnlock(id, true);
                (reservePB, reserveUSDL) = _getPairReserves();
            }
        }
    }

    /// Run the live netting preparation and both buy execution phases.
    function _executeBuyNettingPhases(uint256 usdlAmount, uint256[] calldata unlockIds)
        internal
        returns (BuyExecutionSummary memory summary)
    {
        (uint256 reservePB, uint256 reserveUSDL) = _getPairReserves();
        summary.preBuyPrice = (reserveUSDL * 1e18) / reservePB;

        (uint256[] memory sanitizedUnlockIds, uint256 liveUnlockCount) = _sanitizeUnlockHints(unlockIds);

        (
            NettingScanResult[] memory nettedResults,
            uint256 nettedCount,
            uint256 totalNettedSettlement,
            uint256 totalVBuy,
            uint256 nettingStopIdx,
            PartialTriggerInfo memory partialTrigger,
            bool hasPartial
        ) =
            _sequentialNettingScan(usdlAmount, reservePB, reserveUSDL, sanitizedUnlockIds, liveUnlockCount);

        PendingPartialSettlement memory pendingPartial;
        for (uint256 i = 0; i < nettedCount; i++) {
            if (nettedResults[i].isPartial) {
                pendingPartial = _beginPartialSettlement(nettedResults[i]);
                summary.nettedPBc += nettedResults[i].unlockPBc;
                continue;
            }

            _settleNettedPosition(nettedResults[i]);
            summary.nettedPBc += nettedResults[i].unlockPBc;
        }

        uint256 internalPBc;
        uint256 additionalHolderUSDL;
        (internalPBc, additionalHolderUSDL, summary.compensationPB, summary.lpUSDLUsed, summary.ammPB) =
            _executeNetAMMPhase(
                usdlAmount,
                totalVBuy,
                totalNettedSettlement,
                partialTrigger,
                hasPartial,
                sanitizedUnlockIds,
                liveUnlockCount,
                nettingStopIdx
            );

        if (pendingPartial.exists) {
            pendingPartial.buyerPBc += internalPBc;
            pendingPartial.holderUSDL += additionalHolderUSDL;
            _finalizePendingPartialSettlement(pendingPartial);
        }

        summary.nettedPBc += internalPBc;
        summary.nettedCount = nettedCount;
    }

    /// Direct LP contribution: pair buyer's USDL with vault's excess PB.
    /// No selling! Returns compensation PB amount owed to buyer.
    /// Vault needs 2× PB: one set for LP, one set as buyer compensation.
    function _directLPContribution(uint256 usdlForLP, uint256 reservePB, uint256 reserveUSDL)
        internal returns (uint256 pbCompensation, uint256 usdlUsed)
    {
        if (!isDistributionPhase) return (0, 0);

        uint256 outstandingPBc = totalOutstandingPBc();
        if (vaultPBBalance <= outstandingPBc) return (0, 0);

        uint256 availablePB = vaultPBBalance - outstandingPBc;

        // Calculate PB needed at current pool ratio
        uint256 pbForLP = (usdlForLP * reservePB) / reserveUSDL;

        // Need 2× PB: one set for LP, one set as buyer compensation
        uint256 totalPBNeeded = pbForLP * 2;

        // Cap to available excess
        if (totalPBNeeded > availablePB) {
            totalPBNeeded = availablePB;
            pbForLP = totalPBNeeded / 2;
            usdlForLP = (pbForLP * reserveUSDL) / reservePB;
        }

        if (pbForLP < 2 || usdlForLP < 2) return (0, 0);

        // Add liquidity: vault PB + buyer's USDL
        IERC20(PB_TOKEN).approve(PULSEX_ROUTER, pbForLP);
        IERC20(USDL_TOKEN).approve(PULSEX_ROUTER, usdlForLP);

        bool isFinalContribution = (totalPBNeeded >= availablePB);

        (uint256 pbUsed, uint256 usdlActual, uint256 liquidityMinted) = IPulseXRouter(PULSEX_ROUTER).addLiquidity(
            PB_TOKEN,
            USDL_TOKEN,
            pbForLP,
            usdlForLP,
            (pbForLP * 95) / 100,
            (usdlForLP * 95) / 100,
            address(this),
            block.timestamp + 3600
        );

        // Deduct PB used for LP from vault
        _deductVaultPB(pbUsed);
        vaultLPTokenBalance += liquidityMinted;

        // Buyer compensation = same PB amount as went to LP
        // (Not deducted here — included in buyer's totalPB, deducted during split/transfer)
        pbCompensation = pbUsed;
        usdlUsed = usdlActual;

        if (isFinalContribution) {
            emit FinalLPContribution(pbUsed, usdlActual);
        } else {
            emit LPContributed(pbUsed, usdlActual);
        }
    }

    // ========================================================================
    // MATH & PRICE HELPERS
    // ========================================================================

    /// Calculate next unlock trigger: buyPrice × (1.5555)^n geometric progression
    function computeNextTriggerPrice(uint256 buyPrice, uint256 unlockIndex)
        internal
        pure
        returns (uint256)
    {
        uint256 price = buyPrice;
        for (uint256 i = 0; i <= unlockIndex; i++) {
            // Divide first to prevent overflow with large prices/indices
            // Reorder: (a * b) / c becomes (a / c) * b to keep intermediate value smaller
            price = (price / UNLOCK_DIVISOR) * UNLOCK_MULTIPLIER;
        }
        return price;
    }

    /// Read pair reserves with correct token ordering
    function _getPairReserves() internal view returns (uint256 reservePB, uint256 reserveUSDL) {
        (uint112 r0, uint112 r1, ) = IUniswapV2Pair(PULSEX_PAIR).getReserves();
        address token0 = IUniswapV2Pair(PULSEX_PAIR).token0();
        if (token0 == PB_TOKEN) {
            reservePB = uint256(r0);
            reserveUSDL = uint256(r1);
        } else {
            reservePB = uint256(r1);
            reserveUSDL = uint256(r0);
        }
    }

    // ========================================================================
    // OUTSTANDING SUPPLY QUERIES
    // ========================================================================

    /// Total PBc held by users (21M - Vault balance)
    function totalOutstandingPBc() internal view returns (uint256) {
        return PBC_TOTAL_SUPPLY - vaultPBcBalance;
    }

    // ========================================================================
    // ACCESS CONTROL
    // ========================================================================

    modifier onlyLaunchConverter() {
        if (msg.sender != LAUNCH_CONVERTER) revert Unauthorized();
        _;
    }

    modifier onlyLPRemover() {
        if (msg.sender != LP_REMOVER) revert Unauthorized();
        _;
    }

    /// @notice Forward LP burn proceeds to user (called by PBRemoveUserLP satellite)
    function forwardLPTokensToUser(
        address recipient,
        uint256 amount0,
        uint256 amount1
    ) external onlyLPRemover returns (uint256 pbAmount, uint256 usdlAmount) {
        address token0 = IUniswapV2Pair(PULSEX_PAIR).token0();
        if (token0 == PB_TOKEN) {
            pbAmount = amount0;
            usdlAmount = amount1;
        } else {
            pbAmount = amount1;
            usdlAmount = amount0;
        }
        if (!IERC20(PB_TOKEN).transfer(recipient, pbAmount)) revert TransferFailed();
        if (!IERC20(USDL_TOKEN).transfer(recipient, usdlAmount)) revert TransferFailed();
    }



    // ========================================================================
    // VIEW FUNCTIONS (Query state without modifications)
    // ========================================================================

    // getPBtData, getUserPBtIds, getRecoveryData, getInheritanceData → VaultViews
    // (mappings are public, auto-getters available for field-level access)

    /// @notice Array length helper for VaultViews (public mapping auto-getter lacks length)
    function userPBtIdsCount(address user) external view returns (uint256) {
        return userPBtIds[user].length;
    }

    // ========================================================================
    // VLOCK: Voluntary PB locking for PBc + PBt + bonus from LP fees
    // ========================================================================

    // VLock parameters
    uint256 public constant MIN_VLOCK_BONUS_USDL = 100e18; // 100 USDL
    uint256 public constant VLOCK_BONUS_PCT = 5555;        // 5.555% when PCT_DENOM = 100000
    uint256 public constant PCT_DENOM = 100000;
    uint256 public constant MIN_TWAP_WINDOW = 300;         // seconds (informational market-reference window only)

    // Initial LP seeder: target start price = 0.05555 USDL per PB (scaled 1e18)
    // 0.05555 * 1e18 = 5.555e16 → represent as 5555 * 1e13 for integer math
    uint256 public constant INITIAL_TARGET_PRICE = 5555 * 1e13; // 0.05555 * 1e18
    bool public initialLPSeeded;
    event InitialLPSeeded(uint256 usdlAmount, uint256 pbAmount, uint256 liquidity);

    // Accumulated LP fees for VLock bonuses
    uint256 public accumulatedFeesUSDL;
    uint256 public accumulatedFeesPB;

    // K-tracking: baseline sqrt(reserve0 * reserve1) per LP token (scaled 1e18)
    // Used to isolate trading fees from LP principal. Only fee growth is extracted.
    uint256 public lastKPerLP;

    event LPRewardsHarvested(uint256 usdlAmount, uint256 pbAmount);
    event VLockExecuted(address indexed user, uint256 indexed pbtId, uint256 pbAmount, uint256 usdlBonusPaid, uint256 pbBonusPaid);
    event VLockBonusPaid(address indexed user, uint256 usdlAmount, uint256 pbAmount);

    /**
     * @notice Harvest accumulated LP trading fees (NOT principal withdrawal)
     * SECURITY: LP principal NEVER leaves. Only fee growth (K-tracking) is extracted.
     * Uses sqrt(reserve0 * reserve1) per LP token to detect fee accumulation.
     * Trading fees increase K; adding/removing LP proportionally does not change K per LP.
     * @return usdlHarvested Amount of USDL fees harvested
     * @return pbHarvested Amount of PB fees harvested
     */
    function _harvestLPRewards() internal returns (uint256 usdlHarvested, uint256 pbHarvested) {
        if (vaultLPTokenBalance == 0) return (0, 0);

        // Read current pair state
        (uint112 reserve0, uint112 reserve1,) = IUniswapV2Pair(PULSEX_PAIR).getReserves();
        uint256 totalLP = IUniswapV2Pair(PULSEX_PAIR).totalSupply();
        if (totalLP == 0) return (0, 0);

        // Calculate current K per LP token (scaled 1e18)
        uint256 currentK = _sqrt(uint256(reserve0) * uint256(reserve1));
        uint256 currentKPerLP = (currentK * 1e18) / totalLP;

        // First harvest: set baseline only, no fees to extract yet
        if (lastKPerLP == 0) {
            lastKPerLP = currentKPerLP;
            return (0, 0);
        }

        // No fee growth since last harvest (K per LP only grows from trading fees)
        if (currentKPerLP <= lastKPerLP) return (0, 0);

        // Fee fraction = 1 - (lastKPerLP / currentKPerLP)
        // This is the % of current LP value that is pure trading fees
        uint256 feeFraction = ((currentKPerLP - lastKPerLP) * 1e18) / currentKPerLP;
        uint256 lpToBurn = (vaultLPTokenBalance * feeFraction) / 1e18;
        if (lpToBurn == 0) return (0, 0);

        // Burn fee-only LP tokens (principal stays in pair)
        IUniswapV2Pair(PULSEX_PAIR).transfer(PULSEX_PAIR, lpToBurn);
        (uint256 amount0, uint256 amount1) = IUniswapV2Pair(PULSEX_PAIR).burn(address(this), address(this));

        // Map amounts to PB and USDL based on token ordering
        address token0 = IUniswapV2Pair(PULSEX_PAIR).token0();
        if (token0 == PB_TOKEN) {
            pbHarvested = amount0;
            usdlHarvested = amount1;
        } else {
            usdlHarvested = amount0;
            pbHarvested = amount1;
        }

        // Update vault accounting — LP tokens permanently removed (fee extraction, not principal)
        vaultLPTokenBalance -= lpToBurn;

        // Credit fees to accumulators (for VLock bonuses)
        accumulatedFeesPB += pbHarvested;
        accumulatedFeesUSDL += usdlHarvested;

        // Update baseline: K per LP is unchanged after proportional burn,
        // but re-read to account for any rounding
        (reserve0, reserve1,) = IUniswapV2Pair(PULSEX_PAIR).getReserves();
        totalLP = IUniswapV2Pair(PULSEX_PAIR).totalSupply();
        if (totalLP > 0) {
            lastKPerLP = (_sqrt(uint256(reserve0) * uint256(reserve1)) * 1e18) / totalLP;
        }

        emit LPRewardsHarvested(usdlHarvested, pbHarvested);
        return (usdlHarvested, pbHarvested);
    }

    /// Babylonian sqrt — O(log n) iterations, gas-efficient
    function _sqrt(uint256 x) internal pure returns (uint256) {
        if (x == 0) return 0;
        uint256 z = (x + 1) / 2;
        uint256 y = x;
        while (z < y) {
            y = z;
            z = (x / z + z) / 2;
        }
        return y;
    }

    /**
     * @notice Voluntarily lock PB held by caller and mint PBc + PBt.
        * If available, pay a USDL/PB bonus (capped by `accumulatedFees`).
     * @param pbAmount Amount of PB to lock (wei)
     * @return pbtId The minted PBt tracker ID
     */
    function voluntaryLock(uint256 pbAmount) external nonReentrant returns (uint256) {
        if (pbAmount == 0) revert InvalidAmount();
        // Snapshot price for PBt metadata and eligibility calculation later
        uint256 currentPrice = IPriceInterface(PRICE_FEED).getCurrentPrice();

        // Step 1: Pull PB from user into Vault (external interaction)
        if (!IERC20(PB_TOKEN).transferFrom(msg.sender, address(this), pbAmount)) revert TransferFailed();
        _addVaultPB(pbAmount);
        _transferPBcFromVault(msg.sender, pbAmount);
        uint256 pbtId = _mintPBt(msg.sender, pbAmount, pbAmount, currentPrice);
        isVLockPosition[pbtId] = true;
        (uint256 usdlBonusPaid, uint256 pbBonusPaid) = _claimLPFeesForInternal(pbtId);

        emit VLockExecuted(msg.sender, pbtId, pbAmount, usdlBonusPaid, pbBonusPaid);
        return pbtId;
    }

    /**
     * @notice Public function to harvest LP trading fees (for VLock bonuses)
     * Only extracts accumulated fees. LP principal is re-added immediately.
     */
    function harvestLPRewards() external nonReentrant {
        _harvestLPRewards();
    }

    // ========================================================================
    // VLOCK: CLAIM LP FEES (Harvest → credit Vault → pay pool-percent to PBt holder)
    // ========================================================================

    /**
     * @notice Claim LP fees on behalf of a specific PBt (VLock) position.
    * @dev This function requires that the caller is the PBt `holder` (newly minted PBt). Rewards are paid
    *      to the holder by default; recovery/inheritance flows are not considered during the VLock mint+claim path.
     *      It first harvests any LP rewards into `accumulatedFees*` (caller must have transferred tokens to Vault
     *      prior to calling so the harvest sees the deposits). After harvest, if the specified PBt meets
     *      the USDL-equivalence gate (`MIN_VLOCK_BONUS_USDL`) the function pays the pool-percent allocation
     *      (VLOCK_BONUS_PCT / PCT_DENOM) of each accumulator to the PBt's payout address. All flows are
     *      credited to Vault accumulators first, then paid out atomically to avoid mis-accounting.
     * @param pbtId PBt tracker ID to claim bonus for
     * @return usdlPaid Amount of USDL paid to the PBt payout address
     * @return pbPaid Amount of PB paid in-kind to the PBt payout address
     */
    function claimLPFeesFor(uint256 pbtId) external nonReentrant returns (uint256 usdlPaid, uint256 pbPaid) {
        PBtData storage pbt = pbtRegistry[pbtId];
        if (pbt.holder == address(0)) revert NotExist();
        if (!isVLockPosition[pbtId]) revert NotVLockPosition();
        if (vlockBonusClaimed[pbtId]) revert BonusAlreadyClaimed();

        // Only the PBt holder may call to claim for this PBt
        if (msg.sender != pbt.holder) revert Unauthorized();
        // Delegate to internal helper which harvests then pays if eligible
        return _claimLPFeesForInternal(pbtId);
    }

    /**
     * @notice Internal helper: harvest LP rewards into accumulators and pay pool-percent
     * for a specific PBt position. Used by `voluntaryLock` to ensure canonical ordering
     * (PB -> PBc -> mint PBt -> harvest -> pay). This function assumes the PBt exists
     * and will NOT perform external authorization checks.
     */
    function _claimLPFeesForInternal(uint256 pbtId) internal returns (uint256 usdlPaid, uint256 pbPaid) {
        PBtData storage pbt = pbtRegistry[pbtId];
        if (pbt.holder == address(0)) {
            return (0, 0);
        }
        if (!isVLockPosition[pbtId]) revert NotVLockPosition();
        if (vlockBonusClaimed[pbtId]) revert BonusAlreadyClaimed();

        // Mark this VLock as having used its LP incentive check.
        vlockBonusClaimed[pbtId] = true;

        /// Harvest LP rewards into Vault accumulators
        _harvestLPRewards();

        /// Re-evaluate eligibility using current price and PBc locked for this PBt
        uint256 currentPrice = IPriceInterface(PRICE_FEED).getCurrentPrice();
        uint256 usdlEq = (pbt.pbcLocked * currentPrice) / 1e18;
        if (usdlEq < MIN_VLOCK_BONUS_USDL) {
            return (0, 0);
        }

        // 3) Compute and pay allocations from accumulators (capped by accumulators)
        uint256 usdlAllocation = accumulatedFeesUSDL * VLOCK_BONUS_PCT / PCT_DENOM;
        if (usdlAllocation > 0) {
            accumulatedFeesUSDL = accumulatedFeesUSDL - usdlAllocation;
            if (!IERC20(USDL_TOKEN).transfer(pbt.payoutAddress, usdlAllocation)) revert TransferFailed();
            usdlPaid = usdlAllocation;
        }

        uint256 pbAllocation = accumulatedFeesPB * VLOCK_BONUS_PCT / PCT_DENOM;
        if (pbAllocation > 0) {
            if (vaultPBBalance < pbAllocation) revert InsufficientBalance();
            accumulatedFeesPB = accumulatedFeesPB - pbAllocation;
            vaultPBBalance = vaultPBBalance - pbAllocation;
            if (!IERC20(PB_TOKEN).transfer(pbt.payoutAddress, pbAllocation)) revert TransferFailed();
            pbPaid = pbAllocation;
        }

        if (usdlPaid > 0 || pbPaid > 0) {
            emit VLockBonusPaid(pbt.payoutAddress, usdlPaid, pbPaid);
        }

        return (usdlPaid, pbPaid);
    }

    /**
     * @notice Primary vault-centric buy with SEQUENTIAL NETTING
     * Two-phase execution:
     *   PHASE A (off-AMM): Settle netted triggers. Buyer USDL → holders, PBc → buyer.
     *   PHASE B (real AMM): LP contribution → interleaved buy/sell loop → final buy.
     *
     * @param usdlAmount Amount of USDL to spend
     * @param minPBOut Minimum total PB buyer expects (nettedPBc + compensationPB + ammPB)
     * @param recipient Address to receive PB, PBc and PBt (address(0) = msg.sender)
     * @param unlockIds Candidate PBt IDs from the off-chain indexer; sanitized live on-chain before execution
     * @return pbtId The minted PBt tracker ID
     */
    function buyPBDirect(
        uint256 usdlAmount,
        uint256 minPBOut,
        address recipient,
        uint256[] calldata unlockIds
    ) external nonReentrant returns (uint256 pbtId) {
        address buyer = msg.sender;
        if (recipient == address(0)) recipient = buyer;
        if (usdlAmount == 0) revert InvalidAmount();
        if (minPBOut == 0) revert InvalidAmount();
        if (buyer == address(0)) revert ZeroAddress();
        if (unlockIds.length > MAX_UNLOCK_PER_BUY) revert InvalidAmount();

        // ── Step 1: Receive USDL from buyer ──
        if (!IERC20(USDL_TOKEN).transferFrom(buyer, address(this), usdlAmount)) revert TransferFailed();

        // ── Step 2: Increment buy counter (harvest removed from buy path — use manual or VLock) ──
        ++buyCount;

        // ── Step 3-7: Snapshot pre-buy price, sanitize hints, settle netting, then run Phase B ──
        BuyExecutionSummary memory summary = _executeBuyNettingPhases(usdlAmount, unlockIds);

        // ── Step 8: Tally buyer's total PB ──
        uint256 totalBuyerPB = summary.nettedPBc + summary.compensationPB + summary.ammPB;
        if (totalBuyerPB < minPBOut) revert SlippageTooLow();

        // ── Step 9: Split 3.69% liquid / 96.31% locked, distribute ──
        (uint256 liquidPB, uint256 lockedPBc) = _computeSplit(totalBuyerPB);
        _transferPBFromVault(recipient, liquidPB);
        _transferPBcFromVault(recipient, lockedPBc);

        // M-1 fix: Use pre-buy price (snapshotted at Step 3) for fair trigger computation
        // Post-buy price is inflated by buyer's own AMM activity
        pbtId = _mintPBt(recipient, totalBuyerPB, lockedPBc, summary.preBuyPrice);

        // ── Step 10: Phase transition check ──
        _checkPhaseTransition();

        // ── Step 11: Emit netting event ──
        emit BuyWithNetting(
            buyer, recipient, pbtId, usdlAmount, totalBuyerPB,
            summary.nettedPBc, summary.ammPB, summary.compensationPB, summary.lpUSDLUsed, summary.nettedCount
        );

        return pbtId;
    }

    /**
     * @notice Execute swap on PulseX: USDL → PB
     * Vault is the principal (not callback), so no nesting conflicts
     * Frontend calculates minPBOut based on current price and user's slippage tolerance
     * 
     * @param usdlAmount USDL to swap
     * @param minPBOut Minimum PB to receive (frontend-controlled slippage)
     * @return pbReceived PB amount received
     */
    function _executeBuyOnPulseX(uint256 usdlAmount, uint256 minPBOut) internal returns (uint256 pbReceived) {
        if (usdlAmount == 0) revert InvalidAmount();
        if (minPBOut == 0) revert InvalidAmount();

        // Approve PulseX router
        IERC20(USDL_TOKEN).approve(PULSEX_ROUTER, usdlAmount);

        // Execute swap: USDL → PB (with frontend-controlled slippage via minPBOut)
        address[] memory path = new address[](2);
        path[0] = USDL_TOKEN;
        path[1] = PB_TOKEN;

        uint[] memory amounts = IPulseXRouter(PULSEX_ROUTER).swapExactTokensForTokens(
            usdlAmount,
            minPBOut,  // Frontend controls this via slippage tolerance
            path,
            address(this), // Vault receives PB
            block.timestamp + 3600
        );

        pbReceived = amounts[1]; // amounts[0] = USDL in, amounts[1] = PB out
        if (pbReceived == 0) revert InvalidAmount();
    }



    // getPBQuote, getPositionUnlockStatus, getUserTotalValue → VaultViews
}
        

/VaultViews.sol

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

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/**
 * @title VaultViews
 * @notice Read-only companion to PerpetualBitcoinVault.
 *         All functions here were relocated from Vault to reduce its deployed bytecode
 *         below PulseChain's 24,576-byte limit. No state is modified — pure reads only.
 *
 * Relocated functions:
 *   Category A (simple state wrappers):
 *     - getActivePositionCount()
 *     - getCurrentLPProceeds()
 *     - getVlockParameters()
 *     - getLPTokenBalance()
 *     - totalOutstandingPB()
 *     - totalOutstandingPBc()
 *
 *   Category B (struct/array getters):
 *     - getPBtData()
 *     - getUserPBtIds()
 *     - getRecoveryData()
 *     - getInheritanceData()
 *
 *   Category C (computed views):
 *     - computeNextTriggerPrice()
 *     - getPBQuote()
 *     - getPositionUnlockStatus()
 *     - getUserTotalValue()
 */

interface IPriceInterface {
    function getCurrentPrice() external view returns (uint256);
}

interface IVault {
    // --- Struct field accessors (auto-generated from public mappings) ---
    function pbtRegistry(uint256 pbtId) external view returns (
        uint256 buyPrice,
        uint256 pbAmount,
        uint256 pbcLocked,
        uint256 nextUnlockIndex,
        uint256 nextTriggerPrice,
        uint256 mintBlock,
        address holder,
        address payoutAddress
    );
    function recoveryRegistry(uint256 pbtId) external view returns (
        address recoveryAddress,
        bytes32 passwordHash,
        bool activated
    );
    function inheritanceRegistry(uint256 pbtId) external view returns (
        address inheritanceAddress,
        bytes32 passwordHash,
        bool activated
    );
    function userPBtIds(address user, uint256 index) external view returns (uint256);
    function userPBtIdsCount(address user) external view returns (uint256);

    // --- Scalar state reads ---
    function PB_TOKEN() external view returns (address);
    function PRICE_FEED() external view returns (address);
    function vaultPBBalance() external view returns (uint256);
    function vaultPBcBalance() external view returns (uint256);
    function vaultLPTokenBalance() external view returns (uint256);
    function activePositionCount() external view returns (uint256);
    function accumulatedFeesUSDL() external view returns (uint256);
    function accumulatedFeesPB() external view returns (uint256);
}

contract VaultViews {

    IVault public immutable vault;

    // Mirror Vault structs for return types
    struct PBtData {
        uint256 buyPrice;
        uint256 pbAmount;
        uint256 pbcLocked;
        uint256 nextUnlockIndex;
        uint256 nextTriggerPrice;
        uint256 mintBlock;
        address holder;
        address payoutAddress;
    }

    struct RecoveryData {
        address recoveryAddress;
        bytes32 passwordHash;
        bool activated;
    }

    struct InheritanceData {
        address inheritanceAddress;
        bytes32 passwordHash;
        bool activated;
    }

    // Vault constants (mirrored, never change)
    uint256 public constant PB_TOTAL_SUPPLY  = 21_000_000e18;
    uint256 public constant PBC_TOTAL_SUPPLY = 21_000_000e18;
    uint256 public constant UNLOCK_MULTIPLIER = 15555;
    uint256 public constant UNLOCK_DIVISOR = 10000;
    uint256 public constant MIN_VLOCK_BONUS_USDL = 100e18;
    uint256 public constant VLOCK_BONUS_PCT = 5555;
    uint256 public constant PCT_DENOM = 100000;
    uint256 public constant MIN_TWAP_WINDOW = 300;

    error InvalidAmount();
    error InvalidPrice();
    error NotExist();

    constructor(address _vault) {
        vault = IVault(_vault);
    }

    // ====================================================================
    // Category A: Simple state wrappers (were redundant with public vars)
    // ====================================================================

    /// @notice Active position count
    function getActivePositionCount() external view returns (uint256) {
        return vault.activePositionCount();
    }

    /// @notice Accumulated LP trading fees
    function getCurrentLPProceeds() external view returns (uint256 usdlProceeds, uint256 pbProceeds) {
        return (vault.accumulatedFeesUSDL(), vault.accumulatedFeesPB());
    }

    /// @notice VLock bonus parameters (constants)
    function getVlockParameters() external pure returns (
        uint256 minBonusUsdl, uint256 bonusPct, uint256 pctDenom, uint256 minTwapWindow
    ) {
        return (MIN_VLOCK_BONUS_USDL, VLOCK_BONUS_PCT, PCT_DENOM, MIN_TWAP_WINDOW);
    }

    /// @notice Vault LP token balance
    function getLPTokenBalance() external view returns (uint256) {
        return vault.vaultLPTokenBalance();
    }

    /// @notice Total PB held by users (21M - Vault balance)
    function totalOutstandingPB() external view returns (uint256) {
        return PB_TOTAL_SUPPLY - vault.vaultPBBalance();
    }

    /// @notice Total PBc held by users (21M - Vault balance)
    function totalOutstandingPBc() external view returns (uint256) {
        return PBC_TOTAL_SUPPLY - vault.vaultPBcBalance();
    }

    // ====================================================================
    // Category B: Struct & array getters
    // ====================================================================

    /// @notice Get full PBt data as struct
    function getPBtData(uint256 pbtId) external view returns (PBtData memory data) {
        (
            data.buyPrice,
            data.pbAmount,
            data.pbcLocked,
            data.nextUnlockIndex,
            data.nextTriggerPrice,
            data.mintBlock,
            data.holder,
            data.payoutAddress
        ) = vault.pbtRegistry(pbtId);
    }

    /// @notice Get all PBt IDs owned by user
    function getUserPBtIds(address user) external view returns (uint256[] memory) {
        uint256 len = vault.userPBtIdsCount(user);
        uint256[] memory ids = new uint256[](len);
        for (uint256 i = 0; i < len; i++) {
            ids[i] = vault.userPBtIds(user, i);
        }
        return ids;
    }

    /// @notice Get recovery data as struct
    function getRecoveryData(uint256 pbtId) external view returns (RecoveryData memory data) {
        (data.recoveryAddress, data.passwordHash, data.activated) = vault.recoveryRegistry(pbtId);
    }

    /// @notice Get inheritance data as struct
    function getInheritanceData(uint256 pbtId) external view returns (InheritanceData memory data) {
        (data.inheritanceAddress, data.passwordHash, data.activated) = vault.inheritanceRegistry(pbtId);
    }

    // ====================================================================
    // Category C: Computed view functions
    // ====================================================================

    /// @notice Calculate next unlock trigger: buyPrice × (1.5555)^(unlockIndex+1)
    function computeNextTriggerPrice(uint256 buyPrice, uint256 unlockIndex)
        external
        pure
        returns (uint256)
    {
        uint256 price = buyPrice;
        for (uint256 i = 0; i <= unlockIndex; i++) {
            price = (price / UNLOCK_DIVISOR) * UNLOCK_MULTIPLIER;
        }
        return price;
    }

    /// @notice Quote PB purchase: expected PB, liquid, and locked amounts
    function getPBQuote(uint256 usdlAmount) 
        external view returns (uint256 pbAmount, uint256 liquid, uint256 locked)
    {
        if (usdlAmount == 0) revert InvalidAmount();
        uint256 currentPrice = IPriceInterface(vault.PRICE_FEED()).getCurrentPrice();
        if (currentPrice == 0) revert InvalidPrice();
        
        pbAmount = (usdlAmount * 1e18) / currentPrice;
        liquid = (pbAmount * 369) / 10_000;
        locked = pbAmount - liquid;
    }

    /// @notice Get unlock readiness for a specific position
    function getPositionUnlockStatus(uint256 pbtId) 
        external view returns (
            uint256 currentUnlockIndex,
            uint256 nextTriggerPrice,
            bool isEligibleNow,
            uint256 pbcRemaining
        )
    {
        (
            ,               // buyPrice
            ,               // pbAmount
            uint256 pbcLocked,
            uint256 nextIdx,
            uint256 triggerPrice,
            ,               // mintBlock
            address holder,
            // payoutAddress
        ) = vault.pbtRegistry(pbtId);

        if (holder == address(0)) revert NotExist();
        
        uint256 currentPrice = IPriceInterface(vault.PRICE_FEED()).getCurrentPrice();
        
        return (
            nextIdx,
            triggerPrice,
            currentPrice >= triggerPrice,
            pbcLocked
        );
    }

    /// @notice Get user's total portfolio value
    function getUserTotalValue(address user) 
        external view returns (
            uint256 totalPBEquivalent,
            uint256 liquidPBHeld,
            uint256 totalPBcLocked,
            uint256 usdlEquivalent
        )
    {
        uint256 len = vault.userPBtIdsCount(user);
        totalPBcLocked = 0;
        
        for (uint256 i = 0; i < len; i++) {
            uint256 pbtId = vault.userPBtIds(user, i);
            (,,uint256 pbcLocked,,,,address holder,) = vault.pbtRegistry(pbtId);
            if (holder != address(0)) {
                totalPBcLocked += pbcLocked;
            }
        }
        
        liquidPBHeld = IERC20(vault.PB_TOKEN()).balanceOf(user);
        totalPBEquivalent = liquidPBHeld + totalPBcLocked;
        
        uint256 currentPrice = IPriceInterface(vault.PRICE_FEED()).getCurrentPrice();
        usdlEquivalent = (totalPBEquivalent * currentPrice) / 1e18;
    }
}
          

/V1MigrationIOU.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

/**
 * @title V1MigrationIOU
 * @notice Drop-in PresaleIOU replacement for V2 migration.
 *
 * Stores V1 position data so LaunchConverter's existing
 * executeConversionBatch() → Vault.allocatePresaleUser() flow
 * works unchanged. No actual presale — just a data source.
 *
 * Flow:
 *   1. Deploy V1MigrationIOU
 *   2. loadPositions() in batches (deployer loads V1 snapshot)
 *   3. finalizeLoad() — seals data
 *   4. Deploy LaunchConverter(V1MigrationIOU, Vault)
 *   5. setLaunchConverter(launchConverter)
 *   6. LaunchConverter.executeConversionBatch() reads from here
 */
contract V1MigrationIOU {

    // ========================================================================
    // TYPES (matches IPresaleIOU.Block in LaunchConverter.sol)
    // ========================================================================

    struct Block {
        address buyer;
        uint256 blockNum;        // V1 pbtId used as blockNum
        uint256 priceAtPurchase; // V1 buyPrice (entry price for unlocks)
        bool convertedAtLaunch;
    }

    // ========================================================================
    // STATE
    // ========================================================================

    address public immutable deployer;
    address public LAUNCH_CONVERTER;
    bool public saleClosed;

    /// Position data
    mapping(uint256 => Block) private blocks;
    mapping(uint256 => uint256) private pbAmounts; // blockNum → total pbAmount
    uint256[] private blockNums;                    // ordered list for getPresaleData

    bool public dataFinalized;
    uint256 public totalPositions;

    // ========================================================================
    // CONSTRUCTOR
    // ========================================================================

    constructor() {
        deployer = msg.sender;
        saleClosed = true; // no buying — migration only
    }

    // ========================================================================
    // DATA LOADING (deployer only, before finalization)
    // ========================================================================

    /**
     * @notice Load V1 positions in batches
     * @param holders   V1 holder addresses
     * @param _blockNums V1 pbtIds (used as blockNum keys)
     * @param buyPrices  V1 entry prices (pbtRegistry.buyPrice)
     * @param _pbAmounts V1 total PB allocations (pbtRegistry.pbAmount)
     */
    function loadPositions(
        address[] calldata holders,
        uint256[] calldata _blockNums,
        uint256[] calldata buyPrices,
        uint256[] calldata _pbAmounts
    ) external {
        require(msg.sender == deployer, "Only deployer");
        require(!dataFinalized, "Data finalized");
        require(holders.length == _blockNums.length, "length mismatch");
        require(holders.length == buyPrices.length, "length mismatch");
        require(holders.length == _pbAmounts.length, "length mismatch");

        for (uint256 i = 0; i < holders.length; i++) {
            require(holders[i] != address(0), "Zero buyer");
            require(buyPrices[i] > 0, "Zero price");
            require(_pbAmounts[i] > 0, "Zero amount");
            require(blocks[_blockNums[i]].buyer == address(0), "Duplicate blockNum");

            blocks[_blockNums[i]] = Block({
                buyer: holders[i],
                blockNum: _blockNums[i],
                priceAtPurchase: buyPrices[i],
                convertedAtLaunch: false
            });
            pbAmounts[_blockNums[i]] = _pbAmounts[i];
            blockNums.push(_blockNums[i]);
        }

        totalPositions += holders.length;
    }

    /**
     * @notice Seal data — no more loading after this
     */
    function finalizeLoad() external {
        require(msg.sender == deployer, "Only deployer");
        require(!dataFinalized, "Already finalized");
        require(totalPositions > 0, "No data loaded");
        dataFinalized = true;
    }

    /**
     * @notice Set LaunchConverter address (one-time, deployer only)
     */
    function setLaunchConverter(address _lc) external {
        require(msg.sender == deployer, "Only deployer");
        require(LAUNCH_CONVERTER == address(0), "Already set");
        require(_lc != address(0), "Zero address");
        LAUNCH_CONVERTER = _lc;
    }

    // ========================================================================
    // PRESALEIOU INTERFACE (what LaunchConverter calls)
    // ========================================================================

    /**
     * @notice Returns all position data as Block array
     * @dev LaunchConverter iterates this in batches via executeConversionBatch()
     */
    function getPresaleData() external view returns (Block[] memory) {
        Block[] memory allBlocks = new Block[](blockNums.length);
        for (uint256 i = 0; i < blockNums.length; i++) {
            allBlocks[i] = blocks[blockNums[i]];
        }
        return allBlocks;
    }

    /**
     * @notice Returns total PB allocation for a position
     * @dev Called by LaunchConverter._convertBlock() to get pbAmount
     *      Declared view (not pure) — storage lookup instead of formula
     */
    function getPBPerBlock(uint256 blockNum) external view returns (uint256) {
        return pbAmounts[blockNum];
    }

    /**
     * @notice Mark position as converted
     * @dev Called by LaunchConverter after allocatePresaleUser() succeeds
     */
    function markConverted(uint256 blockNum) external {
        require(msg.sender == LAUNCH_CONVERTER, "Only LaunchConverter");
        require(blocks[blockNum].buyer != address(0), "Unknown position");
        blocks[blockNum].convertedAtLaunch = true;
    }

    /**
     * @notice Stub for ownerOf — LaunchConverter interface includes it
     * @dev Returns buyer address for the position (not a real ERC721)
     */
    function ownerOf(uint256 tokenId) external view returns (address) {
        return blocks[tokenId].buyer;
    }

    // ========================================================================
    // VIEW HELPERS
    // ========================================================================

    function getPosition(uint256 blockNum) external view returns (
        address buyer,
        uint256 priceAtPurchase,
        uint256 pbAmount,
        bool converted
    ) {
        Block storage b = blocks[blockNum];
        return (b.buyer, b.priceAtPurchase, pbAmounts[blockNum], b.convertedAtLaunch);
    }

    function getBlockNumAt(uint256 index) external view returns (uint256) {
        return blockNums[index];
    }

    function getPositionCount() external view returns (uint256) {
        return blockNums.length;
    }
}
          

/PulseXInterface.sol

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

interface IUniswapV2Pair {
    function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
    function price0CumulativeLast() external view returns (uint256);
    function price1CumulativeLast() external view returns (uint256);
    function token0() external view returns (address);
    function token1() external view returns (address);
    function burn(address to) external returns (uint amount0, uint amount1);
    function mint(address to) external returns (uint liquidity);
    function totalSupply() external view returns (uint);
    function balanceOf(address owner) external view returns (uint);
    function transfer(address to, uint value) external returns (bool);
    function transferFrom(address from, address to, uint value) external returns (bool);
    function approve(address spender, uint value) external returns (bool);
}

/**
 * @title PulseXInterface
 * @notice Read-only market-price interface for the PB/USDL PulseX pair.
 * Uses current pair reserves to derive the live PB price in USDL terms.
 * Cumulative pair values are stored for observability, but active price reads
 * in this contract are spot-market based rather than enforced TWAP reads.
 * Immutable after setVault(). No upgrades.
 */
contract PulseXInterface {
    address public immutable DEPLOYER;
    address public immutable PB_USDL_PAIR;
    address public immutable PB_TOKEN;
    address public immutable USDL_TOKEN;
    address public VAULT;  // Set via setVault() one-time only
    bool public vaultLocked;  // Prevents VAULT from being changed after first set

    uint256 public constant PERIOD = 1 minutes; // Supported lookback window for bounded convenience reads
    uint256 public constant MIN_UPDATE_INTERVAL = 1 minutes; // Minimum time between cumulative-state refreshes
    uint256 public constant FIXED_PRICE = 0.0555e18; // Temporary price until LP seeded

    uint256 public price0CumulativeLast;
    uint256 public price1CumulativeLast;
    uint32 public blockTimestampLast;
    uint256 public lastUpdateTime;

    event PriceUpdated(uint256 price, uint256 timestamp);
    event VaultLocked(address indexed vault, uint256 timestamp);

    /**
     * VAULT can be address(0) initially - use setVault() to activate after Vault deployment.
     * @param pbUsdlPair The PulseX PB/USDL pair address.
     * @param pbToken PB token address.
     * @param usdlToken USDL token address.
     * @param vault Vault contract address (can be address(0), set later via setVault()).
     */
    constructor(address pbUsdlPair, address pbToken, address usdlToken, address vault) {
        require(pbUsdlPair != address(0), "Invalid pair address");
        require(pbToken != address(0), "Invalid PB address");
        require(usdlToken != address(0), "Invalid USDL address");
        // vault can be address(0) - will be set via setVault() after Vault deployment

        DEPLOYER = msg.sender;
        PB_USDL_PAIR = pbUsdlPair;
        PB_TOKEN = pbToken;
        USDL_TOKEN = usdlToken;
        VAULT = vault;
        
        // If vault provided in constructor, lock immediately
        if (vault != address(0)) {
            vaultLocked = true;
            emit VaultLocked(vault, block.timestamp);
        }
    }

    /**
     * @notice Set Vault address one-time only (breaks circular dependency)
     * @dev After calling this, VAULT becomes effectively immutable (vaultLocked = true forever)
     * @param _vault Vault contract address
     */
    function setVault(address _vault) external {
        require(msg.sender == DEPLOYER, "Only deployer");
        require(!vaultLocked, "Vault already locked");
        require(_vault != address(0), "Invalid vault address");
        require(VAULT == address(0), "Vault already set");
        
        VAULT = _vault;
        vaultLocked = true;
        
        emit VaultLocked(_vault, block.timestamp);
    }

    /**
     * @notice Return the current live PulseX price for PB in USDL terms.
     * @dev Derived from the pair's current reserves with token-order detection.
     */
    function getCurrentPrice() external view returns (uint256) {
        return _getSpotPrice();
    }

    /**
     * @dev Refresh stored cumulative pair values and emit the current live price.
     * This updates observability state only; protocol price reads remain based
     * on the current pool reserves.
     */
    function updatePrice() external {
        require(VAULT != address(0), "Vault not set - call setVault() first");
        require(block.timestamp >= lastUpdateTime + MIN_UPDATE_INTERVAL, "Update too frequent");
        _updateCumulativePrices();
    }

    /**
        * @notice Return the current live price when the requested timestamp is recent.
        * @dev This is not a true historical TWAP lookup. It is a bounded
        * convenience read that returns the current reserve-implied price if the
        * query falls within the supported lookback window.
     * @param timestamp The timestamp to query.
        * @return price The current live price for recent queries.
     */
    function getPriceHistory(uint256 timestamp) external view returns (uint256 price) {
        require(timestamp >= block.timestamp - PERIOD, "Historical price not available");
        return _getSpotPrice();
    }

    /**
     * @dev Internal: Update stored cumulative prices.
     */
    function _updateCumulativePrices() internal {
        (price0CumulativeLast, price1CumulativeLast, blockTimestampLast) = _getCurrentCumulatives();
        lastUpdateTime = block.timestamp;
        emit PriceUpdated(_getSpotPrice(), block.timestamp);
    }

    /**
        * @dev Internal: Read the pair's current cumulative-price fields.
     */
    function _getCurrentCumulatives() internal view returns (uint256, uint256, uint32) {
        (,, uint32 blockTimestamp) = IUniswapV2Pair(PB_USDL_PAIR).getReserves();
        return (
            IUniswapV2Pair(PB_USDL_PAIR).price0CumulativeLast(),
            IUniswapV2Pair(PB_USDL_PAIR).price1CumulativeLast(),
            blockTimestamp
        );
    }

    /**
        * @dev Internal: Derive the live PB/USDL market price from current pair reserves.
        * Dynamically determines token ordering and calculates USDL per PB.
     */
    function _getSpotPrice() internal view returns (uint256) {
        (uint112 reserve0, uint112 reserve1,) = IUniswapV2Pair(PB_USDL_PAIR).getReserves();
        
        // If pair is empty (no liquidity), return temporary fixed price
        if (reserve0 == 0 || reserve1 == 0) {
            return FIXED_PRICE;
        }
        
        address token0 = IUniswapV2Pair(PB_USDL_PAIR).token0();
        
        if (token0 == PB_TOKEN) {
            return (uint256(reserve1) * 1e18) / uint256(reserve0);
        } else {
            return (uint256(reserve0) * 1e18) / uint256(reserve1);
        }
    }
}
          

/PresaleIOU.sol

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

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IERC165 {
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

interface IERC721 is IERC165 {
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    function balanceOf(address owner) external view returns (uint256 balance);
    function ownerOf(uint256 tokenId) external view returns (address owner);
    function safeTransferFrom(address from, address to, uint256 tokenId) external;
    function transferFrom(address from, address to, uint256 tokenId) external;
    function approve(address to, uint256 tokenId) external;
    function getApproved(uint256 tokenId) external view returns (address operator);
    function setApprovalForAll(address operator, bool _approved) external;
    function isApprovedForAll(address owner, address operator) external view returns (bool);
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
}

interface IERC721Receiver {
    function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns (bytes4);
}

contract PresaleIOU is IERC721, ReentrancyGuard {

    uint256 public constant TOTAL_BLOCKS = 555;
    uint256 public constant FREE_BLOCKS = 102;
    uint256 public constant MAX_NFT_SUPPLY = 555;
    uint256 public constant PB_PER_BLOCK = 10_000e18;
    uint256 public constant MAX_BLOCKS_PER_ADDRESS = 5;
    // USDL_PER_BLOCK is the USDL price per presale block.
    // 555.5 USDL per block => 555.5 * 1e18
    uint256 public constant USDL_PER_BLOCK = 5555e17; // 555.5 * 1e18

    string public constant name = "Presale PB IOU";
    string public constant symbol = "PBIOU";
    string public constant IOU_IMAGE_URI = "https://ipfs.io/ipfs/bafybeiclvfm6pqd4rozhffhewfndebxv3s6f2z5t3xsmirchyda3lg7e54/PBiou.png";

    // USDL kept for compatibility but now used for USDL purchases
    address public immutable USDL;
    address payable public immutable PRESALE_TREASURY;
    address public LAUNCH_CONVERTER;

    struct Block {
        address buyer;
        uint256 blockNum;
        uint256 priceAtPurchase; // entry price for unlocks (USDL/PB equivalent)
        bool convertedAtLaunch;
    }

    mapping(uint256 => Block) private blocks;
    mapping(address => uint256) public blocksPurchased;
    uint256 public nextAvailableBlock;
    uint256 public totalRaised;

    // Founder tracking for higher limits
    mapping(address => bool) public isFounder;
    mapping(uint256 => bool) private _isFounderBlock; // block-level founder flag
    address[] private founderAddresses; // unique founder addresses for distribution

    // Distribution tracking for batched FOAD distribution
    uint256 public distributionNextBlock;   // next block to scan (0 = not started)
    uint256 public distributionFOADIndex;   // round-robin position across batches

    // ERC721 storage
    bool public saleClosed;

    mapping(uint256 => address) private _owners;
    mapping(address => uint256) private _balances;
    mapping(uint256 => address) private _tokenApprovals;
    mapping(address => mapping(address => bool)) private _operatorApprovals;

    constructor(
        address usdl,
        address presaleTreasury,
        address[] memory founderRecipients,
        uint256[] memory founderBlocks
    ) {
        require(presaleTreasury != address(0), "Treasury address required");
        require(founderRecipients.length == founderBlocks.length, "Founder arrays mismatch");

        USDL = usdl;
        PRESALE_TREASURY = payable(presaleTreasury);

        // No longer pre-initialize all blocks - they will be created lazily

        for (uint256 i = 0; i < founderRecipients.length; i++) {
            address recipient = founderRecipients[i];
            uint256 founderBlockNum = founderBlocks[i];

            require(recipient != address(0), "Founder recipient required");
            require(founderBlockNum >= 1 && founderBlockNum <= TOTAL_BLOCKS, "Invalid founder block");
            require(blocks[founderBlockNum].buyer == address(0), "Founder block already assigned");

            uint256 entryPrice = getEntryPriceAtBlock(founderBlockNum);
            blocks[founderBlockNum].buyer = recipient;
            blocks[founderBlockNum].blockNum = founderBlockNum;
            blocks[founderBlockNum].priceAtPurchase = entryPrice;
            blocks[founderBlockNum].convertedAtLaunch = false;

            blocksPurchased[recipient] += 1;
            _mint(recipient, founderBlockNum);
            if (!isFounder[recipient]) {
                isFounder[recipient] = true;
                founderAddresses.push(recipient);
            }
            _isFounderBlock[founderBlockNum] = true;
        }

        nextAvailableBlock = 1;
    }

    function setLaunchConverter(address launchConverter) external {
        require(LAUNCH_CONVERTER == address(0), "LaunchConverter already set");
        require(launchConverter != address(0), "Invalid LaunchConverter address");
        require(msg.sender == PRESALE_TREASURY, "Only treasury can set LaunchConverter");
        LAUNCH_CONVERTER = launchConverter;
    }

    function getEntryPriceAtBlock(uint256 blockNum) public pure returns (uint256) {
        require(blockNum >= 1 && blockNum <= TOTAL_BLOCKS, "Invalid block");
        uint256 minPrice = 0.0369e18;
        uint256 maxPrice = 0.05352e18;
        return minPrice + ((blockNum - 1) * (maxPrice - minPrice)) / 554;
    }

    function getPBPerBlock(uint256 blockNum) public pure returns (uint256) {
        uint256 entryPrice = getEntryPriceAtBlock(blockNum);
        return (USDL_PER_BLOCK * 1e18) / entryPrice;
    }

    function isFounderBlock(uint256 blockNum) public view returns (bool) {
        if (blockNum < 1 || blockNum > TOTAL_BLOCKS) return false;
        return _isFounderBlock[blockNum];
    }

    /// @notice Treasury-only: close the presale early so launch can proceed
    ///         even if not all 555 blocks are sold. Irreversible.
    function closeSale() external {
        require(msg.sender == PRESALE_TREASURY, "Only treasury");
        require(!saleClosed, "Already closed");
        saleClosed = true;
    }

    function buyBlock(uint256 numBlocks) external nonReentrant {
        require(!saleClosed, "Sale closed");
        require(numBlocks > 0, "numBlocks=0");
        require(blocksPurchased[msg.sender] + numBlocks <= MAX_BLOCKS_PER_ADDRESS, "Max blocks per address");

        uint256 totalCost = USDL_PER_BLOCK * numBlocks;

        // Transfer USDL from buyer to treasury
        IERC20(USDL).transferFrom(msg.sender, PRESALE_TREASURY, totalCost);

        uint256 blocksBought = 0;
        uint256 currentBlock = nextAvailableBlock;

        while (blocksBought < numBlocks && currentBlock <= TOTAL_BLOCKS) {
            if (!isFounderBlock(currentBlock) && blocks[currentBlock].buyer == address(0)) {
                uint256 entryPrice = getEntryPriceAtBlock(currentBlock);

                blocks[currentBlock].buyer = msg.sender;
                blocks[currentBlock].blockNum = currentBlock;
                blocks[currentBlock].priceAtPurchase = entryPrice;
                blocks[currentBlock].convertedAtLaunch = false;

                _mint(msg.sender, currentBlock);
                blocksBought++;
            }
            currentBlock++;
        }

        require(blocksBought == numBlocks, "Not enough available blocks");
        nextAvailableBlock = currentBlock;
        blocksPurchased[msg.sender] += numBlocks;
        totalRaised += totalCost;
    }

    /**
     * @notice Treasury-only: distribute any remaining unsold IOU blocks (IF ANY) to the FOAD
     * list in round-robin order, one block per recipient per cycle until exhausted.
     * Batched: pass batchSize > 0 to limit gas per tx. Call repeatedly until remaining == 0.
     * State is tracked on-chain (distributionNextBlock, distributionFOADIndex)
     * so consecutive calls pick up exactly where the previous batch stopped.
     * @param batchSize Max unsold blocks to distribute in this call (0 = all remaining).
     * @return distributed Number of blocks distributed in this batch.
     * @return remaining  Number of unsold blocks still left after this batch.
     */
    function distributeUnsoldToFOAD(uint256 batchSize) external returns (uint256 distributed, uint256 remaining) {
        require(msg.sender == PRESALE_TREASURY, "Only treasury");

        uint256 foadCount = founderAddresses.length;
        require(foadCount > 0, "No FOAD recipients");

        // First call: initialise scan cursor at block 1
        uint256 startBlock = distributionNextBlock;
        if (startBlock == 0) startBlock = 1;

        uint256 fIdx = distributionFOADIndex;
        distributed = 0;

        for (uint256 blk = startBlock; blk <= TOTAL_BLOCKS; blk++) {
            if (blocks[blk].buyer != address(0)) continue;

            // If batchSize > 0 and we've hit the limit, save cursor & return
            if (batchSize > 0 && distributed >= batchSize) {
                distributionNextBlock = blk;
                distributionFOADIndex = fIdx;
                // Count remaining unsold
                remaining = 0;
                for (uint256 r = blk; r <= TOTAL_BLOCKS; r++) {
                    if (blocks[r].buyer == address(0)) remaining++;
                }
                return (distributed, remaining);
            }

            address recipient = founderAddresses[fIdx % foadCount];

            uint256 entryPrice = getEntryPriceAtBlock(blk);
            blocks[blk].buyer = recipient;
            blocks[blk].blockNum = blk;
            blocks[blk].priceAtPurchase = entryPrice;
            blocks[blk].convertedAtLaunch = false;

            blocksPurchased[recipient] += 1;
            _mint(recipient, blk);

            fIdx++;
            distributed++;
        }

        // Finished all blocks — mark cursor past the end
        distributionNextBlock = TOTAL_BLOCKS + 1;
        distributionFOADIndex = fIdx;
        remaining = 0;
    }

    function markConverted(uint256 blockNum) external {
        require(msg.sender == LAUNCH_CONVERTER, "Only LaunchConverter");
        require(blockNum >= 1 && blockNum <= TOTAL_BLOCKS, "Invalid block");
        require(_owners[blockNum] != address(0), "Block IOU already burned or never existed");

        blocks[blockNum].convertedAtLaunch = true;
        _burn(blockNum);
    }

    function getPresaleData() external view returns (Block[] memory) {
        Block[] memory allBlocks = new Block[](TOTAL_BLOCKS);
        for (uint256 i = 1; i <= TOTAL_BLOCKS; i++) {
            allBlocks[i - 1] = blocks[i];
        }
        return allBlocks;
    }

    // --- ERC721 implementation (non-transferable overrides preserved) ---
    function supportsInterface(bytes4 interfaceId) external pure override returns (bool) {
        return interfaceId == type(IERC165).interfaceId || interfaceId == type(IERC721).interfaceId;
    }

    function balanceOf(address owner) external view override returns (uint256) {
        require(owner != address(0), "Zero address");
        return _balances[owner];
    }

    function ownerOf(uint256 tokenId) public view override returns (address) {
        address owner = _owners[tokenId];
        require(owner != address(0), "Nonexistent token");
        return owner;
    }

    function approve(address, uint256) external pure override {
        revert("PBIOU: non-transferable");
    }

    function getApproved(uint256 tokenId) external view override returns (address) {
        require(_owners[tokenId] != address(0), "Nonexistent token");
        return _tokenApprovals[tokenId];
    }

    function setApprovalForAll(address, bool) external pure override {
        revert("PBIOU: non-transferable");
    }

    function isApprovedForAll(address owner, address operator) public view override returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    function transferFrom(address, address, uint256) public pure override {
        revert("PBIOU: non-transferable");
    }

    function safeTransferFrom(address, address, uint256) external pure override {
        revert("PBIOU: non-transferable");
    }

    function safeTransferFrom(address, address, uint256, bytes memory) public pure override {
        revert("PBIOU: non-transferable");
    }

    function tokenURI(uint256 tokenId) external view returns (string memory) {
        require(_owners[tokenId] != address(0), "Nonexistent token");
        return string(
            abi.encodePacked(
                "data:application/json;utf8,{",
                "\"name\":\"PB Presale IOU\",",
                '"description":"Presale IOU for Perpetual Bitcoin (PB). Will be converted @ launch, Visit perpetualbitcoin.io",',
                "\"image\":\"", IOU_IMAGE_URI, "\"",
                "}"
            )
        );
    }

    function _mint(address to, uint256 tokenId) internal {
        require(to != address(0), "Zero address");
        require(_owners[tokenId] == address(0), "Already minted");
        _owners[tokenId] = to;
        _balances[to] += 1;
        emit Transfer(address(0), to, tokenId);
    }

    function _transfer(address from, address to, uint256 tokenId) internal {
        require(ownerOf(tokenId) == from, "Wrong owner");
        require(to != address(0), "Zero address");

        delete _tokenApprovals[tokenId];
        _owners[tokenId] = to;
        _balances[from] -= 1;
        _balances[to] += 1;

        emit Transfer(from, to, tokenId);
    }

    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
        address owner = ownerOf(tokenId);
        return (spender == owner || _tokenApprovals[tokenId] == spender || isApprovedForAll(owner, spender));
    }

    function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private returns (bool) {
        if (to.code.length == 0) {
            return true;
        }
        try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data) returns (bytes4 retval) {
            return retval == IERC721Receiver.onERC721Received.selector;
        } catch {
            return false;
        }
    }

    function _burn(uint256 tokenId) internal {
        address owner = ownerOf(tokenId);
        require(owner != address(0), "Nonexistent token");

        delete _tokenApprovals[tokenId];
        _balances[owner] -= 1;
        delete _owners[tokenId];

        emit Transfer(owner, address(0), tokenId);
    }
}
          

/PBt.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

/**
 * @title PBt.sol - Perpetual Bitcoin Tracker NFT (ERC721)
 * @author PB Team
 * @notice Non-transferable ERC721 that tracks each purchase as a unique NFT.
 * Stores purchase metadata: buy price, unlock history, recovery/inheritance data.
 * Only Vault can mint/burn. Users cannot trade PBt (non-transferable).
 * Burned when position fully redeemed (dust threshold reached).
 */

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

// ============================================================================
// INTERFACES
// ============================================================================

interface IVault {
    // Marker interface - just to verify Vault can call mint/burn
}

// ============================================================================
// PBt CONTRACT
// ============================================================================

/**
 * @title PBt - Perpetual Bitcoin Tracker NFT
 * @dev ERC721 non-transferable NFT. Each purchase = 1 PBt with unique ID.
 * Tracks buy price, unlock tranches, recovery/inheritance addresses, and dust.
 */
contract PBt is ERC721, ERC721Burnable, ReentrancyGuard {
    address public immutable DEPLOYER;

    // ========================================================================
    // IMMUTABLE STATE (after lockVault() called)
    // ========================================================================

    address public VAULT;           // Immutable after lockVault()
    bool public vaultLocked;        // Prevents re-locking

    // ========================================================================
    // DATA STRUCTURES
    // ========================================================================

    /**
     * @notice Complete purchase metadata stored in each PBt NFT
     */
    struct PBtMetadata {
        // Purchase info
        address owner;                          // Original buyer (immutable)
        uint256 buyPrice;                       // Entry price in USD cents (immutable)
        uint256 buyBlockNumber;                 // Block when purchase occurred (immutable)
        uint256 buyTimestamp;                   // Timestamp when purchased (immutable)

        // Allocation info
        uint256 allocatedPB;                    // Total PB allocated to this position
        uint256 allocatedPBc;                   // Total PBc allocated to this position
        uint256 paidoutPB;                      // PB already paid out via unlocks
        uint256 paidoutPBc;                     // PBc already redeemed

        // Unlock history
        uint256[] unlockedAmounts;              // Amount unlocked at each tranche
        uint256[] unlockBlockNumbers;           // Block number of each unlock
        uint256[] unlockTimestamps;             // Timestamp of each unlock
        uint256[] unlockPrices;                 // Price at time of unlock

        // Dust tracking
        uint256 dustRemaining;                  // Last bit of PBc that couldn't be swapped
        bool isDustBurned;                      // Flag: position fully liquidated

        // Recovery/Inheritance
        address recoveryAddress;                // Fallback recovery address
        address inheritanceAddress;             // Succession override address
        bool recoveryActivated;                 // One-time recovery flag
        bool inheritanceActivated;              // One-time inheritance flag
    }

    // ========================================================================
    // MUTABLE STATE
    // ========================================================================

    mapping(uint256 => PBtMetadata) public pbtMetadata;      // tokenId → metadata
    mapping(address => uint256[]) public userTokenIds;       // user → array of owned tokenIds

    // ========================================================================
    // EVENTS
    // ========================================================================

    event PBtMinted(
        uint256 indexed tokenId,
        address indexed owner,
        uint256 allocatedPB,
        uint256 allocatedPBc,
        uint256 buyPrice
    );

    event UnlockRecorded(
        uint256 indexed tokenId,
        uint256 indexed unlockIndex,
        uint256 unlockedAmount,
        uint256 unlockPrice
    );

    event DustRecorded(
        uint256 indexed tokenId,
        uint256 dustAmount
    );

    event PBtBurned(
        uint256 indexed tokenId,
        uint256 finalDust
    );

    event RecoveryAddressUpdated(
        uint256 indexed tokenId,
        address indexed recoveryAddress
    );

    event InheritanceAddressUpdated(
        uint256 indexed tokenId,
        address indexed inheritanceAddress
    );

    event RecoveryActivated(
        uint256 indexed tokenId,
        address indexed newOwner
    );

    event InheritanceActivated(
        uint256 indexed tokenId,
        address indexed newOwner
    );

    // ========================================================================
    // CONSTRUCTOR
    // ========================================================================

    /**
     * @notice Deploy PBt tracker NFT
     * VAULT will be set via lockVault() after deployment
     */
    constructor() ERC721("Perpetual Bitcoin Tracker", "PBt") {
        DEPLOYER = msg.sender;
        // VAULT set via lockVault()
    }

    /**
     * @notice Lock VAULT address permanently (one-time only)
     * @param _vault Vault contract address
     */
    function lockVault(address _vault) external {
        require(msg.sender == DEPLOYER, "Only deployer");
        require(!vaultLocked, "Vault already locked");
        require(VAULT == address(0), "Vault already set");
        require(_vault != address(0), "Invalid vault");
        
        VAULT = _vault;
        vaultLocked = true;
    }

    // ========================================================================
    // MINT FUNCTION (Vault-Only)
    // ========================================================================

    /**
     * @notice Mint new tracker NFT for a purchase
     * Only Vault can call this during presale allocation or regular buy
     *
     * @param to Recipient (buyer) address
     * @param tokenId Unique NFT ID (from Vault's counter)
     * @param buyPrice Entry price in USD cents (immutable)
     * @param allocatedPB Total PB for this purchase
     * @param allocatedPBc Total PBc for this purchase
     */
    function mint(
        address to,
        uint256 tokenId,
        uint256 buyPrice,
        uint256 allocatedPB,
        uint256 allocatedPBc
    ) external onlyVault nonReentrant {
        require(to != address(0), "Invalid recipient");
        require(buyPrice > 0, "Invalid buy price");
        require(allocatedPB > 0, "Invalid PB allocation");
        require(allocatedPBc > 0, "Invalid PBc allocation");

        // Mint the NFT
        _mint(to, tokenId);

        // Create metadata
        pbtMetadata[tokenId] = PBtMetadata({
            owner: to,
            buyPrice: buyPrice,
            buyBlockNumber: block.number,
            buyTimestamp: block.timestamp,
            allocatedPB: allocatedPB,
            allocatedPBc: allocatedPBc,
            paidoutPB: 0,
            paidoutPBc: 0,
            unlockedAmounts: new uint256[](0),
            unlockBlockNumbers: new uint256[](0),
            unlockTimestamps: new uint256[](0),
            unlockPrices: new uint256[](0),
            dustRemaining: 0,
            isDustBurned: false,
            recoveryAddress: address(0),
            inheritanceAddress: address(0),
            recoveryActivated: false,
            inheritanceActivated: false
        });

        // Track ownership
        userTokenIds[to].push(tokenId);

        emit PBtMinted(tokenId, to, allocatedPB, allocatedPBc, buyPrice);
    }

    // ========================================================================
    // UNLOCK FUNCTIONS (Vault-Only)
    // ========================================================================

    /**
     * @notice Record an unlock tranche
     * Called by Vault after successful unlock execution
     *
     * @param tokenId PBt ID
     * @param unlockedAmount PB amount unlocked
     * @param unlockPrice Price at unlock trigger
     */
    function recordUnlock(
        uint256 tokenId,
        uint256 unlockedAmount,
        uint256 unlockPrice
    ) external onlyVault nonReentrant {
        require(_exists(tokenId), "PBt does not exist");
        require(unlockedAmount > 0, "Invalid unlock amount");
        require(unlockPrice > 0, "Invalid unlock price");

        PBtMetadata storage meta = pbtMetadata[tokenId];

        // Verify we're not over-allocating
        uint256 newPaidout = meta.paidoutPB + unlockedAmount;
        require(newPaidout <= meta.allocatedPB, "Unlock exceeds allocation");

        // Record the unlock
        meta.paidoutPB = newPaidout;
        meta.unlockedAmounts.push(unlockedAmount);
        meta.unlockBlockNumbers.push(block.number);
        meta.unlockTimestamps.push(block.timestamp);
        meta.unlockPrices.push(unlockPrice);

        emit UnlockRecorded(tokenId, meta.unlockedAmounts.length - 1, unlockedAmount, unlockPrice);
    }

    /**
     * @notice Record dust (remainder PBc that couldn't be fully converted)
     * Called by Vault when position reaches dust threshold
     *
     * @param tokenId PBt ID
     * @param dustAmount Final dust amount in wei
     */
    function recordDust(uint256 tokenId, uint256 dustAmount) external onlyVault {
        require(_exists(tokenId), "PBt does not exist");

        PBtMetadata storage meta = pbtMetadata[tokenId];
        meta.dustRemaining = dustAmount;

        emit DustRecorded(tokenId, dustAmount);
    }

    // ========================================================================
    // BURN FUNCTION (Vault-Only)
    // ========================================================================

    /**
     * @notice Burn tracker NFT when position fully redeemed
     * Called by Vault when dust threshold reached and position closed
     *
     * @param tokenId PBt ID to burn
     */
    function burnTracker(uint256 tokenId) external onlyVault {
        require(_exists(tokenId), "PBt does not exist");

        PBtMetadata storage meta = pbtMetadata[tokenId];
        meta.isDustBurned = true;

        address owner = ownerOf(tokenId);
        _removeTokenFromUser(owner, tokenId);

        // Burn the NFT
        _burn(tokenId);

        emit PBtBurned(tokenId, meta.dustRemaining);
    }

    // ========================================================================
    // RECOVERY/INHERITANCE FUNCTIONS (Vault-Only)
    // ========================================================================

    /**
     * @notice Set recovery address for a position
     * Called by Vault after user password-protection setup
     *
     * @param tokenId PBt ID
     * @param recoveryAddress Fallback recipient
     */
    function setRecoveryAddress(uint256 tokenId, address recoveryAddress) external onlyVault {
        require(_exists(tokenId), "PBt does not exist");
        require(recoveryAddress != address(0), "Invalid recovery address");

        pbtMetadata[tokenId].recoveryAddress = recoveryAddress;

        emit RecoveryAddressUpdated(tokenId, recoveryAddress);
    }

    /**
     * @notice Set inheritance address for a position
     * Called by Vault after user password-protection setup
     *
     * @param tokenId PBt ID
     * @param inheritanceAddress Succession override
     */
    function setInheritanceAddress(uint256 tokenId, address inheritanceAddress) external onlyVault {
        require(_exists(tokenId), "PBt does not exist");
        require(inheritanceAddress != address(0), "Invalid inheritance address");

        pbtMetadata[tokenId].inheritanceAddress = inheritanceAddress;

        emit InheritanceAddressUpdated(tokenId, inheritanceAddress);
    }

    /**
     * @notice Activate recovery (transfer ownership to recovery address)
     * Called by User true Vault after password verification
     *
     * @param tokenId PBt ID
     */
    function activateRecovery(uint256 tokenId) external onlyVault nonReentrant {
        require(_exists(tokenId), "PBt does not exist");

        PBtMetadata storage meta = pbtMetadata[tokenId];
        require(meta.recoveryAddress != address(0), "No recovery address set");
        require(!meta.recoveryActivated, "Recovery already activated");

        address oldOwner = meta.owner;
        address newOwner = meta.recoveryAddress;

        // Update metadata (ownership + activation flag)
        meta.owner = newOwner;
        meta.recoveryActivated = true;

        // Update user token list
        _removeTokenFromUser(oldOwner, tokenId);

        // Burn old NFT (ERC721 ledger: oldOwner loses it)
        _burn(tokenId);

        // Remint NFT with same tokenId to new owner (ERC721 ledger: newOwner receives it)
        // All metadata is preserved in pbtMetadata[tokenId]
        _mint(newOwner, tokenId);
        userTokenIds[newOwner].push(tokenId);

        emit RecoveryActivated(tokenId, newOwner);
    }

    /**
     * @notice Activate inheritance (transfer ownership to inheritance address)
     * Called by Vault after password verification
     * Takes precedence over recovery
     *
     * @param tokenId PBt ID
     */
    function activateInheritance(uint256 tokenId) external onlyVault nonReentrant {
        require(_exists(tokenId), "PBt does not exist");

        PBtMetadata storage meta = pbtMetadata[tokenId];
        require(meta.inheritanceAddress != address(0), "No inheritance address set");
        require(!meta.inheritanceActivated, "Inheritance already activated");

        address oldOwner = meta.owner;
        address newOwner = meta.inheritanceAddress;

        // Update metadata (ownership + activation flag)
        meta.owner = newOwner;
        meta.inheritanceActivated = true;

        // Update user token list
        _removeTokenFromUser(oldOwner, tokenId);

        // Burn old NFT (ERC721 ledger: oldOwner loses it)
        _burn(tokenId);

        // Remint NFT with same tokenId to new owner (ERC721 ledger: newOwner receives it)
        // All metadata is preserved in pbtMetadata[tokenId]
        _mint(newOwner, tokenId);
        userTokenIds[newOwner].push(tokenId);

        emit InheritanceActivated(tokenId, newOwner);
    }

    // ========================================================================
    // VIEW FUNCTIONS
    // ========================================================================

    /**
     * @notice Get complete metadata for a PBt
     * @param tokenId PBt ID
     * @return Metadata struct
     */
    function getMetadata(uint256 tokenId) external view returns (PBtMetadata memory) {
        require(_exists(tokenId), "PBt does not exist");
        return pbtMetadata[tokenId];
    }

    /**
     * @notice Get all unlock history for a position
     * @param tokenId PBt ID
     * @return unlockedAmounts Array of unlock amounts
     * @return unlockBlockNumbers Array of unlock block numbers
     * @return unlockTimestamps Array of unlock timestamps
     * @return unlockPrices Array of unlock prices
     */
    function getUnlockHistory(uint256 tokenId)
        external
        view
        returns (
            uint256[] memory unlockedAmounts,
            uint256[] memory unlockBlockNumbers,
            uint256[] memory unlockTimestamps,
            uint256[] memory unlockPrices
        )
    {
        require(_exists(tokenId), "PBt does not exist");

        PBtMetadata memory meta = pbtMetadata[tokenId];
        return (
            meta.unlockedAmounts,
            meta.unlockBlockNumbers,
            meta.unlockTimestamps,
            meta.unlockPrices
        );
    }

    /**
     * @notice Get all PBt tokens owned by a user
     * @param user User address
     * @return Array of token IDs
     */
    function getUserTokenIds(address user) external view returns (uint256[] memory) {
        return userTokenIds[user];
    }

    /**
     * @notice Get number of PBt tokens owned by a user
     * @param user User address
     * @return Count
     */
    function getUserTokenCount(address user) external view returns (uint256) {
        return userTokenIds[user].length;
    }

    /**
     * @notice Check if a PBt exists
     * @param tokenId PBt ID
     * @return True if exists
     */
    function exists(uint256 tokenId) external view returns (bool) {
        return _exists(tokenId);
    }

    // ========================================================================
    // TRANSFER RESTRICTIONS (Non-Transferable)
    // ========================================================================

    /**
     * @notice Override burn to prevent direct user burning
     * Only Vault can burn PBt via burnTracker() function
     */
    function burn(uint256 tokenId) public override {
        revert("PBt: Use Vault for burning");
    }

    /**
     * @notice Override transfer to prevent user-to-user trades
     * PBt cannot be transferred between users (non-transferable)
     */
    // `transfer` is not part of ERC721 interface; prevent accidental usage by reverting

    /**
     * @notice Override transferFrom to prevent user-to-user trades
     * Only Vault can transfer PBt internally (via recovery/inheritance)
     */
    function transferFrom(address, address, uint256) public override {
        revert("PBt: Non-transferable");
    }

    /**
     * @notice Override safeTransferFrom to prevent user-to-user trades
     */
    function safeTransferFrom(address, address, uint256) public override {
        revert("PBt: Non-transferable");
    }

    /**
     * @notice Override safeTransferFrom to prevent user-to-user trades
     */
    function safeTransferFrom(address, address, uint256, bytes memory) public override {
        revert("PBt: Non-transferable");
    }

    /**
     * @notice Override approve to prevent approval attacks
     */
    function approve(address, uint256) public override {
        revert("PBt: Cannot approve");
    }

    /**
     * @notice Override setApprovalForAll to prevent approval attacks
     */
    function setApprovalForAll(address, bool) public override {
        revert("PBt: Cannot approve");
    }

    // ========================================================================
    // INTERNAL HELPER FUNCTIONS
    // ========================================================================

    /**
     * @notice Remove token from user's list (helper for recovery/inheritance)
     * @param user User address
     * @param tokenId Token to remove
     */
    function _removeTokenFromUser(address user, uint256 tokenId) internal {
        uint256[] storage tokens = userTokenIds[user];
        for (uint256 i = 0; i < tokens.length; i++) {
            if (tokens[i] == tokenId) {
                // Swap with last element and pop
                tokens[i] = tokens[tokens.length - 1];
                tokens.pop();
                return;
            }
        }
    }

    /**
     * @notice Check if token exists (internal helper)
     * @param tokenId Token ID
     * @return True if exists
     */
    function _exists(uint256 tokenId) internal view override returns (bool) {
        return super._exists(tokenId);
    }

    // ========================================================================
    // ACCESS CONTROL
    // ========================================================================

    modifier onlyVault() {
        require(msg.sender == VAULT, "Only Vault can call");
        _;
    }

    // ========================================================================
    // METADATA OVERRIDES
    // ========================================================================

    /**
     * @notice Return tokenURI with JSON metadata and image
     */
    function tokenURI(uint256 tokenId) public view override returns (string memory) {
        require(_exists(tokenId), "PBt does not exist");
        
        return string(
            abi.encodePacked(
                "data:application/json;utf8,{",
                '"name":"PB Tracker #', _toString(tokenId), '",',
                '"description":"Perpetual Bitcoin Position Tracker NFT. Visit perpetualbitcoin.io for details.",',
                '"image":"https://azure-rational-salmon-152.mypinata.cloud/ipfs/bafybeiclvfm6pqd4rozhffhewfndebxv3s6f2z5t3xsmirchyda3lg7e54/PBt.png"',
                "}"
            )
        );
    }
    
    function _toString(uint256 value) internal pure returns (string memory) {
        if (value == 0) return "0";
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }
}
          

/PBSwitchZapper.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/Pausable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

interface IPBVaultBuyDirect {
    function buyPBDirect(
        uint256 usdlAmount,
        uint256 minPBOut,
        address recipient,
        uint256[] calldata unlockIds
    ) external returns (uint256);
}

interface ISwitchRouter {
    struct AdapterRoute {
        address adapter;
        uint256 amountIn;
        uint24 fee;
    }

    struct SwapHop {
        address tokenIn;
        address tokenOut;
        AdapterRoute[] legs;
    }

    struct SwitchRoute {
        uint256 amountIn;
        SwapHop[] hops;
    }

    function goSwitch(
        SwitchRoute[] calldata routes,
        address to,
        uint256 minTotalAmountOut,
        uint256 fee,
        bool feeOnOutput,
        bool unwrapOutput,
        address partnerAddress
    ) external payable;
}

contract PBSwitchZapper is Ownable, Pausable, ReentrancyGuard {
    using SafeERC20 for IERC20;

    address public constant NATIVE_PLS = address(0);
    address public constant NATIVE_PLACEHOLDER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
    uint256 public constant MAX_SWITCH_FEE_BPS = 100;

    address public immutable SWITCH_ROUTER;
    address public immutable VAULT;
    address public immutable USDL;
    address public immutable SWITCH_PARTNER_ADDRESS;

    mapping(address => bool) public supportedInputTokens;

    error ZeroAddress();
    error InvalidAmount();
    error InvalidRecipient();
    error QuoteExpired();
    error UnsupportedInputToken(address token);
    error UnexpectedMsgValue();
    error NativeValueMismatch();
    error InvalidSwitchFee(uint256 feeBps);
    error InvalidRoutes();
    error InvalidRouteAmount();
    error InvalidRouteInput(address expected, address actual);
    error InvalidRouteOutput(address actual);
    error InvalidRoutePath();
    error InsufficientUSDLReceived(uint256 received, uint256 requiredMin);
    error NativeRefundFailed();

    event SupportedInputTokenSet(address indexed token, bool isSupported);
    event ZappedIntoPB(
        address indexed caller,
        address indexed inputToken,
        address indexed recipient,
        uint256 inputAmount,
        uint256 usdlReceived,
        uint256 pbtId,
        bool usedSwitch
    );

    constructor(
        address switchRouter,
        address vault,
        address usdl,
        address switchPartnerAddress,
        address[] memory initialSupportedTokens
    ) {
        if (switchRouter == address(0) || vault == address(0) || usdl == address(0)) {
            revert ZeroAddress();
        }

        SWITCH_ROUTER = switchRouter;
        VAULT = vault;
        USDL = usdl;
        SWITCH_PARTNER_ADDRESS = switchPartnerAddress;

        for (uint256 i = 0; i < initialSupportedTokens.length; i++) {
            supportedInputTokens[initialSupportedTokens[i]] = true;
            emit SupportedInputTokenSet(initialSupportedTokens[i], true);
        }
    }

    receive() external payable {
        if (msg.sender != SWITCH_ROUTER) {
            revert UnexpectedMsgValue();
        }
    }

    function setSupportedInputToken(address token, bool isSupported) external onlyOwner {
        supportedInputTokens[token] = isSupported;
        emit SupportedInputTokenSet(token, isSupported);
    }

    function pause() external onlyOwner {
        _pause();
    }

    function unpause() external onlyOwner {
        _unpause();
    }

    function zapIntoPB(
        address inputToken,
        uint256 amountIn,
        ISwitchRouter.SwitchRoute[] calldata routes,
        uint256 minUSDLFromSwitch,
        uint256 switchFeeBps,
        bool feeOnOutput,
        uint256 minPBOut,
        address recipient,
        uint256[] calldata unlockIds,
        uint256 deadline
    ) external payable whenNotPaused nonReentrant returns (uint256 pbtId) {
        if (amountIn == 0) revert InvalidAmount();
        if (recipient == address(0)) revert InvalidRecipient();
        if (block.timestamp > deadline) revert QuoteExpired();
        if (switchFeeBps > MAX_SWITCH_FEE_BPS) revert InvalidSwitchFee(switchFeeBps);

        uint256 usdlBalanceBefore = IERC20(USDL).balanceOf(address(this));
        uint256 nativeBalanceBefore = address(this).balance - msg.value;
        bool usedSwitch = !(inputToken == USDL && routes.length == 0);

        if (!usedSwitch) {
            if (msg.value != 0) revert UnexpectedMsgValue();
            IERC20(USDL).safeTransferFrom(msg.sender, address(this), amountIn);
        } else {
            if (!supportedInputTokens[inputToken]) {
                revert UnsupportedInputToken(inputToken);
            }

            _validateRoutes(inputToken, amountIn, routes);

            if (inputToken == NATIVE_PLS) {
                if (msg.value != amountIn) revert NativeValueMismatch();

                ISwitchRouter(SWITCH_ROUTER).goSwitch{ value: amountIn }(
                    routes,
                    address(this),
                    minUSDLFromSwitch,
                    switchFeeBps,
                    feeOnOutput,
                    false,
                    SWITCH_PARTNER_ADDRESS
                );

                uint256 nativeLeftover = address(this).balance - nativeBalanceBefore;
                if (nativeLeftover > 0) {
                    _refundNative(msg.sender, nativeLeftover);
                }
            } else {
                uint256 inputBalanceBefore = IERC20(inputToken).balanceOf(address(this));

                IERC20(inputToken).safeTransferFrom(msg.sender, address(this), amountIn);
                _forceApprove(IERC20(inputToken), SWITCH_ROUTER, amountIn);

                ISwitchRouter(SWITCH_ROUTER).goSwitch(
                    routes,
                    address(this),
                    minUSDLFromSwitch,
                    switchFeeBps,
                    feeOnOutput,
                    false,
                    SWITCH_PARTNER_ADDRESS
                );

                _forceApprove(IERC20(inputToken), SWITCH_ROUTER, 0);

                uint256 leftoverInput = IERC20(inputToken).balanceOf(address(this)) - inputBalanceBefore;
                if (leftoverInput > 0) {
                    IERC20(inputToken).safeTransfer(msg.sender, leftoverInput);
                }
            }
        }

        uint256 usdlReceived = IERC20(USDL).balanceOf(address(this)) - usdlBalanceBefore;
        if (usdlReceived < minUSDLFromSwitch) {
            revert InsufficientUSDLReceived(usdlReceived, minUSDLFromSwitch);
        }

        _forceApprove(IERC20(USDL), VAULT, usdlReceived);
        pbtId = IPBVaultBuyDirect(VAULT).buyPBDirect(usdlReceived, minPBOut, recipient, unlockIds);
        _forceApprove(IERC20(USDL), VAULT, 0);

        emit ZappedIntoPB(msg.sender, inputToken, recipient, amountIn, usdlReceived, pbtId, usedSwitch);
    }

    function _validateRoutes(
        address inputToken,
        uint256 amountIn,
        ISwitchRouter.SwitchRoute[] calldata routes
    ) internal view {
        if (routes.length == 0) revert InvalidRoutes();

        uint256 totalRouteAmount;

        for (uint256 i = 0; i < routes.length; i++) {
            ISwitchRouter.SwitchRoute calldata route = routes[i];
            if (route.amountIn == 0) revert InvalidRouteAmount();
            if (route.hops.length == 0) revert InvalidRoutes();

            totalRouteAmount += route.amountIn;

            ISwitchRouter.SwapHop calldata firstHop = route.hops[0];
            if (!_isValidInputToken(inputToken, firstHop.tokenIn)) {
                revert InvalidRouteInput(inputToken, firstHop.tokenIn);
            }

            for (uint256 j = 0; j < route.hops.length; j++) {
                ISwitchRouter.SwapHop calldata hop = route.hops[j];
                if (hop.legs.length == 0) revert InvalidRoutes();

                if (j > 0 && route.hops[j - 1].tokenOut != hop.tokenIn) {
                    revert InvalidRoutePath();
                }
            }

            if (route.hops[route.hops.length - 1].tokenOut != USDL) {
                revert InvalidRouteOutput(route.hops[route.hops.length - 1].tokenOut);
            }
        }

        if (totalRouteAmount != amountIn) {
            revert InvalidRouteAmount();
        }
    }

    function _isValidInputToken(address expectedInput, address actualInput) internal pure returns (bool) {
        if (expectedInput == NATIVE_PLS) {
            return actualInput == NATIVE_PLS || actualInput == NATIVE_PLACEHOLDER;
        }
        return actualInput == expectedInput;
    }

    function _forceApprove(IERC20 token, address spender, uint256 amount) internal {
        token.safeApprove(spender, 0);
        if (amount > 0) {
            token.safeApprove(spender, amount);
        }
    }

    function _refundNative(address to, uint256 amount) internal {
        (bool ok, ) = payable(to).call{ value: amount }("");
        if (!ok) revert NativeRefundFailed();
    }
}
          

/PBRemoveUserLP.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

/**
 * @title PBRemoveUserLP — LP Removal Middleware
 * @notice Allows users to remove their LP tokens from the PB-USDL PulseX pair.
 *         Required because PB.sol blocks direct pair→user PB transfers.
 *         Flow: user LP → pair burn → pair sends PB+USDL to Vault → Vault forwards to user.
 * 
 * IMMUTABLE: No owner, no admin, no upgrades, no backdoors, no selfdestruct.
 * All contract references set in constructor and cannot be changed.
 * Vault trusts this contract via LP_REMOVER (locked in lockImmutableReferences).
 */

// By 0xBE369

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

interface IUniswapV2Pair {
    function token0() external view returns (address);
    function burn(address to, address senderOrigin) external returns (uint256 amount0, uint256 amount1);
}

interface IVault {
    function PULSEX_PAIR() external view returns (address);
    function forwardLPTokensToUser(
        address recipient,
        uint256 amount0,
        uint256 amount1
    ) external returns (uint256 pbAmount, uint256 usdlAmount);
}

contract PBRemoveUserLP is ReentrancyGuard {

    address public immutable VAULT;

    event UserLPRemoved(
        address indexed user,
        uint256 lpAmount,
        uint256 pbAmount,
        uint256 usdlAmount
    );

    error ZeroAddress();
    error InvalidAmount();
    error Expired();
    error SlippageTooHigh();

    constructor(address _vault) {
        if (_vault == address(0)) revert ZeroAddress();
        VAULT = _vault;
    }

    /**
     * @notice Remove user's LP tokens from PB-USDL pair on PulseX
     * @dev Required because PB.sol blocks direct pair→user transfers.
     *      Vault acts as middleware: pair→Vault→user is the allowed path.
     *      This function ONLY handles USER-OWNED LP, not Vault's LP.
     * 
     * @param lpAmount Amount of LP tokens to remove
     * @param minPB Minimum PB to receive (slippage protection)
     * @param minUSDL Minimum USDL to receive (slippage protection)
     * @param deadline Transaction deadline timestamp
     * @return pbAmount Amount of PB received
     * @return usdlAmount Amount of USDL received
     */
    function removeUserPBLP(
        uint256 lpAmount,
        uint256 minPB,
        uint256 minUSDL,
        uint256 deadline
    ) external nonReentrant returns (uint256 pbAmount, uint256 usdlAmount) {
        if (block.timestamp > deadline) revert Expired();
        if (lpAmount == 0) revert InvalidAmount();

        address pair = IVault(VAULT).PULSEX_PAIR();

        // 1. Pull LP tokens from user and send directly to pair
        if (!IERC20(pair).transferFrom(msg.sender, pair, lpAmount)) {
            revert("LP transfer failed");
        }

        // 2. Burn LP — pair sends PB + USDL to Vault (allowed by PB.sol)
        (uint256 amount0, uint256 amount1) = IUniswapV2Pair(pair).burn(VAULT, msg.sender);

        // 3. Vault forwards tokens to user
        (pbAmount, usdlAmount) = IVault(VAULT).forwardLPTokensToUser(
            msg.sender,
            amount0,
            amount1
        );

        // 4. Slippage protection
        if (pbAmount < minPB) revert SlippageTooHigh();
        if (usdlAmount < minUSDL) revert SlippageTooHigh();

        emit UserLPRemoved(msg.sender, lpAmount, pbAmount, usdlAmount);
    }
}
          

/PBr.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

/**
 * @title PBr.sol - Recovery Badge (ERC-1155)
 * @author PB Team
 * @notice Non-transferable ERC-1155 badge for recovery address activation.
 *
 * Recovery Mechanism:
 * 1. PBt holder calls Vault.setRecoveryAddress(pbtId, recoveryAddr, passwordHash)
 * 2. Vault mints 1 PBr badge to recoveryAddr (identifies recovery authority)
 * 3. Recovery address holder calls Vault.activateRecovery(pbtId, password)
 * 4. Vault verifies password matches hash
 * 5. Vault burns PBr badge (one-time use)
 * 6. Payouts redirect to recovery address
 *
 * Non-Transferability:
 * - PBr cannot be transferred between users (transfer attempts revert)
 * - Only Vault can mint/burn
 * - Protocol flow treats each badge as one-time use
 *
 * IMMUTABLE. NO UPGRADES.
 */

// By 0xBE369

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract PBr is ERC1155, ReentrancyGuard {
    address public immutable DEPLOYER;
    // ========================================================================
    // IMMUTABLE STATE (after lockVault() called)
    // ========================================================================

    address public VAULT;           // Immutable after lockVault() - Only Vault can mint/burn
    bool public vaultLocked;        // Prevents re-locking
    
    // ========================================================================
    // BADGE TRACKING (for efficient lookup)
    // ========================================================================
    
    mapping(address => uint256[]) public userBadgeIds;  // Track all badge IDs per user
    mapping(uint256 => string) public badgeMessage;     // Optional message per badge (e.g. "Happy Birthday")

    // ========================================================================
    // EVENTS
    // ========================================================================

    event BadgeMinted(address indexed recipient, uint256 indexed pbtId);
    event BadgeBurned(address indexed holder, uint256 indexed pbtId);

    // ========================================================================
    // CONSTRUCTOR
    // ========================================================================

    /**
     * @notice Deploy recovery badge contract
     * VAULT will be set via lockVault() after deployment
     */
    constructor() ERC1155("https://azure-rational-salmon-152.mypinata.cloud/ipfs/bafybeiclvfm6pqd4rozhffhewfndebxv3s6f2z5t3xsmirchyda3lg7e54/PBr.png") {
        DEPLOYER = msg.sender;
        // VAULT set via lockVault()
    }

    /**
     * @notice Lock VAULT address permanently (one-time only)
     * @param _vault Vault contract address
     */
    function lockVault(address _vault) external {
        require(msg.sender == DEPLOYER, "Only deployer");
        require(!vaultLocked, "Vault already locked");
        require(VAULT == address(0), "Vault already set");
        require(_vault != address(0), "Invalid vault");
        
        VAULT = _vault;
        vaultLocked = true;
    }

    // ========================================================================
    // MINTING FUNCTION (Vault-Only)
    // ========================================================================

    /**
     * @notice Mint recovery badge to designated recovery address
     * Called by Vault when recovery address is set
     *
     * @param recipient Recovery address (designated by PBt holder)
     * @param pbtId Tracker NFT ID this badge is associated with
     */
    function mint(address recipient, uint256 pbtId, string calldata message) external nonReentrant {
        require(msg.sender == VAULT, "Only Vault can mint");
        require(recipient != address(0), "Invalid recipient");
        require(bytes(message).length <= 280, "Message too long");
        require(_isValidMessage(message), "Message: letters numbers spaces dash comma period only");

        // Mint 1 badge (amount = 1, since one-time use)
        // pbtId is the tokenId - uniquely identifies this recovery badge
        _mint(recipient, pbtId, 1, "");
        
        // Store optional message (empty string = no message)
        if (bytes(message).length > 0) {
            badgeMessage[pbtId] = message;
        }

        // Track badge ID for this user
        userBadgeIds[recipient].push(pbtId);

        emit BadgeMinted(recipient, pbtId);
    }

    // ========================================================================
    // BURNING FUNCTION (Vault-Only)
    // ========================================================================

    /**
     * @notice Burn recovery badge when recovery is activated
     * Called by Vault when recovery address activates recovery
     * This is one-time use - once burned, recovery cannot be re-activated
     *
     * @param holder Recovery address holder
     * @param pbtId Tracker NFT ID
     */
    function burn(address holder, uint256 pbtId) external nonReentrant {
        require(msg.sender == VAULT, "Only Vault can burn");
        require(balanceOf(holder, pbtId) >= 1, "Insufficient badge balance");

        _burn(holder, pbtId, 1);
        
        // Remove badge ID from tracking
        uint256[] storage badges = userBadgeIds[holder];
        for (uint256 i = 0; i < badges.length; i++) {
            if (badges[i] == pbtId) {
                badges[i] = badges[badges.length - 1];
                badges.pop();
                break;
            }
        }

        emit BadgeBurned(holder, pbtId);
    }

    // ========================================================================
    // NON-TRANSFERABILITY ENFORCEMENT
    // ========================================================================

    /**
     * @notice Reverts on transfer attempts (non-transferable)
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) public override {
        revert("PBr: Non-transferable");
    }

    /**
     * @notice Reverts on batch transfer attempts (non-transferable)
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) public override {
        revert("PBr: Non-transferable");
    }

    // ========================================================================
    // METADATA FUNCTIONS
    // ========================================================================

    /**
     * @notice Return URI for badge metadata
     * @param tokenId PBt ID associated with this badge
     * @return URI string for badge metadata
     */
    function uri(uint256 tokenId) public view override returns (string memory) {
        string memory msg_ = badgeMessage[tokenId];
        if (bytes(msg_).length == 0) {
            return string(
                abi.encodePacked(
                    "data:application/json;utf8,{",
                    '"name":"PB Recovery Badge #', _toString(tokenId), '",',
                    '"description":"Recovery Badge for PBt position. PASSWORD REQUIRED to activate recovery address and redirect payouts. Visit perpetualbitcoin.io to activate.",',
                    '"image":"https://azure-rational-salmon-152.mypinata.cloud/ipfs/bafybeiclvfm6pqd4rozhffhewfndebxv3s6f2z5t3xsmirchyda3lg7e54/PBr.png"',
                    "}"
                )
            );
        }
        return string(
            abi.encodePacked(
                "data:application/json;utf8,{",
                '"name":"PB Recovery Badge #', _toString(tokenId), '",',
                '"description":"Recovery Badge for PBt position. PASSWORD REQUIRED to activate recovery address and redirect payouts. Visit perpetualbitcoin.io to activate.",',
                '"message":"', msg_, '",',
                '"image":"https://azure-rational-salmon-152.mypinata.cloud/ipfs/bafybeiclvfm6pqd4rozhffhewfndebxv3s6f2z5t3xsmirchyda3lg7e54/PBr.png"',
                "}"
            )
        );
    }

    function _isValidMessage(string memory value) internal pure returns (bool) {
        bytes memory source = bytes(value);
        for (uint256 i = 0; i < source.length; i++) {
            uint8 charCode = uint8(source[i]);
            bool isUpper = charCode >= 65 && charCode <= 90;
            bool isLower = charCode >= 97 && charCode <= 122;
            bool isDigit = charCode >= 48 && charCode <= 57;
            bool isSpace = charCode == 32;
            bool isDash = charCode == 45;
            bool isComma = charCode == 44;
            bool isPeriod = charCode == 46;
            if (!(isUpper || isLower || isDigit || isSpace || isDash || isComma || isPeriod)) {
                return false;
            }
        }
        return true;
    }
    
    function _toString(uint256 value) internal pure returns (string memory) {
        if (value == 0) return "0";
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }

    // ========================================================================
    // UTILITY FUNCTIONS
    // ========================================================================

    /**
     * @notice Check if address holds recovery badge for a specific position
     * @param holder Address to check
     * @param pbtId Tracker NFT ID
     * @return True if holder has recovery badge for this position
     */
    function hasRecoveryBadge(address holder, uint256 pbtId) external view returns (bool) {
        return balanceOf(holder, pbtId) >= 1;
    }
    
    /**
     * @notice Get all badge IDs owned by an address
     * Efficient lookup without scanning - used by frontend to find user's badges
     * @param holder Address to query
     * @return Array of badge IDs owned by this address
     */
    function getBadgeIds(address holder) external view returns (uint256[] memory) {
        return userBadgeIds[holder];
    }
}
          

/PBi.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

/**
 * @title PBi.sol - Inheritance Badge (ERC-1155)
 * @author PB Team
 * @notice Non-transferable ERC-1155 badge for inheritance address activation.
 *
 * Inheritance Mechanism:
 * 1. PBt holder calls Vault.setInheritanceAddress(pbtId, inheritanceAddr, passwordHash)
 * 2. Vault mints 1 PBi badge to inheritanceAddr (identifies inheritance authority)
 * 3. Inheritance address holder calls Vault.activateInheritance(pbtId, password)
 * 4. Vault verifies password matches hash
 * 5. Vault burns PBi badge (one-time use)
 * 6. Payouts redirect to inheritance address
 *
 * Precedence:
 * - If both recovery AND inheritance are set and both activated
 * - Inheritance TAKES PRECEDENCE over recovery
 * - Inheritance activation cannot be reverted
 *
 * Non-Transferability:
 * - PBi cannot be transferred between users (transfer attempts revert)
 * - Only Vault can mint/burn
 * - Protocol flow treats each badge as one-time use
 *
 * IMMUTABLE. NO UPGRADES.
 */

// By 0xBE369

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract PBi is ERC1155, ReentrancyGuard {
    address public immutable DEPLOYER;
    // ========================================================================
    // IMMUTABLE STATE (after lockVault() called)
    // ========================================================================

    address public VAULT;           // Immutable after lockVault() - Only Vault can mint/burn
    bool public vaultLocked;        // Prevents re-locking
    
    // ========================================================================
    // BADGE TRACKING (for efficient lookup)
    // ========================================================================
    
    mapping(address => uint256[]) public userBadgeIds;  // Track all badge IDs per user
    mapping(uint256 => string) public badgeMessage;     // Optional message per badge (e.g. "Happy Birthday")

    // ========================================================================
    // EVENTS
    // ========================================================================

    event BadgeMinted(address indexed recipient, uint256 indexed pbtId);
    event BadgeBurned(address indexed holder, uint256 indexed pbtId);

    // ========================================================================
    // CONSTRUCTOR
    // ========================================================================

    /**
     * @notice Deploy inheritance badge contract
     * VAULT will be set via lockVault() after deployment
     */
    constructor() ERC1155("https://azure-rational-salmon-152.mypinata.cloud/ipfs/bafybeiclvfm6pqd4rozhffhewfndebxv3s6f2z5t3xsmirchyda3lg7e54/PBi.png") {
        DEPLOYER = msg.sender;
        // VAULT set via lockVault()
    }

    /**
     * @notice Lock VAULT address permanently (one-time only)
     * @param _vault Vault contract address
     */
    function lockVault(address _vault) external {
        require(msg.sender == DEPLOYER, "Only deployer");
        require(!vaultLocked, "Vault already locked");
        require(VAULT == address(0), "Vault already set");
        require(_vault != address(0), "Invalid vault");
        
        VAULT = _vault;
        vaultLocked = true;
    }

    // ========================================================================
    // MINTING FUNCTION (Vault-Only)
    // ========================================================================

    /**
     * @notice Mint inheritance badge to designated inheritance address
     * Called by Vault when inheritance address is set
     *
     * @param recipient Inheritance address (designated by PBt holder)
     * @param pbtId Tracker NFT ID this badge is associated with
     */
    function mint(address recipient, uint256 pbtId, string calldata message) external nonReentrant {
        require(msg.sender == VAULT, "Only Vault can mint");
        require(recipient != address(0), "Invalid recipient");
        require(bytes(message).length <= 280, "Message too long");
        require(_isValidMessage(message), "Message: letters numbers spaces dash comma period only");

        // Mint 1 badge (amount = 1, since one-time use)
        // pbtId is the tokenId - uniquely identifies this inheritance badge
        _mint(recipient, pbtId, 1, "");
        
        // Store optional message (empty string = no message)
        if (bytes(message).length > 0) {
            badgeMessage[pbtId] = message;
        }

        // Track badge ID for this user
        userBadgeIds[recipient].push(pbtId);

        emit BadgeMinted(recipient, pbtId);
    }

    // ========================================================================
    // BURNING FUNCTION (Vault-Only)
    // ========================================================================

    /**
     * @notice Burn inheritance badge when inheritance is activated
     * Called by Vault when inheritance address activates inheritance
     * This is one-time use - once burned, inheritance cannot be re-activated
     *
     * @param holder Inheritance address holder
     * @param pbtId Tracker NFT ID
     */
    function burn(address holder, uint256 pbtId) external nonReentrant {
        require(msg.sender == VAULT, "Only Vault can burn");
        require(balanceOf(holder, pbtId) >= 1, "Insufficient badge balance");

        _burn(holder, pbtId, 1);
        
        // Remove badge ID from tracking
        uint256[] storage badges = userBadgeIds[holder];
        for (uint256 i = 0; i < badges.length; i++) {
            if (badges[i] == pbtId) {
                badges[i] = badges[badges.length - 1];
                badges.pop();
                break;
            }
        }

        emit BadgeBurned(holder, pbtId);
    }

    // ========================================================================
    // NON-TRANSFERABILITY ENFORCEMENT
    // ========================================================================

    /**
     * @notice Reverts on transfer attempts (non-transferable)
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) public override {
        revert("PBi: Non-transferable");
    }

    /**
     * @notice Reverts on batch transfer attempts (non-transferable)
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) public override {
        revert("PBi: Non-transferable");
    }

    // ========================================================================
    // METADATA FUNCTIONS
    // ========================================================================

    /**
     * @notice Return URI for badge metadata
     * @param tokenId PBt ID associated with this badge
     * @return URI string for badge metadata
     */
    function uri(uint256 tokenId) public view override returns (string memory) {
        string memory msg_ = badgeMessage[tokenId];
        if (bytes(msg_).length == 0) {
            return string(
                abi.encodePacked(
                    "data:application/json;utf8,{",
                    '"name":"PB Inheritance Badge #', _toString(tokenId), '",',
                    '"description":"Inheritance Badge for PBt position. PASSWORD REQUIRED to activate inheritance address and redirect payouts. Visit perpetualbitcoin.io to activate.",',
                    '"image":"https://azure-rational-salmon-152.mypinata.cloud/ipfs/bafybeiclvfm6pqd4rozhffhewfndebxv3s6f2z5t3xsmirchyda3lg7e54/PBi.png"',
                    "}"
                )
            );
        }
        return string(
            abi.encodePacked(
                "data:application/json;utf8,{",
                '"name":"PB Inheritance Badge #', _toString(tokenId), '",',
                '"description":"Inheritance Badge for PBt position. PASSWORD REQUIRED to activate inheritance address and redirect payouts. Visit perpetualbitcoin.io to activate.",',
                '"message":"', msg_, '",',
                '"image":"https://azure-rational-salmon-152.mypinata.cloud/ipfs/bafybeiclvfm6pqd4rozhffhewfndebxv3s6f2z5t3xsmirchyda3lg7e54/PBi.png"',
                "}"
            )
        );
    }

    function _isValidMessage(string memory value) internal pure returns (bool) {
        bytes memory source = bytes(value);
        for (uint256 i = 0; i < source.length; i++) {
            uint8 charCode = uint8(source[i]);
            bool isUpper = charCode >= 65 && charCode <= 90;
            bool isLower = charCode >= 97 && charCode <= 122;
            bool isDigit = charCode >= 48 && charCode <= 57;
            bool isSpace = charCode == 32;
            bool isDash = charCode == 45;
            bool isComma = charCode == 44;
            bool isPeriod = charCode == 46;
            if (!(isUpper || isLower || isDigit || isSpace || isDash || isComma || isPeriod)) {
                return false;
            }
        }
        return true;
    }
    
    function _toString(uint256 value) internal pure returns (string memory) {
        if (value == 0) return "0";
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }

    // ========================================================================
    // UTILITY FUNCTIONS
    // ========================================================================

    /**
     * @notice Check if address holds inheritance badge for a specific position
     * @param holder Address to check
     * @param pbtId Tracker NFT ID
     * @return True if holder has inheritance badge for this position
     */
    function hasInheritanceBadge(address holder, uint256 pbtId) external view returns (bool) {
        return balanceOf(holder, pbtId) >= 1;
    }

    /**
     * @notice Get all badge IDs owned by an address
     * Efficient lookup without scanning - used by frontend to find user's badges
     * @param holder Address to query
     * @return Array of badge IDs owned by this address
     */
    function getBadgeIds(address holder) external view returns (uint256[] memory) {
        return userBadgeIds[holder];
    }

    /**
     * @notice Verify inheritance precedence (takes priority over recovery)
     * @return True - inheritance always takes precedence when activated
     */
    function inheritanceTakesPrecedence() external pure returns (bool) {
        return true;
    }
}
          

/PBc.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

/**
 * @title PBc - Perpetual Bitcoin Claim Token (Locked)
 * @notice Non-transferable ERC20 locked token. 21M minted at genesis to Vault.
 * Users receive PBc from PB purchases (96.31% allocation) - represents locked PB (PBc).
 * PBc NEVER transfers between users - only between users and Vault.
 * NO BURN MECHANISM - Supply stays 21M forever.
 * Vault redeems PBc by transferring it back to Vault during unlocks.
 */

// By 0xBE369

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract PBc is IERC20, ReentrancyGuard {
    string public constant name = "Perpetual Bitcoin Claim";
    string public constant symbol = "PBc";
    uint8 public constant decimals = 18;
    uint256 public constant totalSupply = 21_000_000e18;
    string public constant logoURI = "https://azure-rational-salmon-152.mypinata.cloud/ipfs/bafybeiclvfm6pqd4rozhffhewfndebxv3s6f2z5t3xsmirchyda3lg7e54/PBc.jpg";
    string public constant website = "https://perpetualbitcoin.io";
    string public constant description = "Perpetual Bitcoin Claim (PBc) - Non-transferable locked claim token. Redeemable 1:1 for PB via geometric price unlocks. Visit perpetualbitcoin.io";
    address public immutable DEPLOYER;

    // IMMUTABLE STATE (after lockVault() called)
    address public VAULT;           // Immutable after lockVault()
    bool public vaultLocked;        // Prevents re-locking

    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    constructor() {
        DEPLOYER = msg.sender;
        // VAULT will be set via lockVault() after deployment
    }

    /**
     * @notice Lock VAULT address and mint total supply to Vault
     * Can only be called once by deployer after Vault deployment
     * Mints all 21M PBc to Vault at genesis
     */
    function lockVault(address _vault) external {
        require(msg.sender == DEPLOYER, "Only deployer");
        require(!vaultLocked, "Vault already locked");
        require(VAULT == address(0), "Vault already set");
        require(_vault != address(0), "Invalid vault");
        
        VAULT = _vault;
        vaultLocked = true;
        
        // Mint total supply to Vault
        balanceOf[_vault] = totalSupply;
        emit Transfer(address(0), _vault, totalSupply);
    }

    /**
     * @notice Transfer PBc - RESTRICTED
     * Only Vault can transfer PBc during unlock operations
     * Users CANNOT transfer PBc between each other
     */
    function transfer(address to, uint256 amount) external nonReentrant returns (bool) {
        require(msg.sender == VAULT, "Only Vault can transfer PBc");
        balanceOf[msg.sender] -= amount;
        balanceOf[to] += amount;
        emit Transfer(msg.sender, to, amount);
        return true;
    }

    /**
     * @notice TransferFrom PBc - RESTRICTED
        * Only Vault can transfer PBc FROM users during unlock operations
        * PBc transfers only back into Vault during settlement flows
     */
    function transferFrom(address from, address to, uint256 amount) external nonReentrant returns (bool) {
        require(msg.sender == VAULT, "Only Vault can call transferFrom");
        require(to == VAULT, "PBc can only transfer to Vault");

        balanceOf[from] -= amount;
        balanceOf[to] += amount;
        emit Transfer(from, to, amount);
        return true;
    }

    /**
        * @notice Standard approval interface for ERC20 compatibility
        * PBc settlement is Vault-mediated and does not consume allowance here
     */
    function approve(address spender, uint256 amount) external returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    modifier onlyVault() {
        require(vaultLocked, "Vault not locked yet");
        require(msg.sender == VAULT, "Only Vault");
        _;
    }
}
          

/PB.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

/**
 * @title PB - Perpetual Bitcoin Liquid Token
 * @notice ERC20: 21M supply, 3.69% liquid (users), 96.31% locked (PBc).
 * SECURITY: Only Vault can receive PB from swaps (prevents direct pair.swap()).
 */

// By 0xBE369

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract PB is IERC20, ReentrancyGuard {
    string public constant name = "Perpetual Bitcoin";
    string public constant symbol = "PB";
    uint8 public constant decimals = 18;
    uint256 public constant totalSupply = 21_000_000e18;
    string public constant logoURI = "https://azure-rational-salmon-152.mypinata.cloud/ipfs/bafybeiclvfm6pqd4rozhffhewfndebxv3s6f2z5t3xsmirchyda3lg7e54/PB.jpg";
    string public constant website = "https://perpetualbitcoin.io";
    string public constant description = "Perpetual Bitcoin (PB) - Price-based unlock protocol. 21M supply, 3.69% liquid on purchase, 96.31% locked. Visit perpetualbitcoin.io";
    address public immutable DEPLOYER;

    // IMMUTABLE STATE (after lockVault() called)
    address public VAULT;           // Immutable after lockVault()
    bool public vaultLocked;        // Prevents re-locking
    address public PAIR;            // PulseX pair address (set by vault only)

    mapping(address => uint256) public balanceOf;
    mapping(address => mapping(address => uint256)) public allowance;

    constructor() {
        DEPLOYER = msg.sender;
        // VAULT will be set via lockVault() after deployment
    }

    /**
     * @notice Lock VAULT address and mint total supply to Vault
     * Can only be called once by deployer after Vault deployment
     * Mints all 21M PB to Vault at genesis
     */
    function lockVault(address _vault) external {
        require(msg.sender == DEPLOYER, "Only deployer");
        require(!vaultLocked, "Vault already locked");
        require(VAULT == address(0), "Vault already set");
        require(_vault != address(0), "Invalid vault");
        
        VAULT = _vault;
        vaultLocked = true;
        
        // Mint total supply to Vault
        balanceOf[_vault] = totalSupply;
        emit Transfer(address(0), _vault, totalSupply);
    }

    /**
     * @notice Set the PulseX pair address (SECURITY: only vault can call)
     * Used to prevent direct pair.swap() calls - vault is the ONLY source of PB swaps
     * 
     * @param _pair The PulseX pair address
     */
    function setPairAddress(address _pair) external {
        require(msg.sender == VAULT, "Only vault can set pair");
        require(_pair != address(0), "Invalid pair address");
        require(PAIR == address(0), "Pair already set");
        PAIR = _pair;
    }

    /**
     * @notice Transfer PB (standard ERC20)
     * Users can transfer their liquid PB freely - including selling to pair
     * SECURITY: Only vault can receive PB from pair (prevents direct pair.swap())
     */
    function transfer(address to, uint256 amount) external nonReentrant returns (bool) {
        address from = msg.sender;
        require(amount > 0, "Amount must be > 0");
        require(balanceOf[from] >= amount, "Insufficient balance");

        // SECURITY: If pair is trying to send PB to non-vault, DENY
        // This prevents direct pair.swap() calls - all PB must come through vault.buyPBDirect()
        if (from == PAIR && to != VAULT) {
            revert("Go to PerpetualBitcoin.io to 'BUY PB'");
        }

        // Standard ERC20 transfer (no split enforcement)
        balanceOf[from] -= amount;
        balanceOf[to] += amount;
        emit Transfer(from, to, amount);
        return true;
    }

    /**
     * @notice TransferFrom (standard ERC20)
     * Users can trade their PB on DEX (including selling to pair)
     * SECURITY: Only vault can receive PB from pair (prevents direct pair.swap())
     */
    function transferFrom(address from, address to, uint256 amount) external nonReentrant returns (bool) {
        // SECURITY: If pair is trying to get PB to non-vault, DENY
        // This prevents direct pair.swap() calls - all swaps must go through vault
        if (from == PAIR && to != VAULT) {
            revert("Go to PerpetualBitcoin.io to 'BUY PB'");
        }

        uint256 allowed = allowance[from][msg.sender];
        if (allowed != type(uint256).max) {
            require(allowed >= amount, "Allowance exceeded");
            allowance[from][msg.sender] = allowed - amount;
        }

        require(amount > 0, "Amount must be > 0");
        require(balanceOf[from] >= amount, "Insufficient balance");

        // Standard ERC20 transferFrom (no split enforcement)
        balanceOf[from] -= amount;
        balanceOf[to] += amount;
        emit Transfer(from, to, amount);
        return true;
    }

    /**
     * @notice Approve spender (standard ERC20)
     */
    function approve(address spender, uint256 amount) external returns (bool) {
        allowance[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    modifier onlyVault() {
        require(vaultLocked, "Vault not locked yet");
        require(msg.sender == VAULT, "Only Vault");
        _;
    }
}
          

/MockSwitchRouter.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract MockSwitchRouter {
    struct AdapterRoute {
        address adapter;
        uint256 amountIn;
        uint24 fee;
    }

    struct SwapHop {
        address tokenIn;
        address tokenOut;
        AdapterRoute[] legs;
    }

    struct SwitchRoute {
        uint256 amountIn;
        SwapHop[] hops;
    }

    address public constant NATIVE_PLACEHOLDER = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;

    address public immutable outputToken;
    uint256 public configuredOutputAmount;
    uint256 public callCount;
    address public lastReceiver;
    uint256 public lastMinAmountOut;
    uint256 public lastFee;
    bool public lastFeeOnOutput;
    address public lastPartnerAddress;

    constructor(address outputToken_) {
        outputToken = outputToken_;
    }

    receive() external payable {}

    function setConfiguredOutputAmount(uint256 amount) external {
        configuredOutputAmount = amount;
    }

    function goSwitch(
        SwitchRoute[] calldata routes,
        address to,
        uint256 minTotalAmountOut,
        uint256 fee,
        bool feeOnOutput,
        bool,
        address partnerAddress
    ) external payable {
        uint256 totalNativeExpected;

        for (uint256 i = 0; i < routes.length; i++) {
            SwitchRoute calldata route = routes[i];
            SwapHop calldata firstHop = route.hops[0];

            if (firstHop.tokenIn == address(0) || firstHop.tokenIn == NATIVE_PLACEHOLDER) {
                totalNativeExpected += route.amountIn;
            } else {
                IERC20(firstHop.tokenIn).transferFrom(msg.sender, address(this), route.amountIn);
            }
        }

        require(msg.value == totalNativeExpected, "bad native amount");
        require(configuredOutputAmount >= minTotalAmountOut, "min out");

        callCount += 1;
        lastReceiver = to;
        lastMinAmountOut = minTotalAmountOut;
        lastFee = fee;
        lastFeeOnOutput = feeOnOutput;
        lastPartnerAddress = partnerAddress;

        IERC20(outputToken).transfer(to, configuredOutputAmount);
    }
}
          

/MockPBVault.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract MockPBVault {
    IERC20 public immutable usdl;
    uint256 public nextPbtId = 1;

    uint256 public lastUsdlAmount;
    uint256 public lastMinPBOut;
    address public lastBuyer;
    address public lastRecipient;
    uint256[] public lastUnlockIds;

    constructor(address usdlToken) {
        usdl = IERC20(usdlToken);
    }

    function buyPBDirect(
        uint256 usdlAmount,
        uint256 minPBOut,
        address recipient,
        uint256[] calldata unlockIds
    ) external returns (uint256) {
        require(usdl.transferFrom(msg.sender, address(this), usdlAmount), "transfer failed");

        delete lastUnlockIds;
        for (uint256 i = 0; i < unlockIds.length; i++) {
            lastUnlockIds.push(unlockIds[i]);
        }

        lastUsdlAmount = usdlAmount;
        lastMinPBOut = minPBOut;
        lastBuyer = msg.sender;
        lastRecipient = recipient;

        uint256 pbtId = nextPbtId;
        nextPbtId++;
        return pbtId;
    }

    function lastUnlockIdsLength() external view returns (uint256) {
        return lastUnlockIds.length;
    }
}
          

/MockERC20.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockERC20 is ERC20 {
    constructor(string memory name_, string memory symbol_) ERC20(name_, symbol_) {}

    function mint(address to, uint256 amount) external {
        _mint(to, amount);
    }
}
          

/LaunchConverter.sol

// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

/**
 * @title LaunchConverter
 * @notice ONE-TIME: Converts PresaleIOU blocks to PBt at launch (BATCHED, IMMUTABLE).
 * Reads priceAtPurchase from each IOU for unlock schedules.
 */

import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

// ============================================================================
// INTERFACES
// ============================================================================

interface IPresaleIOU {
    struct Block {
        address buyer;
        uint256 blockNum;
        uint256 priceAtPurchase;
        bool convertedAtLaunch;
    }

    function getPresaleData() external view returns (Block[] memory);
    function markConverted(uint256 blockNum) external;
    function ownerOf(uint256 tokenId) external view returns (address);
    function getPBPerBlock(uint256 blockNum) external pure returns (uint256);
}

interface IVault {
    function allocatePresaleUser(
        address user,
        uint256 pbAmount,
        uint256 entryPrice
    ) external;
    
    function seedInitialLP(uint256, uint256, uint256, uint256) 
        external 
        returns (uint256, uint256, uint256);
}

contract LaunchConverter is ReentrancyGuard {

    // ========================================================================
    // IMMUTABLE STATE
    // ========================================================================

    address public immutable PRESALE_IOU;
    address public immutable VAULT;
    address public immutable deployer;

    // ========================================================================
    // CONVERSION PARAMETERS
    // ========================================================================

    uint256 public constant TOTAL_BLOCKS = 555;
    uint256 public batchSize = 56; // Adjustable (default ~3M gas), can be tweaked

    // ========================================================================
    // BATCH TRACKING STATE
    // ========================================================================

    uint256 public nextBlockToConvert = 0;      // Next block index to process (0-554)
    uint256 public currentBatch = 0;            // Current batch number (1, 2, 3, ...)
    uint256 public totalBatches = 0;            // Total batches needed (calculated on first run)
    uint256 public blocksConverted = 0;         // Total blocks successfully converted
    
    bool public conversionStarted = false;      // True after first batch
    bool public conversionFinalized = false;    // True when n=N (FINAL LOCKED)

    // ========================================================================
    // EVENTS
    // ========================================================================

    event ConversionBatchExecuted(
        uint256 batchNumber,       // Current batch (n)
        uint256 totalBatches,      // Total batches (N)
        uint256 fromBlock,         // Starting block number (1-555)
        uint256 toBlock,           // Ending block number (1-555)
        uint256 totalConverted,    // Cumulative blocks converted
        bool finalized             // True when n=N (FINAL LOCKED)
    );

    event BlockConverted(
        uint256 indexed blockNum,
        address indexed buyer,
        uint256 pbAmount,
        uint256 entryPrice
    );

    event ConversionFinalized(uint256 timestamp, uint256 totalConverted);

    // ========================================================================
    // CONSTRUCTOR
    // ========================================================================

    constructor(address _presaleIOU, address _vault) {
        require(_presaleIOU != address(0), "Invalid PresaleIOU address");
        require(_vault != address(0), "Invalid Vault address");

        PRESALE_IOU = _presaleIOU;
        VAULT = _vault;
        deployer = msg.sender;
    }

    // ========================================================================
    // BATCHED CONVERSION
    // ========================================================================

    /**
     * @notice Execute one batch of conversions
     * @dev Call multiple times until conversionFinalized = true
     * 
     * Gas Efficiency:
     * - Default 56 blocks/batch = ~3M gas (safe for busy networks)
     * - Adjustable via setBatchSize() before starting
     * 
     * Progress Tracking:
     * - Emits "Batch n/N" in ConversionBatchExecuted event
     * - When n=N, sets conversionFinalized = FINAL LOCKED
     * 
     * Example:
     * - Batch 1/10: Converts blocks 1-56
     * - Batch 2/10: Converts blocks 57-112
     * - ...
     * - Batch 10/10: Converts blocks 505-555, sets FINAL LOCKED
     */
    function executeConversionBatch() external nonReentrant {
        require(msg.sender == deployer, "Only deployer");
        require(!conversionFinalized, "Conversion FINAL LOCKED");
        
        // Fetch all blocks from PresaleIOU
        IPresaleIOU.Block[] memory blocks = IPresaleIOU(PRESALE_IOU).getPresaleData();
        uint256 totalBlocks = blocks.length;
        
        require(nextBlockToConvert < totalBlocks, "All blocks converted");
        
        // Calculate total batches on first run
        if (!conversionStarted) {
            conversionStarted = true;
            totalBatches = (totalBlocks + batchSize - 1) / batchSize; // Ceiling division
        }
        
        // Increment batch counter (1-indexed for user display)
        currentBatch++;
        
        // Calculate range for this batch
        uint256 startBlock = nextBlockToConvert;
        uint256 endBlock = nextBlockToConvert + batchSize;
        if (endBlock > totalBlocks) {
            endBlock = totalBlocks;
        }
        
        // Convert blocks in this batch
        for (uint256 i = startBlock; i < endBlock; i++) {
            IPresaleIOU.Block memory block_data = blocks[i];
            
            // Skip empty blocks (no buyer)
            if (block_data.buyer == address(0)) {
                continue;
            }
            
            // Skip already converted blocks
            if (block_data.convertedAtLaunch) {
                continue;
            }
            
            // Convert this block
            _convertBlock(block_data);
            blocksConverted++;
        }
        
        // Update progress
        nextBlockToConvert = endBlock;
        
        // Check if this is the FINAL batch (n=N)
        bool isFinalBatch = (nextBlockToConvert >= totalBlocks);
        
        if (isFinalBatch) {
            conversionFinalized = true; // FINAL LOCKED - cannot be called again
            emit ConversionFinalized(block.timestamp, blocksConverted);
        }
        
        // Emit progress: Batch n/N
        emit ConversionBatchExecuted(
            currentBatch,           // n
            totalBatches,           // N
            startBlock + 1,         // From block (1-indexed for display)
            endBlock,               // To block (1-indexed)
            blocksConverted,        // Total converted so far
            isFinalBatch            // True when n=N (FINAL LOCKED)
        );
    }

    /**
     * @notice Convert a single PresaleIOU block to PBt
     */
    function _convertBlock(IPresaleIOU.Block memory block_data) internal {
        address buyer = block_data.buyer;
        uint256 blockNum = block_data.blockNum;
        uint256 entryPrice = block_data.priceAtPurchase;

        require(buyer != address(0), "Invalid buyer");
        require(entryPrice > 0, "Invalid entry price");

        // Get variable PB amount for this block
        uint256 pbAmount = IPresaleIOU(PRESALE_IOU).getPBPerBlock(blockNum);

        // Allocate tokens via Vault (mints PBt tracker)
        IVault(VAULT).allocatePresaleUser(
            buyer,
            pbAmount,
            entryPrice
        );

        // Burn IOU NFT
        IPresaleIOU(PRESALE_IOU).markConverted(blockNum);

        emit BlockConverted(blockNum, buyer, pbAmount, entryPrice);
    }

    // ========================================================================
    // CONFIGURATION (before starting)
    // ========================================================================

    /**
     * @notice Adjust batch size before starting conversion
     * @param _batchSize New batch size (1-555)
     * 
     * Gas Guide:
     * - 25 blocks = ~1.35M gas (very safe)
     * - 50 blocks = ~2.7M gas (recommended)
     * - 56 blocks = ~3M gas (default, safe for busy mainnet)
     * - 100 blocks = ~5.4M gas (risky if network congested)
     */
    function setBatchSize(uint256 _batchSize) external {
        require(msg.sender == deployer, "Only deployer");
        require(!conversionStarted, "Conversion already started");
        require(_batchSize > 0 && _batchSize <= TOTAL_BLOCKS, "Invalid batch size");
        
        batchSize = _batchSize;
    }

    // ========================================================================
    // VIEW FUNCTIONS
    // ========================================================================

    /**
     * @notice Get conversion progress as "Batch n/N"
     * @return batch Current batch number (n)
     * @return total Total batches (N)
     * @return converted Total blocks converted
     * @return finalized True when n=N (FINAL LOCKED)
     */
    function getProgress() external view returns (
        uint256 batch,
        uint256 total,
        uint256 converted,
        bool finalized
    ) {
        return (currentBatch, totalBatches, blocksConverted, conversionFinalized);
    }

    /**
     * @notice Get detailed conversion status
     */
    function getStatus() external view returns (
        bool started,
        bool finalized,
        uint256 nextBlock,
        uint256 currentBatch_,
        uint256 totalBatches_,
        uint256 blocksConverted_,
        uint256 batchSize_
    ) {
        return (
            conversionStarted,
            conversionFinalized,
            nextBlockToConvert,
            currentBatch,
            totalBatches,
            blocksConverted,
            batchSize
        );
    }

    /**
     * @notice Get blocks remaining to convert
     */
    function getBlocksRemaining() external view returns (uint256) {
        if (conversionFinalized) return 0;
        return TOTAL_BLOCKS - nextBlockToConvert;
    }

    // ========================================================================
    // DEPLOYMENT HELPER (Phase 9: Seed LP)
    // ========================================================================

    /**
     * @notice Proxy to Vault.seedInitialLP() during deployment
     * @dev Only callable before conversion starts
     */
    function seedLP(uint256 a, uint256 b, uint256 c, uint256 d) 
        external 
        returns (uint256, uint256, uint256) 
    {
        require(msg.sender == deployer, "Only deployer");
        require(!conversionStarted, "Already started");
        
        return IVault(VAULT).seedInitialLP(a, b, c, d);
    }

    // ========================================================================
    // SECURITY NOTES
    // ========================================================================

    /**
     * @notice IMMUTABLE & FINAL LOCKED
     * 
     * Design ensures:
     * 1. No owner or upgrades (fully immutable contract)
     * 2. conversionFinalized flag is permanent when n=N (cannot be reset)
     * 3. Progress tracked as "Batch n/N" in events
     * 4. Batch size adjustable before start (for gas optimization)
     * 5. Reentrancy guard prevents attacks during execution
     * 
     * When n=N (final batch), conversion is FINAL LOCKED forever.
     */
}
          

/IERC1155Receiver.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev _Available since v3.1._
 */
interface IERC1155Receiver is IERC165 {
    /**
     * @dev Handles the receipt of a single ERC1155 token type. This function is
     * called at the end of a `safeTransferFrom` after the balance has been updated.
     *
     * NOTE: To accept the transfer, this must return
     * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
     * (i.e. 0xf23a6e61, or its own function selector).
     *
     * @param operator The address which initiated the transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param id The ID of the token being transferred
     * @param value The amount of tokens being transferred
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
     */
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    ) external returns (bytes4);

    /**
     * @dev Handles the receipt of a multiple ERC1155 token types. This function
     * is called at the end of a `safeBatchTransferFrom` after the balances have
     * been updated.
     *
     * NOTE: To accept the transfer(s), this must return
     * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
     * (i.e. 0xbc197c81, or its own function selector).
     *
     * @param operator The address which initiated the batch transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param ids An array containing ids of each token being transferred (order and length must match values array)
     * @param values An array containing amounts of each token being transferred (order and length must match ids array)
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
     */
    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external returns (bytes4);
}
          

/Strings.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Strings.sol)

pragma solidity ^0.8.0;

import "./math/Math.sol";
import "./math/SignedMath.sol";

/**
 * @dev String operations.
 */
library Strings {
    bytes16 private constant _SYMBOLS = "0123456789abcdef";
    uint8 private constant _ADDRESS_LENGTH = 20;

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        unchecked {
            uint256 length = Math.log10(value) + 1;
            string memory buffer = new string(length);
            uint256 ptr;
            /// @solidity memory-safe-assembly
            assembly {
                ptr := add(buffer, add(32, length))
            }
            while (true) {
                ptr--;
                /// @solidity memory-safe-assembly
                assembly {
                    mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
                }
                value /= 10;
                if (value == 0) break;
            }
            return buffer;
        }
    }

    /**
     * @dev Converts a `int256` to its ASCII `string` decimal representation.
     */
    function toString(int256 value) internal pure returns (string memory) {
        return string(abi.encodePacked(value < 0 ? "-" : "", toString(SignedMath.abs(value))));
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        unchecked {
            return toHexString(value, Math.log256(value) + 1);
        }
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
     */
    function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
        bytes memory buffer = new bytes(2 * length + 2);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 2 * length + 1; i > 1; --i) {
            buffer[i] = _SYMBOLS[value & 0xf];
            value >>= 4;
        }
        require(value == 0, "Strings: hex length insufficient");
        return string(buffer);
    }

    /**
     * @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
     */
    function toHexString(address addr) internal pure returns (string memory) {
        return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
    }

    /**
     * @dev Returns true if the two strings are equal.
     */
    function equal(string memory a, string memory b) internal pure returns (bool) {
        return keccak256(bytes(a)) == keccak256(bytes(b));
    }
}
          

/SignedMath.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard signed math utilities missing in the Solidity language.
 */
library SignedMath {
    /**
     * @dev Returns the largest of two signed numbers.
     */
    function max(int256 a, int256 b) internal pure returns (int256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two signed numbers.
     */
    function min(int256 a, int256 b) internal pure returns (int256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two signed numbers without overflow.
     * The result is rounded towards zero.
     */
    function average(int256 a, int256 b) internal pure returns (int256) {
        // Formula from the book "Hacker's Delight"
        int256 x = (a & b) + ((a ^ b) >> 1);
        return x + (int256(uint256(x) >> 255) & (a ^ b));
    }

    /**
     * @dev Returns the absolute unsigned value of a signed value.
     */
    function abs(int256 n) internal pure returns (uint256) {
        unchecked {
            // must be unchecked in order to support `n = type(int256).min`
            return uint256(n >= 0 ? n : -n);
        }
    }
}
          

/Math.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/math/Math.sol)

pragma solidity ^0.8.0;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    enum Rounding {
        Down, // Toward negative infinity
        Up, // Toward infinity
        Zero // Toward zero
    }

    /**
     * @dev Returns the largest of two numbers.
     */
    function max(uint256 a, uint256 b) internal pure returns (uint256) {
        return a > b ? a : b;
    }

    /**
     * @dev Returns the smallest of two numbers.
     */
    function min(uint256 a, uint256 b) internal pure returns (uint256) {
        return a < b ? a : b;
    }

    /**
     * @dev Returns the average of two numbers. The result is rounded towards
     * zero.
     */
    function average(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b) / 2 can overflow.
        return (a & b) + (a ^ b) / 2;
    }

    /**
     * @dev Returns the ceiling of the division of two numbers.
     *
     * This differs from standard division with `/` in that it rounds up instead
     * of rounding down.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        // (a + b - 1) / b can overflow on addition, so we distribute.
        return a == 0 ? 0 : (a - 1) / b + 1;
    }

    /**
     * @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
     * @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
     * with further edits by Uniswap Labs also under MIT license.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
        unchecked {
            // 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
            // use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
            // variables such that product = prod1 * 2^256 + prod0.
            uint256 prod0; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                prod0 := mul(x, y)
                prod1 := sub(sub(mm, prod0), lt(mm, prod0))
            }

            // Handle non-overflow cases, 256 by 256 division.
            if (prod1 == 0) {
                // Solidity will revert if denominator == 0, unlike the div opcode on its own.
                // The surrounding unchecked block does not change this fact.
                // See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
                return prod0 / denominator;
            }

            // Make sure the result is less than 2^256. Also prevents denominator == 0.
            require(denominator > prod1, "Math: mulDiv overflow");

            ///////////////////////////////////////////////
            // 512 by 256 division.
            ///////////////////////////////////////////////

            // Make division exact by subtracting the remainder from [prod1 prod0].
            uint256 remainder;
            assembly {
                // Compute remainder using mulmod.
                remainder := mulmod(x, y, denominator)

                // Subtract 256 bit number from 512 bit number.
                prod1 := sub(prod1, gt(remainder, prod0))
                prod0 := sub(prod0, remainder)
            }

            // Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
            // See https://cs.stackexchange.com/q/138556/92363.

            // Does not overflow because the denominator cannot be zero at this stage in the function.
            uint256 twos = denominator & (~denominator + 1);
            assembly {
                // Divide denominator by twos.
                denominator := div(denominator, twos)

                // Divide [prod1 prod0] by twos.
                prod0 := div(prod0, twos)

                // Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
                twos := add(div(sub(0, twos), twos), 1)
            }

            // Shift in bits from prod1 into prod0.
            prod0 |= prod1 * twos;

            // Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
            // that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
            // four bits. That is, denominator * inv = 1 mod 2^4.
            uint256 inverse = (3 * denominator) ^ 2;

            // Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
            // in modular arithmetic, doubling the correct bits in each step.
            inverse *= 2 - denominator * inverse; // inverse mod 2^8
            inverse *= 2 - denominator * inverse; // inverse mod 2^16
            inverse *= 2 - denominator * inverse; // inverse mod 2^32
            inverse *= 2 - denominator * inverse; // inverse mod 2^64
            inverse *= 2 - denominator * inverse; // inverse mod 2^128
            inverse *= 2 - denominator * inverse; // inverse mod 2^256

            // Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
            // This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
            // less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
            // is no longer required.
            result = prod0 * inverse;
            return result;
        }
    }

    /**
     * @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
     */
    function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
        uint256 result = mulDiv(x, y, denominator);
        if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
            result += 1;
        }
        return result;
    }

    /**
     * @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
     *
     * Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
     */
    function sqrt(uint256 a) internal pure returns (uint256) {
        if (a == 0) {
            return 0;
        }

        // For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
        //
        // We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
        // `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
        //
        // This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
        // → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
        // → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
        //
        // Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
        uint256 result = 1 << (log2(a) >> 1);

        // At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
        // since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
        // every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
        // into the expected uint128 result.
        unchecked {
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            result = (result + a / result) >> 1;
            return min(result, a / result);
        }
    }

    /**
     * @notice Calculates sqrt(a), following the selected rounding direction.
     */
    function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = sqrt(a);
            return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 128;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 64;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 32;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 16;
            }
            if (value >> 8 > 0) {
                value >>= 8;
                result += 8;
            }
            if (value >> 4 > 0) {
                value >>= 4;
                result += 4;
            }
            if (value >> 2 > 0) {
                value >>= 2;
                result += 2;
            }
            if (value >> 1 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 2, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log2(value);
            return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10, rounded down, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >= 10 ** 64) {
                value /= 10 ** 64;
                result += 64;
            }
            if (value >= 10 ** 32) {
                value /= 10 ** 32;
                result += 32;
            }
            if (value >= 10 ** 16) {
                value /= 10 ** 16;
                result += 16;
            }
            if (value >= 10 ** 8) {
                value /= 10 ** 8;
                result += 8;
            }
            if (value >= 10 ** 4) {
                value /= 10 ** 4;
                result += 4;
            }
            if (value >= 10 ** 2) {
                value /= 10 ** 2;
                result += 2;
            }
            if (value >= 10 ** 1) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 10, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log10(value);
            return result + (rounding == Rounding.Up && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256, rounded down, of a positive value.
     * Returns 0 if given 0.
     *
     * Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
     */
    function log256(uint256 value) internal pure returns (uint256) {
        uint256 result = 0;
        unchecked {
            if (value >> 128 > 0) {
                value >>= 128;
                result += 16;
            }
            if (value >> 64 > 0) {
                value >>= 64;
                result += 8;
            }
            if (value >> 32 > 0) {
                value >>= 32;
                result += 4;
            }
            if (value >> 16 > 0) {
                value >>= 16;
                result += 2;
            }
            if (value >> 8 > 0) {
                result += 1;
            }
        }
        return result;
    }

    /**
     * @dev Return the log in base 256, following the selected rounding direction, of a positive value.
     * Returns 0 if given 0.
     */
    function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
        unchecked {
            uint256 result = log256(value);
            return result + (rounding == Rounding.Up && 1 << (result << 3) < value ? 1 : 0);
        }
    }
}
          

/IERC165.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
          

/ERC165.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}
          

/Context.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
}
          

/Address.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     *
     * Furthermore, `isContract` will also return true if the target contract within
     * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
     * which only has an effect at the end of a transaction.
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
     *
     * IMPORTANT: because control is transferred to `recipient`, care must be
     * taken to not create reentrancy vulnerabilities. Consider using
     * {ReentrancyGuard} or the
     * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        require(address(this).balance >= amount, "Address: insufficient balance");

        (bool success, ) = recipient.call{value: amount}("");
        require(success, "Address: unable to send value, recipient may have reverted");
    }

    /**
     * @dev Performs a Solidity function call using a low level `call`. A
     * plain `call` is an unsafe replacement for a function call: use this
     * function instead.
     *
     * If `target` reverts with a revert reason, it is bubbled up by this
     * function (like regular Solidity function calls).
     *
     * Returns the raw returned data. To convert to the expected return value,
     * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
     *
     * Requirements:
     *
     * - `target` must be a contract.
     * - calling `target` with `data` must not revert.
     *
     * _Available since v3.1._
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, "Address: low-level call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
     * `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but also transferring `value` wei to `target`.
     *
     * Requirements:
     *
     * - the calling contract must have an ETH balance of at least `value`.
     * - the called Solidity function must be `payable`.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
    }

    /**
     * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
     * with `errorMessage` as a fallback revert reason when `target` reverts.
     *
     * _Available since v3.1._
     */
    function functionCallWithValue(
        address target,
        bytes memory data,
        uint256 value,
        string memory errorMessage
    ) internal returns (bytes memory) {
        require(address(this).balance >= value, "Address: insufficient balance for call");
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
        return functionStaticCall(target, data, "Address: low-level static call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionDelegateCall(target, data, "Address: low-level delegate call failed");
    }

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a delegate call.
     *
     * _Available since v3.4._
     */
    function functionDelegateCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal returns (bytes memory) {
        (bool success, bytes memory returndata) = target.delegatecall(data);
        return verifyCallResultFromTarget(target, success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
     * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
     *
     * _Available since v4.8._
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        if (success) {
            if (returndata.length == 0) {
                // only check isContract if the call was successful and the return data is empty
                // otherwise we already know that it was a contract
                require(isContract(target), "Address: call to non-contract");
            }
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason or using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            _revert(returndata, errorMessage);
        }
    }

    function _revert(bytes memory returndata, string memory errorMessage) private pure {
        // Look for revert reason and bubble it up if present
        if (returndata.length > 0) {
            // The easiest way to bubble the revert reason is using memory via assembly
            /// @solidity memory-safe-assembly
            assembly {
                let returndata_size := mload(returndata)
                revert(add(32, returndata), returndata_size)
            }
        } else {
            revert(errorMessage);
        }
    }
}
          

/IERC721Receiver.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)

pragma solidity ^0.8.0;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
interface IERC721Receiver {
    /**
     * @dev Whenever an {IERC721} `tokenId` token is transferred to this contract via {IERC721-safeTransferFrom}
     * by `operator` from `from`, this function is called.
     *
     * It must return its Solidity selector to confirm the token transfer.
     * If any other value is returned or the interface is not implemented by the recipient, the transfer will be reverted.
     *
     * The selector can be obtained in Solidity with `IERC721Receiver.onERC721Received.selector`.
     */
    function onERC721Received(
        address operator,
        address from,
        uint256 tokenId,
        bytes calldata data
    ) external returns (bytes4);
}
          

/IERC721.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the caller.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}
          

/extensions/IERC721Metadata.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/extensions/IERC721Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC721.sol";

/**
 * @title ERC-721 Non-Fungible Token Standard, optional metadata extension
 * @dev See https://eips.ethereum.org/EIPS/eip-721
 */
interface IERC721Metadata is IERC721 {
    /**
     * @dev Returns the token collection name.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the token collection symbol.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
     */
    function tokenURI(uint256 tokenId) external view returns (string memory);
}
          

/extensions/ERC721Burnable.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC721/extensions/ERC721Burnable.sol)

pragma solidity ^0.8.0;

import "../ERC721.sol";
import "../../../utils/Context.sol";

/**
 * @title ERC721 Burnable Token
 * @dev ERC721 Token that can be burned (destroyed).
 */
abstract contract ERC721Burnable is Context, ERC721 {
    /**
     * @dev Burns `tokenId`. See {ERC721-_burn}.
     *
     * Requirements:
     *
     * - The caller must own `tokenId` or be an approved operator.
     */
    function burn(uint256 tokenId) public virtual {
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
        _burn(tokenId);
    }
}
          

/ERC721.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/ERC721.sol)

pragma solidity ^0.8.0;

import "./IERC721.sol";
import "./IERC721Receiver.sol";
import "./extensions/IERC721Metadata.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/Strings.sol";
import "../../utils/introspection/ERC165.sol";

/**
 * @dev Implementation of https://eips.ethereum.org/EIPS/eip-721[ERC721] Non-Fungible Token Standard, including
 * the Metadata extension, but not including the Enumerable extension, which is available separately as
 * {ERC721Enumerable}.
 */
contract ERC721 is Context, ERC165, IERC721, IERC721Metadata {
    using Address for address;
    using Strings for uint256;

    // Token name
    string private _name;

    // Token symbol
    string private _symbol;

    // Mapping from token ID to owner address
    mapping(uint256 => address) private _owners;

    // Mapping owner address to token count
    mapping(address => uint256) private _balances;

    // Mapping from token ID to approved address
    mapping(uint256 => address) private _tokenApprovals;

    // Mapping from owner to operator approvals
    mapping(address => mapping(address => bool)) private _operatorApprovals;

    /**
     * @dev Initializes the contract by setting a `name` and a `symbol` to the token collection.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return
            interfaceId == type(IERC721).interfaceId ||
            interfaceId == type(IERC721Metadata).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    /**
     * @dev See {IERC721-balanceOf}.
     */
    function balanceOf(address owner) public view virtual override returns (uint256) {
        require(owner != address(0), "ERC721: address zero is not a valid owner");
        return _balances[owner];
    }

    /**
     * @dev See {IERC721-ownerOf}.
     */
    function ownerOf(uint256 tokenId) public view virtual override returns (address) {
        address owner = _ownerOf(tokenId);
        require(owner != address(0), "ERC721: invalid token ID");
        return owner;
    }

    /**
     * @dev See {IERC721Metadata-name}.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev See {IERC721Metadata-symbol}.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev See {IERC721Metadata-tokenURI}.
     */
    function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
        _requireMinted(tokenId);

        string memory baseURI = _baseURI();
        return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
    }

    /**
     * @dev Base URI for computing {tokenURI}. If set, the resulting URI for each
     * token will be the concatenation of the `baseURI` and the `tokenId`. Empty
     * by default, can be overridden in child contracts.
     */
    function _baseURI() internal view virtual returns (string memory) {
        return "";
    }

    /**
     * @dev See {IERC721-approve}.
     */
    function approve(address to, uint256 tokenId) public virtual override {
        address owner = ERC721.ownerOf(tokenId);
        require(to != owner, "ERC721: approval to current owner");

        require(
            _msgSender() == owner || isApprovedForAll(owner, _msgSender()),
            "ERC721: approve caller is not token owner or approved for all"
        );

        _approve(to, tokenId);
    }

    /**
     * @dev See {IERC721-getApproved}.
     */
    function getApproved(uint256 tokenId) public view virtual override returns (address) {
        _requireMinted(tokenId);

        return _tokenApprovals[tokenId];
    }

    /**
     * @dev See {IERC721-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public virtual override {
        _setApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev See {IERC721-isApprovedForAll}.
     */
    function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[owner][operator];
    }

    /**
     * @dev See {IERC721-transferFrom}.
     */
    function transferFrom(address from, address to, uint256 tokenId) public virtual override {
        //solhint-disable-next-line max-line-length
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");

        _transfer(from, to, tokenId);
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) public virtual override {
        safeTransferFrom(from, to, tokenId, "");
    }

    /**
     * @dev See {IERC721-safeTransferFrom}.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes memory data) public virtual override {
        require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: caller is not token owner or approved");
        _safeTransfer(from, to, tokenId, data);
    }

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC721 protocol to prevent tokens from being forever locked.
     *
     * `data` is additional data, it has no specified format and it is sent in call to `to`.
     *
     * This internal function is equivalent to {safeTransferFrom}, and can be used to e.g.
     * implement alternative mechanisms to perform token transfer, such as signature-based.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal virtual {
        _transfer(from, to, tokenId);
        require(_checkOnERC721Received(from, to, tokenId, data), "ERC721: transfer to non ERC721Receiver implementer");
    }

    /**
     * @dev Returns the owner of the `tokenId`. Does NOT revert if token doesn't exist
     */
    function _ownerOf(uint256 tokenId) internal view virtual returns (address) {
        return _owners[tokenId];
    }

    /**
     * @dev Returns whether `tokenId` exists.
     *
     * Tokens can be managed by their owner or approved accounts via {approve} or {setApprovalForAll}.
     *
     * Tokens start existing when they are minted (`_mint`),
     * and stop existing when they are burned (`_burn`).
     */
    function _exists(uint256 tokenId) internal view virtual returns (bool) {
        return _ownerOf(tokenId) != address(0);
    }

    /**
     * @dev Returns whether `spender` is allowed to manage `tokenId`.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
        address owner = ERC721.ownerOf(tokenId);
        return (spender == owner || isApprovedForAll(owner, spender) || getApproved(tokenId) == spender);
    }

    /**
     * @dev Safely mints `tokenId` and transfers it to `to`.
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function _safeMint(address to, uint256 tokenId) internal virtual {
        _safeMint(to, tokenId, "");
    }

    /**
     * @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is
     * forwarded in {IERC721Receiver-onERC721Received} to contract recipients.
     */
    function _safeMint(address to, uint256 tokenId, bytes memory data) internal virtual {
        _mint(to, tokenId);
        require(
            _checkOnERC721Received(address(0), to, tokenId, data),
            "ERC721: transfer to non ERC721Receiver implementer"
        );
    }

    /**
     * @dev Mints `tokenId` and transfers it to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {_safeMint} whenever possible
     *
     * Requirements:
     *
     * - `tokenId` must not exist.
     * - `to` cannot be the zero address.
     *
     * Emits a {Transfer} event.
     */
    function _mint(address to, uint256 tokenId) internal virtual {
        require(to != address(0), "ERC721: mint to the zero address");
        require(!_exists(tokenId), "ERC721: token already minted");

        _beforeTokenTransfer(address(0), to, tokenId, 1);

        // Check that tokenId was not minted by `_beforeTokenTransfer` hook
        require(!_exists(tokenId), "ERC721: token already minted");

        unchecked {
            // Will not overflow unless all 2**256 token ids are minted to the same owner.
            // Given that tokens are minted one by one, it is impossible in practice that
            // this ever happens. Might change if we allow batch minting.
            // The ERC fails to describe this case.
            _balances[to] += 1;
        }

        _owners[tokenId] = to;

        emit Transfer(address(0), to, tokenId);

        _afterTokenTransfer(address(0), to, tokenId, 1);
    }

    /**
     * @dev Destroys `tokenId`.
     * The approval is cleared when the token is burned.
     * This is an internal function that does not check if the sender is authorized to operate on the token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     *
     * Emits a {Transfer} event.
     */
    function _burn(uint256 tokenId) internal virtual {
        address owner = ERC721.ownerOf(tokenId);

        _beforeTokenTransfer(owner, address(0), tokenId, 1);

        // Update ownership in case tokenId was transferred by `_beforeTokenTransfer` hook
        owner = ERC721.ownerOf(tokenId);

        // Clear approvals
        delete _tokenApprovals[tokenId];

        unchecked {
            // Cannot overflow, as that would require more tokens to be burned/transferred
            // out than the owner initially received through minting and transferring in.
            _balances[owner] -= 1;
        }
        delete _owners[tokenId];

        emit Transfer(owner, address(0), tokenId);

        _afterTokenTransfer(owner, address(0), tokenId, 1);
    }

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *  As opposed to {transferFrom}, this imposes no restrictions on msg.sender.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     *
     * Emits a {Transfer} event.
     */
    function _transfer(address from, address to, uint256 tokenId) internal virtual {
        require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
        require(to != address(0), "ERC721: transfer to the zero address");

        _beforeTokenTransfer(from, to, tokenId, 1);

        // Check that tokenId was not transferred by `_beforeTokenTransfer` hook
        require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");

        // Clear approvals from the previous owner
        delete _tokenApprovals[tokenId];

        unchecked {
            // `_balances[from]` cannot overflow for the same reason as described in `_burn`:
            // `from`'s balance is the number of token held, which is at least one before the current
            // transfer.
            // `_balances[to]` could overflow in the conditions described in `_mint`. That would require
            // all 2**256 token ids to be minted, which in practice is impossible.
            _balances[from] -= 1;
            _balances[to] += 1;
        }
        _owners[tokenId] = to;

        emit Transfer(from, to, tokenId);

        _afterTokenTransfer(from, to, tokenId, 1);
    }

    /**
     * @dev Approve `to` to operate on `tokenId`
     *
     * Emits an {Approval} event.
     */
    function _approve(address to, uint256 tokenId) internal virtual {
        _tokenApprovals[tokenId] = to;
        emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Emits an {ApprovalForAll} event.
     */
    function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
        require(owner != operator, "ERC721: approve to caller");
        _operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

    /**
     * @dev Reverts if the `tokenId` has not been minted yet.
     */
    function _requireMinted(uint256 tokenId) internal view virtual {
        require(_exists(tokenId), "ERC721: invalid token ID");
    }

    /**
     * @dev Internal function to invoke {IERC721Receiver-onERC721Received} on a target address.
     * The call is not executed if the target address is not a contract.
     *
     * @param from address representing the previous owner of the given token ID
     * @param to target address that will receive the tokens
     * @param tokenId uint256 ID of the token to be transferred
     * @param data bytes optional data to send along with the call
     * @return bool whether the call correctly returned the expected magic value
     */
    function _checkOnERC721Received(
        address from,
        address to,
        uint256 tokenId,
        bytes memory data
    ) private returns (bool) {
        if (to.isContract()) {
            try IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, data) returns (bytes4 retval) {
                return retval == IERC721Receiver.onERC721Received.selector;
            } catch (bytes memory reason) {
                if (reason.length == 0) {
                    revert("ERC721: transfer to non ERC721Receiver implementer");
                } else {
                    /// @solidity memory-safe-assembly
                    assembly {
                        revert(add(32, reason), mload(reason))
                    }
                }
            }
        } else {
            return true;
        }
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting and burning. If {ERC721Consecutive} is
     * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s tokens will be transferred to `to`.
     * - When `from` is zero, the tokens will be minted for `to`.
     * - When `to` is zero, ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     * - `batchSize` is non-zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}

    /**
     * @dev Hook that is called after any token transfer. This includes minting and burning. If {ERC721Consecutive} is
     * used, the hook may be called as part of a consecutive (batch) mint, as indicated by `batchSize` greater than 1.
     *
     * Calling conditions:
     *
     * - When `from` and `to` are both non-zero, ``from``'s tokens were transferred to `to`.
     * - When `from` is zero, the tokens were minted for `to`.
     * - When `to` is zero, ``from``'s tokens were burned.
     * - `from` and `to` are never both zero.
     * - `batchSize` is non-zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(address from, address to, uint256 firstTokenId, uint256 batchSize) internal virtual {}

    /**
     * @dev Unsafe write access to the balances, used by extensions that "mint" tokens using an {ownerOf} override.
     *
     * WARNING: Anyone calling this MUST ensure that the balances remain consistent with the ownership. The invariant
     * being that for any address `a` the value returned by `balanceOf(a)` must be equal to the number of tokens such
     * that `ownerOf(tokenId)` is `a`.
     */
    // solhint-disable-next-line func-name-mixedcase
    function __unsafe_increaseBalance(address account, uint256 amount) internal {
        _balances[account] += amount;
    }
}
          

/utils/SafeERC20.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";
import "../extensions/IERC20Permit.sol";
import "../../../utils/Address.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20 {
    using Address for address;

    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(IERC20 token, address spender, uint256 value) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        uint256 oldAllowance = token.allowance(address(this), spender);
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance + value));
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 value) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, oldAllowance - value));
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Compatible with tokens that require the approval to be set to
     * 0 before setting it to a non-zero value.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeWithSelector(token.approve.selector, spender, value);

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, 0));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    function safePermit(
        IERC20Permit token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        require(returndata.length == 0 || abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
        // and not revert is the subcall reverts.

        (bool success, bytes memory returndata) = address(token).call(data);
        return
            success && (returndata.length == 0 || abi.decode(returndata, (bool))) && Address.isContract(address(token));
    }
}
          

/IERC20.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20 {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the amount of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves `amount` tokens from the caller's account to `to`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address to, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `from` to `to` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 amount) external returns (bool);
}
          

/extensions/IERC20Permit.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20Permit {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}
          

/extensions/IERC20Metadata.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.0;

import "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 *
 * _Available since v4.1._
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}
          

/ERC20.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/ERC20.sol)

pragma solidity ^0.8.0;

import "./IERC20.sol";
import "./extensions/IERC20Metadata.sol";
import "../../utils/Context.sol";

/**
 * @dev Implementation of the {IERC20} interface.
 *
 * This implementation is agnostic to the way tokens are created. This means
 * that a supply mechanism has to be added in a derived contract using {_mint}.
 * For a generic mechanism see {ERC20PresetMinterPauser}.
 *
 * TIP: For a detailed writeup see our guide
 * https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
 * to implement supply mechanisms].
 *
 * The default value of {decimals} is 18. To change this, you should override
 * this function so it returns a different value.
 *
 * We have followed general OpenZeppelin Contracts guidelines: functions revert
 * instead returning `false` on failure. This behavior is nonetheless
 * conventional and does not conflict with the expectations of ERC20
 * applications.
 *
 * Additionally, an {Approval} event is emitted on calls to {transferFrom}.
 * This allows applications to reconstruct the allowance for all accounts just
 * by listening to said events. Other implementations of the EIP may not emit
 * these events, as it isn't required by the specification.
 *
 * Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
 * functions have been added to mitigate the well-known issues around setting
 * allowances. See {IERC20-approve}.
 */
contract ERC20 is Context, IERC20, IERC20Metadata {
    mapping(address => uint256) private _balances;

    mapping(address => mapping(address => uint256)) private _allowances;

    uint256 private _totalSupply;

    string private _name;
    string private _symbol;

    /**
     * @dev Sets the values for {name} and {symbol}.
     *
     * All two of these values are immutable: they can only be set once during
     * construction.
     */
    constructor(string memory name_, string memory symbol_) {
        _name = name_;
        _symbol = symbol_;
    }

    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual override returns (string memory) {
        return _name;
    }

    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override returns (string memory) {
        return _symbol;
    }

    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual override returns (uint8) {
        return 18;
    }

    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
        return _totalSupply;
    }

    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(address account) public view virtual override returns (uint256) {
        return _balances[account];
    }

    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(address to, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _transfer(owner, to, amount);
        return true;
    }

    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(address owner, address spender) public view virtual override returns (uint256) {
        return _allowances[owner][spender];
    }

    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(address spender, uint256 amount) public virtual override returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, amount);
        return true;
    }

    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `amount`.
     */
    function transferFrom(address from, address to, uint256 amount) public virtual override returns (bool) {
        address spender = _msgSender();
        _spendAllowance(from, spender, amount);
        _transfer(from, to, amount);
        return true;
    }

    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
        address owner = _msgSender();
        _approve(owner, spender, allowance(owner, spender) + addedValue);
        return true;
    }

    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
        address owner = _msgSender();
        uint256 currentAllowance = allowance(owner, spender);
        require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
        unchecked {
            _approve(owner, spender, currentAllowance - subtractedValue);
        }

        return true;
    }

    /**
     * @dev Moves `amount` of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     */
    function _transfer(address from, address to, uint256 amount) internal virtual {
        require(from != address(0), "ERC20: transfer from the zero address");
        require(to != address(0), "ERC20: transfer to the zero address");

        _beforeTokenTransfer(from, to, amount);

        uint256 fromBalance = _balances[from];
        require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
        unchecked {
            _balances[from] = fromBalance - amount;
            // Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
            // decrementing then incrementing.
            _balances[to] += amount;
        }

        emit Transfer(from, to, amount);

        _afterTokenTransfer(from, to, amount);
    }

    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: mint to the zero address");

        _beforeTokenTransfer(address(0), account, amount);

        _totalSupply += amount;
        unchecked {
            // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
            _balances[account] += amount;
        }
        emit Transfer(address(0), account, amount);

        _afterTokenTransfer(address(0), account, amount);
    }

    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
        require(account != address(0), "ERC20: burn from the zero address");

        _beforeTokenTransfer(account, address(0), amount);

        uint256 accountBalance = _balances[account];
        require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
        unchecked {
            _balances[account] = accountBalance - amount;
            // Overflow not possible: amount <= accountBalance <= totalSupply.
            _totalSupply -= amount;
        }

        emit Transfer(account, address(0), amount);

        _afterTokenTransfer(account, address(0), amount);
    }

    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(address owner, address spender, uint256 amount) internal virtual {
        require(owner != address(0), "ERC20: approve from the zero address");
        require(spender != address(0), "ERC20: approve to the zero address");

        _allowances[owner][spender] = amount;
        emit Approval(owner, spender, amount);
    }

    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
     *
     * Does not update the allowance amount in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Might emit an {Approval} event.
     */
    function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
        uint256 currentAllowance = allowance(owner, spender);
        if (currentAllowance != type(uint256).max) {
            require(currentAllowance >= amount, "ERC20: insufficient allowance");
            unchecked {
                _approve(owner, spender, currentAllowance - amount);
            }
        }
    }

    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}

    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * has been transferred to `to`.
     * - when `from` is zero, `amount` tokens have been minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
}
          

/IERC1155.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/IERC1155.sol)

pragma solidity ^0.8.0;

import "../../utils/introspection/IERC165.sol";

/**
 * @dev Required interface of an ERC1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155 is IERC165 {
    /**
     * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

    /**
     * @dev Returns the amount of tokens of token type `id` owned by `account`.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(
        address[] calldata accounts,
        uint256[] calldata ids
    ) external view returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the caller.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external;
}
          

/extensions/IERC1155MetadataURI.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC1155/extensions/IERC1155MetadataURI.sol)

pragma solidity ^0.8.0;

import "../IERC1155.sol";

/**
 * @dev Interface of the optional ERC1155MetadataExtension interface, as defined
 * in the https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155MetadataURI is IERC1155 {
    /**
     * @dev Returns the URI for token type `id`.
     *
     * If the `\{id\}` substring is present in the URI, it must be replaced by
     * clients with the actual token type ID.
     */
    function uri(uint256 id) external view returns (string memory);
}
          

/ERC1155.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC1155/ERC1155.sol)

pragma solidity ^0.8.0;

import "./IERC1155.sol";
import "./IERC1155Receiver.sol";
import "./extensions/IERC1155MetadataURI.sol";
import "../../utils/Address.sol";
import "../../utils/Context.sol";
import "../../utils/introspection/ERC165.sol";

/**
 * @dev Implementation of the basic standard multi-token.
 * See https://eips.ethereum.org/EIPS/eip-1155
 * Originally based on code by Enjin: https://github.com/enjin/erc-1155
 *
 * _Available since v3.1._
 */
contract ERC1155 is Context, ERC165, IERC1155, IERC1155MetadataURI {
    using Address for address;

    // Mapping from token ID to account balances
    mapping(uint256 => mapping(address => uint256)) private _balances;

    // Mapping from account to operator approvals
    mapping(address => mapping(address => bool)) private _operatorApprovals;

    // Used as the URI for all token types by relying on ID substitution, e.g. https://token-cdn-domain/{id}.json
    string private _uri;

    /**
     * @dev See {_setURI}.
     */
    constructor(string memory uri_) {
        _setURI(uri_);
    }

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return
            interfaceId == type(IERC1155).interfaceId ||
            interfaceId == type(IERC1155MetadataURI).interfaceId ||
            super.supportsInterface(interfaceId);
    }

    /**
     * @dev See {IERC1155MetadataURI-uri}.
     *
     * This implementation returns the same URI for *all* token types. It relies
     * on the token type ID substitution mechanism
     * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
     *
     * Clients calling this function must replace the `\{id\}` substring with the
     * actual token type ID.
     */
    function uri(uint256) public view virtual override returns (string memory) {
        return _uri;
    }

    /**
     * @dev See {IERC1155-balanceOf}.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) public view virtual override returns (uint256) {
        require(account != address(0), "ERC1155: address zero is not a valid owner");
        return _balances[id][account];
    }

    /**
     * @dev See {IERC1155-balanceOfBatch}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(
        address[] memory accounts,
        uint256[] memory ids
    ) public view virtual override returns (uint256[] memory) {
        require(accounts.length == ids.length, "ERC1155: accounts and ids length mismatch");

        uint256[] memory batchBalances = new uint256[](accounts.length);

        for (uint256 i = 0; i < accounts.length; ++i) {
            batchBalances[i] = balanceOf(accounts[i], ids[i]);
        }

        return batchBalances;
    }

    /**
     * @dev See {IERC1155-setApprovalForAll}.
     */
    function setApprovalForAll(address operator, bool approved) public virtual override {
        _setApprovalForAll(_msgSender(), operator, approved);
    }

    /**
     * @dev See {IERC1155-isApprovedForAll}.
     */
    function isApprovedForAll(address account, address operator) public view virtual override returns (bool) {
        return _operatorApprovals[account][operator];
    }

    /**
     * @dev See {IERC1155-safeTransferFrom}.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) public virtual override {
        require(
            from == _msgSender() || isApprovedForAll(from, _msgSender()),
            "ERC1155: caller is not token owner or approved"
        );
        _safeTransferFrom(from, to, id, amount, data);
    }

    /**
     * @dev See {IERC1155-safeBatchTransferFrom}.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) public virtual override {
        require(
            from == _msgSender() || isApprovedForAll(from, _msgSender()),
            "ERC1155: caller is not token owner or approved"
        );
        _safeBatchTransferFrom(from, to, ids, amounts, data);
    }

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function _safeTransferFrom(
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) internal virtual {
        require(to != address(0), "ERC1155: transfer to the zero address");

        address operator = _msgSender();
        uint256[] memory ids = _asSingletonArray(id);
        uint256[] memory amounts = _asSingletonArray(amount);

        _beforeTokenTransfer(operator, from, to, ids, amounts, data);

        uint256 fromBalance = _balances[id][from];
        require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
        unchecked {
            _balances[id][from] = fromBalance - amount;
        }
        _balances[id][to] += amount;

        emit TransferSingle(operator, from, to, id, amount);

        _afterTokenTransfer(operator, from, to, ids, amounts, data);

        _doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function _safeBatchTransferFrom(
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");
        require(to != address(0), "ERC1155: transfer to the zero address");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, from, to, ids, amounts, data);

        for (uint256 i = 0; i < ids.length; ++i) {
            uint256 id = ids[i];
            uint256 amount = amounts[i];

            uint256 fromBalance = _balances[id][from];
            require(fromBalance >= amount, "ERC1155: insufficient balance for transfer");
            unchecked {
                _balances[id][from] = fromBalance - amount;
            }
            _balances[id][to] += amount;
        }

        emit TransferBatch(operator, from, to, ids, amounts);

        _afterTokenTransfer(operator, from, to, ids, amounts, data);

        _doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
    }

    /**
     * @dev Sets a new URI for all token types, by relying on the token type ID
     * substitution mechanism
     * https://eips.ethereum.org/EIPS/eip-1155#metadata[defined in the EIP].
     *
     * By this mechanism, any occurrence of the `\{id\}` substring in either the
     * URI or any of the amounts in the JSON file at said URI will be replaced by
     * clients with the token type ID.
     *
     * For example, the `https://token-cdn-domain/\{id\}.json` URI would be
     * interpreted by clients as
     * `https://token-cdn-domain/000000000000000000000000000000000000000000000000000000000004cce0.json`
     * for token type ID 0x4cce0.
     *
     * See {uri}.
     *
     * Because these URIs cannot be meaningfully represented by the {URI} event,
     * this function emits no events.
     */
    function _setURI(string memory newuri) internal virtual {
        _uri = newuri;
    }

    /**
     * @dev Creates `amount` tokens of token type `id`, and assigns them to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function _mint(address to, uint256 id, uint256 amount, bytes memory data) internal virtual {
        require(to != address(0), "ERC1155: mint to the zero address");

        address operator = _msgSender();
        uint256[] memory ids = _asSingletonArray(id);
        uint256[] memory amounts = _asSingletonArray(amount);

        _beforeTokenTransfer(operator, address(0), to, ids, amounts, data);

        _balances[id][to] += amount;
        emit TransferSingle(operator, address(0), to, id, amount);

        _afterTokenTransfer(operator, address(0), to, ids, amounts, data);

        _doSafeTransferAcceptanceCheck(operator, address(0), to, id, amount, data);
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_mint}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function _mintBatch(
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {
        require(to != address(0), "ERC1155: mint to the zero address");
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, address(0), to, ids, amounts, data);

        for (uint256 i = 0; i < ids.length; i++) {
            _balances[ids[i]][to] += amounts[i];
        }

        emit TransferBatch(operator, address(0), to, ids, amounts);

        _afterTokenTransfer(operator, address(0), to, ids, amounts, data);

        _doSafeBatchTransferAcceptanceCheck(operator, address(0), to, ids, amounts, data);
    }

    /**
     * @dev Destroys `amount` tokens of token type `id` from `from`
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `from` must have at least `amount` tokens of token type `id`.
     */
    function _burn(address from, uint256 id, uint256 amount) internal virtual {
        require(from != address(0), "ERC1155: burn from the zero address");

        address operator = _msgSender();
        uint256[] memory ids = _asSingletonArray(id);
        uint256[] memory amounts = _asSingletonArray(amount);

        _beforeTokenTransfer(operator, from, address(0), ids, amounts, "");

        uint256 fromBalance = _balances[id][from];
        require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
        unchecked {
            _balances[id][from] = fromBalance - amount;
        }

        emit TransferSingle(operator, from, address(0), id, amount);

        _afterTokenTransfer(operator, from, address(0), ids, amounts, "");
    }

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {_burn}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     */
    function _burnBatch(address from, uint256[] memory ids, uint256[] memory amounts) internal virtual {
        require(from != address(0), "ERC1155: burn from the zero address");
        require(ids.length == amounts.length, "ERC1155: ids and amounts length mismatch");

        address operator = _msgSender();

        _beforeTokenTransfer(operator, from, address(0), ids, amounts, "");

        for (uint256 i = 0; i < ids.length; i++) {
            uint256 id = ids[i];
            uint256 amount = amounts[i];

            uint256 fromBalance = _balances[id][from];
            require(fromBalance >= amount, "ERC1155: burn amount exceeds balance");
            unchecked {
                _balances[id][from] = fromBalance - amount;
            }
        }

        emit TransferBatch(operator, from, address(0), ids, amounts);

        _afterTokenTransfer(operator, from, address(0), ids, amounts, "");
    }

    /**
     * @dev Approve `operator` to operate on all of `owner` tokens
     *
     * Emits an {ApprovalForAll} event.
     */
    function _setApprovalForAll(address owner, address operator, bool approved) internal virtual {
        require(owner != operator, "ERC1155: setting approval status for self");
        _operatorApprovals[owner][operator] = approved;
        emit ApprovalForAll(owner, operator, approved);
    }

    /**
     * @dev Hook that is called before any token transfer. This includes minting
     * and burning, as well as batched variants.
     *
     * The same hook is called on both single and batched variants. For single
     * transfers, the length of the `ids` and `amounts` arrays will be 1.
     *
     * Calling conditions (for each `id` and `amount` pair):
     *
     * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * of token type `id` will be  transferred to `to`.
     * - When `from` is zero, `amount` tokens of token type `id` will be minted
     * for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens of token type `id`
     * will be burned.
     * - `from` and `to` are never both zero.
     * - `ids` and `amounts` have the same, non-zero length.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {}

    /**
     * @dev Hook that is called after any token transfer. This includes minting
     * and burning, as well as batched variants.
     *
     * The same hook is called on both single and batched variants. For single
     * transfers, the length of the `id` and `amount` arrays will be 1.
     *
     * Calling conditions (for each `id` and `amount` pair):
     *
     * - When `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * of token type `id` will be  transferred to `to`.
     * - When `from` is zero, `amount` tokens of token type `id` will be minted
     * for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens of token type `id`
     * will be burned.
     * - `from` and `to` are never both zero.
     * - `ids` and `amounts` have the same, non-zero length.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) internal virtual {}

    function _doSafeTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256 id,
        uint256 amount,
        bytes memory data
    ) private {
        if (to.isContract()) {
            try IERC1155Receiver(to).onERC1155Received(operator, from, id, amount, data) returns (bytes4 response) {
                if (response != IERC1155Receiver.onERC1155Received.selector) {
                    revert("ERC1155: ERC1155Receiver rejected tokens");
                }
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert("ERC1155: transfer to non-ERC1155Receiver implementer");
            }
        }
    }

    function _doSafeBatchTransferAcceptanceCheck(
        address operator,
        address from,
        address to,
        uint256[] memory ids,
        uint256[] memory amounts,
        bytes memory data
    ) private {
        if (to.isContract()) {
            try IERC1155Receiver(to).onERC1155BatchReceived(operator, from, ids, amounts, data) returns (
                bytes4 response
            ) {
                if (response != IERC1155Receiver.onERC1155BatchReceived.selector) {
                    revert("ERC1155: ERC1155Receiver rejected tokens");
                }
            } catch Error(string memory reason) {
                revert(reason);
            } catch {
                revert("ERC1155: transfer to non-ERC1155Receiver implementer");
            }
        }
    }

    function _asSingletonArray(uint256 element) private pure returns (uint256[] memory) {
        uint256[] memory array = new uint256[](1);
        array[0] = element;

        return array;
    }
}
          

/ReentrancyGuard.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant _NOT_ENTERED = 1;
    uint256 private constant _ENTERED = 2;

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

        // Any calls to nonReentrant after this point will fail
        _status = _ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = _NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == _ENTERED;
    }
}
          

/Pausable.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (security/Pausable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

    bool private _paused;

    /**
     * @dev Initializes the contract in unpaused state.
     */
    constructor() {
        _paused = false;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        require(!paused(), "Pausable: paused");
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        require(paused(), "Pausable: not paused");
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        _paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        _paused = false;
        emit Unpaused(_msgSender());
    }
}
          

/Ownable.sol

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
          

Compiler Settings

{"viaIR":true,"remappings":[],"optimizer":{"runs":1,"enabled":true},"metadata":{"bytecodeHash":"none"},"libraries":{},"evmVersion":"shanghai","compilationTarget":{"contracts/Vault.sol":"PerpetualBitcoinVault"}}
              

Contract ABI

[{"type":"constructor","stateMutability":"nonpayable","inputs":[{"type":"address","name":"_pbToken","internalType":"address"},{"type":"address","name":"_pbcToken","internalType":"address"},{"type":"address","name":"_pbtToken","internalType":"address"},{"type":"address","name":"_pbrToken","internalType":"address"},{"type":"address","name":"_pbiToken","internalType":"address"},{"type":"address","name":"_usdlToken","internalType":"address"},{"type":"address","name":"_priceFeed","internalType":"address"},{"type":"address","name":"_pulsexRouter","internalType":"address"}]},{"type":"error","name":"AlreadyLocked","inputs":[]},{"type":"error","name":"AlreadySet","inputs":[]},{"type":"error","name":"BonusAlreadyClaimed","inputs":[]},{"type":"error","name":"DuplicateUnlockId","inputs":[]},{"type":"error","name":"InsufficientBalance","inputs":[]},{"type":"error","name":"InvalidAmount","inputs":[]},{"type":"error","name":"InvalidPrice","inputs":[]},{"type":"error","name":"NotExist","inputs":[]},{"type":"error","name":"NotVLockPosition","inputs":[]},{"type":"error","name":"PasswordMismatch","inputs":[]},{"type":"error","name":"PasswordTooShort","inputs":[]},{"type":"error","name":"PriceBelowTrigger","inputs":[]},{"type":"error","name":"SlippageTooLow","inputs":[]},{"type":"error","name":"TransferFailed","inputs":[]},{"type":"error","name":"Unauthorized","inputs":[]},{"type":"error","name":"ZeroAddress","inputs":[]},{"type":"event","name":"BuyWithNetting","inputs":[{"type":"address","name":"buyer","internalType":"address","indexed":true},{"type":"address","name":"recipient","internalType":"address","indexed":true},{"type":"uint256","name":"pbtId","internalType":"uint256","indexed":true},{"type":"uint256","name":"usdlIn","internalType":"uint256","indexed":false},{"type":"uint256","name":"totalPBOut","internalType":"uint256","indexed":false},{"type":"uint256","name":"nettedPB","internalType":"uint256","indexed":false},{"type":"uint256","name":"ammPB","internalType":"uint256","indexed":false},{"type":"uint256","name":"lpPB","internalType":"uint256","indexed":false},{"type":"uint256","name":"lpUSDL","internalType":"uint256","indexed":false},{"type":"uint256","name":"unlocksNetted","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"FinalLPContribution","inputs":[{"type":"uint256","name":"pbAmount","internalType":"uint256","indexed":false},{"type":"uint256","name":"usdlAmount","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"ImmutableReferencesLocked","inputs":[{"type":"address","name":"launchConverter","internalType":"address","indexed":true},{"type":"address","name":"pulsexPair","internalType":"address","indexed":true},{"type":"address","name":"lpRemover","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"InheritanceActivated","inputs":[{"type":"uint256","name":"pbtId","internalType":"uint256","indexed":true},{"type":"address","name":"inheritanceAddress","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"InheritanceAddressSet","inputs":[{"type":"uint256","name":"pbtId","internalType":"uint256","indexed":true},{"type":"address","name":"inheritanceAddress","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"InitialLPSeeded","inputs":[{"type":"uint256","name":"usdlAmount","internalType":"uint256","indexed":false},{"type":"uint256","name":"pbAmount","internalType":"uint256","indexed":false},{"type":"uint256","name":"liquidity","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"LPContributed","inputs":[{"type":"uint256","name":"pbAmount","internalType":"uint256","indexed":false},{"type":"uint256","name":"usdlAmount","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"LPRewardsHarvested","inputs":[{"type":"uint256","name":"usdlAmount","internalType":"uint256","indexed":false},{"type":"uint256","name":"pbAmount","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"PBtBurned","inputs":[{"type":"uint256","name":"pbtId","internalType":"uint256","indexed":true},{"type":"uint256","name":"remainingDust","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"PhaseTransitioned","inputs":[{"type":"bool","name":"wasDistribution","internalType":"bool","indexed":false},{"type":"uint256","name":"vaultPBBalance","internalType":"uint256","indexed":false},{"type":"uint256","name":"totalOutstandingPBc","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"PresaleAllocated","inputs":[{"type":"address","name":"user","internalType":"address","indexed":true},{"type":"uint256","name":"pbtId","internalType":"uint256","indexed":true},{"type":"uint256","name":"pbAmount","internalType":"uint256","indexed":false},{"type":"uint256","name":"buyPrice","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"RecoveryActivated","inputs":[{"type":"uint256","name":"pbtId","internalType":"uint256","indexed":true},{"type":"address","name":"recoveryAddress","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"RecoveryAddressSet","inputs":[{"type":"uint256","name":"pbtId","internalType":"uint256","indexed":true},{"type":"address","name":"recoveryAddress","internalType":"address","indexed":true}],"anonymous":false},{"type":"event","name":"UnlockNetted","inputs":[{"type":"uint256","name":"pbtId","internalType":"uint256","indexed":true},{"type":"uint256","name":"unlockIndex","internalType":"uint256","indexed":false},{"type":"uint256","name":"pbcSettled","internalType":"uint256","indexed":false},{"type":"uint256","name":"usdlPaid","internalType":"uint256","indexed":false},{"type":"address","name":"payoutAddress","internalType":"address","indexed":false},{"type":"uint256","name":"settlementPrice","internalType":"uint256","indexed":false},{"type":"uint256","name":"newTriggerPrice","internalType":"uint256","indexed":false},{"type":"uint256","name":"remainingPBcLocked","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"UnlockTriggered","inputs":[{"type":"uint256","name":"pbtId","internalType":"uint256","indexed":true},{"type":"uint256","name":"unlockIndex","internalType":"uint256","indexed":false},{"type":"uint256","name":"pbUnlocked","internalType":"uint256","indexed":false},{"type":"uint256","name":"usdlProceeds","internalType":"uint256","indexed":false},{"type":"address","name":"payoutAddress","internalType":"address","indexed":false},{"type":"uint256","name":"newTriggerPrice","internalType":"uint256","indexed":false},{"type":"uint256","name":"remainingPBcLocked","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"VLockBonusPaid","inputs":[{"type":"address","name":"user","internalType":"address","indexed":true},{"type":"uint256","name":"usdlAmount","internalType":"uint256","indexed":false},{"type":"uint256","name":"pbAmount","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"event","name":"VLockExecuted","inputs":[{"type":"address","name":"user","internalType":"address","indexed":true},{"type":"uint256","name":"pbtId","internalType":"uint256","indexed":true},{"type":"uint256","name":"pbAmount","internalType":"uint256","indexed":false},{"type":"uint256","name":"usdlBonusPaid","internalType":"uint256","indexed":false},{"type":"uint256","name":"pbBonusPaid","internalType":"uint256","indexed":false}],"anonymous":false},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"AMM_BUY_BPS","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"DEPLOYER","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"DUST_CHECK_THRESHOLD","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"DUST_THRESHOLD","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"FOUNDER_TOTAL","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"HARVEST_INTERVAL","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"INITIAL_TARGET_PRICE","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"LAUNCH_CONVERTER","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"LP_ALLOCATION","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"LP_CONTRIBUTION_BPS","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"LP_REMOVER","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"MAX_UNLOCK_PER_BUY","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"MIN_TWAP_WINDOW","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"MIN_VLOCK_BONUS_USDL","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"PBC_TOKEN","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"PBC_TOTAL_SUPPLY","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"PBI_TOKEN","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"PBR_TOKEN","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"PBT_TOKEN","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"PB_TOKEN","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"PB_TOTAL_SUPPLY","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"PCT_DENOM","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"PRESALE_TOTAL","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"PRICE_FEED","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"PULSEX_PAIR","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"PULSEX_ROUTER","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"REMAINING_OPERATIONAL","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"TRANCHE_FRACTION","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"UNLOCK_DIVISOR","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"UNLOCK_MULTIPLIER","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"","internalType":"address"}],"name":"USDL_TOKEN","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"VLOCK_BONUS_PCT","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"accumulatedFeesPB","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"accumulatedFeesUSDL","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"activateInheritance","inputs":[{"type":"uint256","name":"pbtId","internalType":"uint256"},{"type":"string","name":"password","internalType":"string"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"activateRecovery","inputs":[{"type":"uint256","name":"pbtId","internalType":"uint256"},{"type":"string","name":"password","internalType":"string"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"activePositionCount","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"allocatePresaleUser","inputs":[{"type":"address","name":"user","internalType":"address"},{"type":"uint256","name":"pbAmount","internalType":"uint256"},{"type":"uint256","name":"entryPrice","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"buyCount","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"pbtId","internalType":"uint256"}],"name":"buyPBDirect","inputs":[{"type":"uint256","name":"usdlAmount","internalType":"uint256"},{"type":"uint256","name":"minPBOut","internalType":"uint256"},{"type":"address","name":"recipient","internalType":"address"},{"type":"uint256[]","name":"unlockIds","internalType":"uint256[]"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"usdlPaid","internalType":"uint256"},{"type":"uint256","name":"pbPaid","internalType":"uint256"}],"name":"claimLPFeesFor","inputs":[{"type":"uint256","name":"pbtId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"executeUnlock","inputs":[{"type":"uint256","name":"pbtId","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"pbAmount","internalType":"uint256"},{"type":"uint256","name":"usdlAmount","internalType":"uint256"}],"name":"forwardLPTokensToUser","inputs":[{"type":"address","name":"recipient","internalType":"address"},{"type":"uint256","name":"amount0","internalType":"uint256"},{"type":"uint256","name":"amount1","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"harvestLPRewards","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"immutableReferencesLocked","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"inheritanceAddress","internalType":"address"},{"type":"bytes32","name":"passwordHash","internalType":"bytes32"},{"type":"bool","name":"activated","internalType":"bool"}],"name":"inheritanceRegistry","inputs":[{"type":"uint256","name":"","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"initialLPSeeded","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"isDistributionPhase","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"isVLockPosition","inputs":[{"type":"uint256","name":"","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"lastKPerLP","inputs":[]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"lockImmutableReferences","inputs":[{"type":"address","name":"_launchConverter","internalType":"address"},{"type":"address","name":"_pulsexPair","internalType":"address"},{"type":"address","name":"_lpRemover","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"pbtIdCounter","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"buyPrice","internalType":"uint256"},{"type":"uint256","name":"pbAmount","internalType":"uint256"},{"type":"uint256","name":"pbcLocked","internalType":"uint256"},{"type":"uint256","name":"nextUnlockIndex","internalType":"uint256"},{"type":"uint256","name":"nextTriggerPrice","internalType":"uint256"},{"type":"uint256","name":"mintBlock","internalType":"uint256"},{"type":"address","name":"holder","internalType":"address"},{"type":"address","name":"payoutAddress","internalType":"address"}],"name":"pbtRegistry","inputs":[{"type":"uint256","name":"","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"phaseTransitionExecuted","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"address","name":"recoveryAddress","internalType":"address"},{"type":"bytes32","name":"passwordHash","internalType":"bytes32"},{"type":"bool","name":"activated","internalType":"bool"}],"name":"recoveryRegistry","inputs":[{"type":"uint256","name":"","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"amountA","internalType":"uint256"},{"type":"uint256","name":"amountB","internalType":"uint256"},{"type":"uint256","name":"liquidity","internalType":"uint256"}],"name":"seedInitialLP","inputs":[{"type":"uint256","name":"usdlAmount","internalType":"uint256"},{"type":"uint256","name":"minPB","internalType":"uint256"},{"type":"uint256","name":"minUSDL","internalType":"uint256"},{"type":"uint256","name":"deadline","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setInheritanceAddress","inputs":[{"type":"uint256","name":"pbtId","internalType":"uint256"},{"type":"address","name":"inheritanceAddr","internalType":"address"},{"type":"bytes32","name":"passwordHash","internalType":"bytes32"},{"type":"string","name":"message","internalType":"string"}]},{"type":"function","stateMutability":"nonpayable","outputs":[],"name":"setRecoveryAddress","inputs":[{"type":"uint256","name":"pbtId","internalType":"uint256"},{"type":"address","name":"recoveryAddr","internalType":"address"},{"type":"bytes32","name":"passwordHash","internalType":"bytes32"},{"type":"string","name":"message","internalType":"string"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"totalUSDLDistributed","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"userPBtIds","inputs":[{"type":"address","name":"","internalType":"address"},{"type":"uint256","name":"","internalType":"uint256"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"userPBtIdsCount","inputs":[{"type":"address","name":"user","internalType":"address"}]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"vaultLPTokenBalance","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"vaultPBBalance","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"vaultPBcBalance","inputs":[]},{"type":"function","stateMutability":"view","outputs":[{"type":"bool","name":"","internalType":"bool"}],"name":"vlockBonusClaimed","inputs":[{"type":"uint256","name":"","internalType":"uint256"}]},{"type":"function","stateMutability":"nonpayable","outputs":[{"type":"uint256","name":"","internalType":"uint256"}],"name":"voluntaryLock","inputs":[{"type":"uint256","name":"pbAmount","internalType":"uint256"}]},{"type":"receive","stateMutability":"payable"}]
              

Contract Creation Code

Verify & Publish
0x6101a034620003b857601f6200611838819003918201601f19168301916001600160401b03831184841017620003bc578084926040948552833961010093849181010312620003b8576200005381620003d0565b906200006260208201620003d0565b6200006f848301620003d0565b936200007e60608401620003d0565b6200008c60808501620003d0565b956200009b60a08601620003d0565b93620000b860e0620000b060c08901620003d0565b9701620003d0565b9660015f556a115eec47f6cf7e35000000600490808255600555600162ffffff1960095416176009555f600a553360805260018060a01b0380831615620003a95780841615620003a95780851615620003a95780861615620003a957808b1615620003a95780881615620003a95780891615620003a9578916156200039b575060a05260c05260e05286526101209485526101409182526101609283526101809384525193615d329586620003e68739608051868181610f3e0152611809015260a05186818161067e0152818161070601528181611890015281816119d001528181612fc40152818161305001528181613cd201528181614163015281816146660152818161487601528181614adc015281816156be0152615a40015260c051868181610400015281816109cc0152818161145a0152818161169f0152818161230c01528181612756015281816142010152614cec015260e05186818161044c01528181610aa001528181610af201528181610bd801528181610ce5015281816111000152818161149c01528181611fd6015281816120f4015281816122080152818161225b015281816124070152818161253a01528181612642015281816134e9015281816135e70152818161436b01528181614dc001528181614e1101528181614f0801528181614f52015261504201525185818161049f015281816110a10152611b110152518481816114ef0152818161331d015261348a01525183818161078e01528181610a4801528181611c860152818161238b015281816127d901528181612e550152818161302f0152818161364d0152818161473a01528181614a4f01528181614a9f01528181614d68015281816152790152818161533a015281816157350152615a860152518281816108ff01528181611978015281816133bf0152614516015251818181610efa01528181612f4601528181612f970152818161307d01528181614a2201528181614b6c015281816156930152615a150152f35b855163d92e233d60e01b8152fd5b50855163d92e233d60e01b8152fd5b5f80fd5b634e487b7160e01b5f52604160045260245ffd5b51906001600160a01b0382168203620003b85756fe61016080604052600436101561001d575b50361561001b575f80fd5b005b5f3560e01c908163017d12691461374457508063083131f1146137155780630a9f88a6146136d05780630d9a34a2146136b55780630ea1df6f146136985780630ee5215c1461367c5780630f093fb81461363857806313f9243a1461361657806315420018146135d25780631a65893b146135b65780631db6fbd71461358e5780631f6f91c414611ada5780632f138c0a146133ee57806331eb318a146133aa5780633a0d171a146133855780633a8425d6146133685780633b7238e11461334c5780633b7d6d0b146133085780634097f54714612e03578063413a548c14611bc1578063429858e214611b4057806345a3042514611afc57806348423de514611adf5780634a1d97cc14611ada5780634aff6483146119395780634c519cf9146117ce578063515bce08146117b15780635c9707eb146117955780635e7761bb14611770578063722050801461174c57806378b022e81461172f5780637ac7a3d5146117075780637c8acb88146116ea5780637f046db4146116ce5780637f108e0a1461168a5780638004f9ad1461166f57806380d7624e146115b65780638781fbc91461137a578063880896241461132357806392f3c6411461127a5780639f56bf5314611258578063a3376a1b14611230578063a902dd72146111f8578063b237d2be146111d5578063b8357a8d14611001578063ba48ab7814610fb8578063babec6e814610f9c578063bf9b240114610f6d578063c1b8411a14610f29578063c687782214610ee5578063c69d9e3c14610ebd578063ca70307514610ea0578063cabe7eff14610e83578063ce75e84314610e66578063dbb97afa14610e49578063dc9dcaf7146108a3578063dd7dc9a914610888578063df8579381461086d578063e8e72ca614610850578063e99d0863146106ad578063eb4a659814610669578063ebd3687d14610644578063ebda16eb1461061f578063ec8a903014610603578063ed78a98b146105de5763ef598e49146102ff575f610010565b346105655761030d3661388f565b610318929192613ad8565b5f90825f52602093600e855260405f209460018060a01b039283875416968733036105cd57600281019182549460ff86166105bb57600b82106105a95760019161037a8660405183819483830196873781015f8382015203808452018261394c565b519020910154036105975760ff199092166001179091555f848152600b8252604090206006810180546007830180546001600160a01b03199081168a17909155811688179091556103fa929084169181906002906103d98a868b61551c565b0154809360405195869283926323b872dd60e01b8452309060048501613a89565b03815f887f0000000000000000000000000000000000000000000000000000000000000000165af192831561055a5761044993610569575b505061044081600554613a32565b600555856141c2565b807f000000000000000000000000000000000000000000000000000000000000000016803b15610565575f8091602460405180948193636af39aa360e01b83528960048401525af1801561055a57610547575b507f000000000000000000000000000000000000000000000000000000000000000016803b1561054357816040518092632770a7eb60e21b82528183816104e78933600484016139b8565b03925af1801561053857610521575b50807ffb33a7af0d1bab03646542c2475651411e9d8add57afa25f63e67d6c13103e0e91a360015f55005b61052b829161391e565b610535575f6104f6565b80fd5b6040513d84823e3d90fd5b5080fd5b61055291925061391e565b5f905f61049c565b6040513d5f823e3d90fd5b5f80fd5b8161058892903d10610590575b610580818361394c565b8101906139a0565b505f80610432565b503d610576565b604051631b93fb8b60e31b8152600490fd5b6040516307540a0f60e31b8152600490fd5b60405163a741a04560e01b8152600490fd5b6040516282b42960e81b8152600490fd5b34610565575f36600319011261056557602060ff60095460081c166040519015158152f35b34610565575f366003190112610565576020604051610e6a8152f35b34610565575f3660031901126105655760206040516a017d2a320dd745550000008152f35b34610565575f3660031901126105655760206040516a0b4a7ffb93a0786f4000008152f35b34610565575f366003190112610565576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610565576106bb36613865565b600354919290916001600160a01b039190821633036105cd57816001541690604051948592630dfe168160e01b845283600460209889935afa801561055a5784935f91610823575b507f000000000000000000000000000000000000000000000000000000000000000084169416840361081d5793925b604051868163a9059cbb60e01b93848252815f816107548c8a600484016139b8565b03925af190811561055a575f91610800575b50156107d15761078a9286925f8693604051968795869485938452600484016139b8565b03927f0000000000000000000000000000000000000000000000000000000000000000165af190811561055a575f916107e3575b50156107d1576040928351928352820152f35b6040516312171d8360e31b8152600490fd5b6107fa9150843d861161059057610580818361394c565b846107be565b6108179150873d891161059057610580818361394c565b87610766565b92610732565b6108439150873d8911610849575b61083b818361394c565b810190613ab9565b87610703565b503d610831565b34610565575f366003190112610565576020600654604051908152f35b34610565575f366003190112610565576020604051600a8152f35b34610565575f366003190112610565576020604051600f8152f35b34610565576020366003190112610565576108bc613ad8565b6004355f908152600b6020526040812060068101549091906001600160a01b031615610e375760038201546040516375c8e9bf60e11b81529092906020816004817f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165afa90811561055a575f91610e05575b50600482015411610df357600281015491600f831015610dda576002820154925f60028401555b60018060a01b0360068401541691826004355f52600f6020528560ff600260405f200154165f14610da15750506004355f52600f6020526109c760208660018060a01b0360405f205416955b6040516323b872dd60e01b81529384928392309060048501613a89565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1801561055a57610d82575b50610a0e85600554613a32565b600555610a1a85615655565b91610a248661403d565b60405163a9059cbb60e01b815260208180610a438789600484016139b8565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af190811561055a575f91610d63575b50156107d157610a9283600754613a32565b600755600f811015610cde577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163b1561054357604051637ad80e6360e01b81526004803590820152602481018790528281604481837f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af18015610cd357908391610cbf575b50505b600f808210159182610c98575b1090610c8a575b610b9c575b50610b4f614418565b60026004840154930154936040519586526020860152604085015260018060a01b03166060840152608083015260a08201525f80516020615cc683398151915260c060043592a260015f55005b6006840154610bb6906001600160a01b031660043561545c565b6006840180546001600160a01b031990811690915560078501805490911690557f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316803b15610543578180916024604051809481936311cead4b60e31b835260043560048401525af1801561053857610c76575b5050600d5480610c64575b5060028301546040519081525f80516020615ce6833981519152602060043592a285610b46565b610c6d90614808565b600d5585610c3d565b610c80829161391e565b6105355780610c32565b50600a600285015410610b41565b610cb5610ca86003890154613aab565b8060038a01558854615404565b6004880155610b3a565b610cc89061391e565b610543578188610b2a565b6040513d85823e3d90fd5b60048501547f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316803b1561056557610d3b915f918983604051809681958294631e45ec4360e31b84526004356004850161384f565b03925af1801561055a57610d50575b50610b2d565b610d5b91925061391e565b5f9087610d4a565b610d7c915060203d60201161059057610580818361394c565b88610a80565b610d9a9060203d60201161059057610580818361394c565b5086610a01565b9093600e60205260405f2060ff600282015416610dc6575b506109c7916020916109aa565b546001600160a01b031694506109c7610db9565b6003830492610de98482613b2c565b600284015561095e565b60405163582515d760e11b8152600490fd5b90506020813d602011610e2f575b81610e206020938361394c565b81010312610565575184610937565b3d9150610e13565b60405163ad5679e160e01b8152600490fd5b34610565575f366003190112610565576020601554604051908152f35b34610565575f366003190112610565576020601354604051908152f35b34610565575f366003190112610565576020600554604051908152f35b34610565575f366003190112610565576020600854604051908152f35b34610565575f366003190112610565576001546040516001600160a01b039091168152602090f35b34610565575f366003190112610565576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610565575f366003190112610565576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610565576020366003190112610565576004355f526010602052602060ff60405f2054166040519015158152f35b34610565575f3660031901126105655760206040516101f48152f35b34610565576020366003190112610565576004355f52600e60205260405f2060018060a01b03815416610ffd60ff600260018501549401541660405193849384613763565b0390f35b3461056557600661101136613804565b61101f959195939293613ad8565b845f52600b60205260405f2060018060a01b0392839101541633036105cd578183169586156111c35784156111b1575f94865f52600e60205260405f2060ff6002820154166105bb575484166105bb5760405161109e9161107f82613931565b89825260208201525f6040820152875f52600e60205260405f20613faf565b827f00000000000000000000000000000000000000000000000000000000000000001691823b15610565576110ee925f92836040518096819582946334ff261960e21b84528d8c60048601613fea565b03925af1801561055a5761119e575b507f00000000000000000000000000000000000000000000000000000000000000001690813b1561119a5761114c8392839260405194858094819363574d799f60e11b83528a60048401614024565b03925af1801561053857611186575b50807f1c159cfd96672eca794ae8139b8b5d0d2c4363aaeddd3fa7df0a626daff3a38e91a360015f55005b611190829161391e565b610535578361115b565b8280fd5b6111a991935061391e565b5f91856110fd565b60405163162908e360e11b8152600490fd5b60405163d92e233d60e01b8152600490fd5b34610565575f36600319011261056557602060405168056bc75e2d631000008152f35b34610565576020366003190112610565576001600160a01b036112196137c1565b165f52600c602052602060405f2054604051908152f35b34610565575f366003190112610565576002546040516001600160a01b039091168152602090f35b34610565575f36600319011261056557602060ff601254166040519015158152f35b3461056557602036600319011261056557600435611296613ad8565b5f818152600b60205260409020600601546001600160a01b03168015610e3757815f52601060205260ff60405f2054161561131257815f52601160205260ff60405f2054166113005733036105cd576112f0604091614499565b60015f5582519182526020820152f35b604051630e50c52360e21b8152600490fd5b604051623c461f60e31b8152600490fd5b346105655760403660031901126105655761133c6137c1565b6001600160a01b03165f908152600c60205260409020805460243591908210156105655760209161136c916138be565b90546040519160031b1c8152f35b34610565576113883661388f565b611393929192613ad8565b5f90825f52602093600f855260405f209460018060a01b039283875416968733036105cd57600281019182549460ff86166105bb57600b82106105a9576001916113f58660405183819483830196873781015f8382015203808452018261394c565b519020910154036105975760ff199092166001179091555f848152600b8252604090206006810180546007830180546001600160a01b03199081168a1790915581168817909155611454929084169181906002906103d98a868b61551c565b03815f887f0000000000000000000000000000000000000000000000000000000000000000165af192831561055a576114999361159857505061044081600554613a32565b807f000000000000000000000000000000000000000000000000000000000000000016803b15610565575f80916024604051809481936301ac002160e51b83528960048401525af1801561055a57611585575b507f000000000000000000000000000000000000000000000000000000000000000016803b1561054357816040518092632770a7eb60e21b82528183816115378933600484016139b8565b03925af1801561053857611571575b50807fa2107f39e66e914a571170508240a4ad588a19b13668f6b1a085924e9cc752a491a360015f55005b61157b829161391e565b6105355783611546565b61159091925061391e565b5f90846114ec565b816115ae92903d1061059057610580818361394c565b508680610432565b34610565576115c436613865565b91906115ce613ad8565b6002546001600160a01b0390811633036105cd5782169182156111c35781156111b157831561165e5781611644856116358360409561162d7f49da8a165df89c4dc7a071ab41d505985b3a3351edbbf5aada3ba64e16f2b3709861410d565b939082614133565b61163f83826141c2565b61427d565b9461164d614418565b82519182526020820152a360015f55005b60405162bfc92160e01b8152600490fd5b34610565575f36600319011261056557602060405160038152f35b34610565575f366003190112610565576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610565575f3660031901126105655760206040516115b38152f35b34610565575f366003190112610565576020600754604051908152f35b34610565575f366003190112610565576003546040516001600160a01b039091168152602090f35b34610565575f366003190112610565576020600454604051908152f35b34610565575f366003190112610565576020604051694e59bd7d27fbc34000008152f35b34610565575f3660031901126105655760206040516a0497421a5557c070c000008152f35b34610565575f366003190112610565576020604051613cc38152f35b34610565575f366003190112610565576020604051620186a08152f35b34610565576060366003190112610565576117e76137c1565b6024356001600160a01b0381811691829003610565576118056137ab565b90807f00000000000000000000000000000000000000000000000000000000000000001633036105cd5760ff60095460101c1661192757600254938185166105bb57600154908282166105bb5782169283156111c35784156111c35782169485156111c3576001600160a01b03199081168417600255908116841760015560038054909116851790557f000000000000000000000000000000000000000000000000000000000000000016803b15610565575f8091602460405180948193635116a41960e11b83528860048401525af1801561055a57611918575b506201000062ff00001960095416176009557f0a6d7e5d0a33f7178bd67452a2e7a76f4ed9b05f686a67be9668091c104a24075f80a4005b6119219061391e565b836118e0565b6040516317c3335f60e21b8152600490fd5b346105655760208060031936011261056557600435611956613ad8565b80156111b1576040516375c8e9bf60e11b81526001600160a01b0383826004817f000000000000000000000000000000000000000000000000000000000000000085165afa91821561055a575f92611aab575b508360405180926323b872dd60e01b8252815f816119cc89303360048501613a89565b03927f0000000000000000000000000000000000000000000000000000000000000000165af190811561055a575f91611a8e575b50156107d157611a2b90611a1683600454613a32565b600455611a2383336141c2565b82803361427d565b8091815f526010845260405f20600160ff198254161790557f276b7740d8e50775651f99441ea05a1838a931d5611055cd11bacf039c35d7b6611a6d83614499565b90611a7f60405192839233968461384f565b0390a360015f55604051908152f35b611aa59150843d861161059057610580818361394c565b84611a00565b9091508381813d8311611ad3575b611ac3818361394c565b81010312610565575190846119a9565b503d611ab9565b613786565b34610565575f366003190112610565576020600d54604051908152f35b34610565575f366003190112610565576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610565576020366003190112610565576004355f52600b60205261010060405f208054906001810154906002810154600382015460048301549060058401549260018060a01b036007816006880154169601541695604051978852602088015260408701526060860152608085015260a084015260c083015260e0820152f35b3461056557608036600319011261056557611bda6137ab565b6001600160401b036064351161056557366023606435011215610565576001600160401b036004606435013511610565573660246064356004013560051b60643501011161056557611c2a613ad8565b6001600160a01b03811615612dfc575b600435156111b157602435156111b15733156111c3576101f460643560040135116111b1576040516323b872dd60e01b815260208180611c81600435303360048501613a89565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af190811561055a575f91612ddd575b50156107d157611ccf600854613aab565b600855604051906001600160401b0360c0830190811190831117612dc95760c082016040525f82525f60208301525f60408301525f60608301525f60808301525f60a0830152611d1d614814565b80670de0b6b3a7640000810204670de0b6b3a76400001481151715612bfd57611d5082670de0b6b3a76400008302613982565b84525f60c052611d6560643560040135614926565b60a052611d7760643560040135614926565b5f5b606435600401358110612c745750505f610140525f610100525f905f8061014052604051611da6816138e7565b5f81525f60208201525f60408201525f60608201525f6080820152905f92611dcf60c05161490f565b95611ddd604051978861394c565b60c0518752601f19611df060c05161490f565b015f5b818110612c5d57505060c051610100526004356080525f610120525b60c05161012051106128e6575b5050611e266140a0565b945f5b8581106122a557505090611e519291610100519260c0519260a051926101405160043561498e565b60808901526060880152604087015260e0840151611f75575b50611ea4925090611e82611e99926020870151613a32565b9081602087015260a0860152604085015190613a32565b608084015190613a32565b916024358310611f635782611edc611ec8611ec060209661410d565b919086614133565b611ed281866141c2565b835190838661427d565b928392611ee7614418565b8581015190608081015160408201519060a06060840151930151936040519660043588528a88015260408701526060860152608085015260a084015260c083015260018060a01b0316907f9c7bfec5ca88127b0c60e4320ac0b9ddc9f8365d5b2ab14ca209dc3d458a3fad60e03392a460015f55604051908152f35b6040516329b6a4e960e21b8152600490fd5b611f949060408501611f88848251613a32565b90526060850151613a32565b606084015282515f52600b60205260405f20926003840154611fbe60208301516002870154613b2c565b600286015560c0820151156121f857815160208301517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169190823b156105655760445f92836040519586948593637ad80e6360e01b8552600485015260248401525af1801561055a576121e9575b505b60c0820151156121c2575b60c0820151158015906121b4575b6120b8575b91815f80516020615ca6833981519152611ea496611e9996956120ad611e82965194602081015193606082015191608060018060a01b0360a08301511691015190600260048401549301549360405197889788614958565b0390a2919250611e6a565b929181516120d4600687019160018060a01b038354169061545c565b80546001600160a01b0319908116909155600786018054909116905581517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169290833b15610565575f936024859260405196879384926311cead4b60e31b845260048401525af1801561055a57611ea496611e9996611e82955f80516020615ca6833981519152936121a5575b50612176600d54614808565b600d5583515f80516020615ce683398151915260206002850154604051908152a2939596509650509150612055565b6121ae9061391e565b8b61216a565b50600a600286015410612050565b6121df6121d26003870154613aab565b8060038801558654615404565b6004860155612042565b6121f29061391e565b87612035565b81516020830151608084015191907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163b15610565575f916122566040519485938493631e45ec4360e31b85526004850161384f565b0381837f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1801561055a57612296575b50612037565b61229f9061391e565b87612290565b60e06122b182846140f9565b5101516126f7576122c281836140f9565b519081515f52600b60205260405f2091602081015161230760208260018060a01b0360068801541660405193849283926323b872dd60e01b8452309060048501613a89565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1801561055a576126d8575b5061234e81600554613a32565b60055560a0820151608083015160405163a9059cbb60e01b81529160209183918291612386916001600160a01b0316600484016139b8565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af190811561055a575f916126b9575b50156107d1576123d7906002850154613b2c565b60028401556123ec6080820151600754613a32565b600755600383015460c08201511561263157815160208301517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169190823b156105655760445f92836040519586948593637ad80e6360e01b8552600485015260248401525af1801561055a57612622575b505b60c0820151156125fb575b60c0820151158015906125ed575b6124fe575b6001935f80516020615ca6833981519152916124d384519460208101519360808201519160608a8060a01b0360a08301511691015190600260048401549301549360405197889788614958565b0390a26124f260206124e583866140f9565b51015160208c0151613a32565b60208b01525b01611e29565b612516825160018060a01b036006870154169061545c565b6006840180546001600160a01b0319908116909155600785018054909116905581517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169490853b15610565575f956024879260405198899384926311cead4b60e31b845260048401525af191821561055a576001955f80516020615ca6833981519152936125de575b50600d6125b58154614808565b905583515f80516020615ce683398151915260206002850154604051908152a291509350612486565b6125e79061391e565b8e6125a8565b50600a600285015410612481565b61261861260b6003860154613aab565b8060038701558554615404565b6004850155612473565b61262b9061391e565b8c612466565b8151602083015160608401519091907f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316803b1561056557612695935f809460405196879586948593631e45ec4360e31b85526004850161384f565b03925af1801561055a576126aa575b50612468565b6126b39061391e565b8c6126a4565b6126d2915060203d60201161059057610580818361394c565b8d6123c3565b6126f09060203d60201161059057610580818361394c565b508c612341565b955061270386826140f9565b5161270c6140a0565b5080515f908152600b6020908152604091829020600601548284015192516323b872dd60e01b815292839182916127519130906001600160a01b031660048501613a89565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1801561055a576128c7575b5061279c6040820151600554613a32565b60055560a0810151608082015160405163a9059cbb60e01b815291602091839182916127d4916001600160a01b0316600484016139b8565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af190811561055a575f916128a8575b50156107d157806080600192015161282c6007918254613a32565b90558051906040810151906020810151608082015160608301519160c0878060a01b0360a0860151169401511515946040519661286888613902565b8752602087015260408601526060850152608084015260a083015260c08201528160e08201529661289e60206124e583866140f9565b60208b01526124f8565b6128c1915060203d60201161059057610580818361394c565b8a612811565b6128df9060203d60201161059057610580818361394c565b508961278b565b90946128f76101205160a0516140f9565b515f818152600b6020526040902060068101546001600160a01b0316158015612c51575b612c4957600481015494612930868a8761581f565b95600283015492600f84105f14612c3f5783925b670de0b6b3a7640000612957848661396f565b0460e052886080511080159081612c29575b612a7e57506129a69061298961298160e0518c613a32565b608051613b2c565b6080526129998a61014051613a32565b6101405260e05190613a32565b9780612a41575b5060070154612a2e959493600f93909290916129d2906001600160a01b031686615939565b91604051956129e087613902565b86528060208701526040860152606085015260e051608085015260018060a01b031660a08401521060c08201525f60e0820152612a1d828a6140f9565b52612a2881896140f9565b50613aab565b945b600161012051016101205290611e0f565b916129d2612a708d612a6a8b9f96612a64600f99979d612a2e9d9c9b99836158f2565b92613a32565b9e613b2c565b9892945050919394956129ad565b9b939a9196949b999098929795995f14612c11575050600161012051016101205111612bfd57612b7793600161012051016101005281612bb5575b505060e051159050612baf57612adc60e051612ad76080518a61396f565b613982565b600f612ae8828a613b2c565b99612b1c612b12612afd60805160e051613b2c565b6007909801546001600160a01b03168b615939565b9860805190613a32565b9960405193612b2a85613902565b8a8552602085015260408401526060830187905260808051908401526001600160a01b03881660a08401521060c0820152600160e0820152612b6c828b6140f9565b52612a28818a6140f9565b9560405194612b85866138e7565b85526020850152604084015260608301526001600160a01b031660808201526001915b8780611e1c565b5f612adc565b8281612bd284612bcc612bd895612bf198836158f2565b93613a32565b50613b2c565b50612be581608051613b2c565b60805261014051613a32565b610140528b8080612ab9565b634e487b7160e01b5f52601160045260245ffd5b98509850505096505050506101205161010052612ba8565b5060e051612c398b608051613b2c565b10612969565b6003840492612944565b505094612a30565b5060028101541561291b565b602090612c686140a0565b82828c01015201611df3565b606435600582901b01602401355f908152600b6020526040902060068101546001600160a01b0316158015612dbd575b612d84575f805b60c0518110612d8d575b50612d8457600401549060c05191825b612d05575b612cef60019360248460051b606435010135612ce88260a0516140f9565b52856140f9565b52612cfb60c051613aab565b60c0525b01611d79565b5f19830192808411612bfd57612d28612d208560a0516140f9565b5194866140f9565b51828110612d685782811480612d70575b612d6857612d60929394612d4f8360a0516140f9565b52612d5a82876140f9565b52614808565b919082612cc5565b509250612cca565b5060248460051b6064350101358510612d39565b50600190612cff565b60248460051b606435010135612da58260a0516140f9565b5114612db357600101612cab565b5050600188612cb5565b50600281015415612ca4565b634e487b7160e01b5f52604160045260245ffd5b612df6915060203d60201161059057610580818361394c565b82611cbe565b5033611c3a565b3461056557608036600319011261056557612e1c613ad8565b6002546001600160a01b031633036105cd5760ff601254166105bb57600435156111b1576040516370a0823160e01b81523060048201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690602081602481855afa90811561055a575f916132d6575b50600435116132c457670de0b6b3a7640000600435026004358104670de0b6b3a764000003612bfd5766c55a6e4145e0008104156111b15766c55a6e4145e0008104600454106132c457605f66c55a6e4145e00082040266c55a6e4145e00082048104605f03612bfd576064900460243510611f6357605f600435026004358104605f03612bfd576064900460443510611f635760405160208163095ea7b360e01b94858252815f81612f6e6004357f0000000000000000000000000000000000000000000000000000000000000000600484016139b8565b03925af1801561055a576132a5575b5060405191825260208280612fbf66c55a6e4145e00085047f0000000000000000000000000000000000000000000000000000000000000000600484016139b8565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af190811561055a5761307892606092613286575b506040518093819262e8e33760e81b8352606435903090604435906024359066c55a6e4145e00060043591047f00000000000000000000000000000000000000000000000000000000000000007f000000000000000000000000000000000000000000000000000000000000000060048a016139ee565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1801561055a575f905f925f91613251575b506130c28261403d565b600160ff1960125416176012556130db81600654613a32565b600655600154604051630240bc6b60e21b81526001600160a01b039091169190606081600481865afa801561055a576004935f925f92613218575b50602090604051958680926318160ddd60e01b82525afa93841561055a575f946131e4575b508361318d575b5050610ffd91507f2c4c1392b0b55915ca15a44ebd78091450f67f2a338fe85e3e3b4c64b53003076040518061317a8487898461384f565b0390a160015f556040519384938461384f565b6131ac916131a7916001600160701b03908116911661396f565b614056565b670de0b6b3a7640000810290808204670de0b6b3a76400001490151715612bfd57610ffd926131da91613982565b6015558480613142565b9093506020813d602011613210575b816132006020938361394c565b810103126105655751928661313b565b3d91506131f3565b6020935061323f91925060603d60601161324a575b613237818361394c565b810190613a53565b509290929190613116565b503d61322d565b915050613276915060603d60601161327f575b61326e818361394c565b8101906139d3565b919091836130b8565b503d613264565b61329e9060203d60201161059057610580818361394c565b5083613001565b6132bd9060203d60201161059057610580818361394c565b5082612f7d565b604051631e9acf1760e31b8152600490fd5b90506020813d602011613300575b816132f16020938361394c565b81010312610565575182612e95565b3d91506132e4565b34610565575f366003190112610565576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610565575f3660031901126105655760206040516118a68152f35b34610565575f366003190112610565576020601454604051908152f35b34610565575f36600319011261056557602060ff60095460101c166040519015158152f35b34610565575f366003190112610565576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b346105655760066133fe36613804565b9061340a959295613ad8565b845f52600b60205260405f2060018060a01b0393849101541633036105cd578284169586156111c35780156111b157855f52600f60205260405f2060ff6002820154166105bb575484166105bb576040516134879161346882613931565b88825260208201525f6040820152865f52600f60205260405f20613faf565b827f00000000000000000000000000000000000000000000000000000000000000001691823b15610565576134d7925f92836040518096819582946334ff261960e21b84528c8c60048601613fea565b03925af1801561055a5761357f575b507f000000000000000000000000000000000000000000000000000000000000000016803b1561056557604051638547b8a160e01b8152915f918391829084908290613536908960048401614024565b03925af1801561055a57613570575b507fe2401087f6532bdc9af5c991e44926ff764d533c514eba06d830d971f5baba4d5f80a360015f55005b6135799061391e565b82613545565b6135889061391e565b846134e6565b34610565575f366003190112610565576135a6613ad8565b6135ae613b39565b505060015f55005b34610565575f36600319011261056557602060405161012c8152f35b34610565575f366003190112610565576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610565575f36600319011261056557602060ff600954166040519015158152f35b34610565575f366003190112610565576040517f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03168152602090f35b34610565575f3660031901126105655760206040516127108152f35b34610565575f366003190112610565576020600a54604051908152f35b34610565575f36600319011261056557602060405160378152f35b34610565576020366003190112610565576004355f52600f60205260405f2060018060a01b03815416610ffd60ff600260018501549401541660405193849384613763565b34610565576020366003190112610565576004355f526011602052602060ff60405f2054166040519015158152f35b34610565575f366003190112610565578066c55a6e4145e00060209252f35b6001600160a01b0390911681526020810191909152901515604082015260600190565b34610565575f3660031901126105655760206040516a115eec47f6cf7e350000008152f35b604435906001600160a01b038216820361056557565b600435906001600160a01b038216820361056557565b9181601f84011215610565578235916001600160401b038311610565576020838186019501011161056557565b90608060031983011261056557600435916024356001600160a01b0381168103610565579160443591606435906001600160401b0382116105655761384b916004016137d7565b9091565b6040919493926060820195825260208201520152565b6060906003190112610565576004356001600160a01b038116810361056557906024359060443590565b9060406003198301126105655760043591602435906001600160401b0382116105655761384b916004016137d7565b80548210156138d3575f5260205f2001905f90565b634e487b7160e01b5f52603260045260245ffd5b60a081019081106001600160401b03821117612dc957604052565b61010081019081106001600160401b03821117612dc957604052565b6001600160401b038111612dc957604052565b606081019081106001600160401b03821117612dc957604052565b601f909101601f19168101906001600160401b03821190821017612dc957604052565b81810292918115918404141715612bfd57565b811561398c570490565b634e487b7160e01b5f52601260045260245ffd5b90816020910312610565575180151581036105655790565b6001600160a01b039091168152602081019190915260400190565b90816060910312610565578051916040602083015192015190565b9490989796929360e096929461010087019a60018060a01b039687809216895216602088015260408701526060860152608085015260a08401521660c08201520152565b91908201809211612bfd57565b51906001600160701b038216820361056557565b9081606091031261056557613a6781613a3f565b916040613a7660208401613a3f565b92015163ffffffff811681036105655790565b6001600160a01b03918216815291166020820152604081019190915260600190565b5f198114612bfd5760010190565b9081602091031261056557516001600160a01b03811681036105655790565b60025f5414613ae75760025f55565b60405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606490fd5b91908203918211612bfd57565b600654908115613fa85760015460408051630240bc6b60e21b80825290946001600160a01b03938416939060046060808983818a5afa8015613f9e575f995f91613f7a575b5086516318160ddd60e01b8082529a602097919590888787818e5afa968715613f70575f97613f41575b508615613f2e576001600160701b0393613bca916131a791861690861661396f565b95670de0b6b3a76400009687810290808204891490151715613ef25790613bf091613982565b60155415613f195760155480821115613f0557613c0d9082613b2c565b87810290808204891490151715613ef25791613c2d613c33928994613982565b9061396f565b04988915613ee05787613c5d5f928c8c5194858094819363a9059cbb60e01b8352858d84016139b8565b03925af18015613e5157613ec3575b5085600154169a885f604482519e8f9283916327fc84a360e01b8352308b8401523060248401525af19b8c15613e51575f905f9d613e8e575b506001548a51630dfe168160e01b8152908916988a8289818d5afa918215613e84575f92613e65575b50807f0000000000000000000000000000000000000000000000000000000000000000169116145f14613e5b57613d0a909c9a5b600654613b2c565b600655613d198c601454613a32565b601455613d288a601354613a32565b6013558851918252838286818a5afa918215613e51579088915f955f94613e29575b5050858a518099819382525afa958615613e1f575f96613df0575b5085613d91575b505050505050905f80516020615d068339815191529185825191858352820152a19190565b91816131a792613da39416911661396f565b828102928184041490151715613ddd57505f80516020615d06833981519152939291613dce91613982565b60155590915f80808080613d6c565b601190634e487b7160e01b5f525260245ffd5b9095508681813d8311613e18575b613e08818361394c565b810103126105655751945f613d65565b503d613dfe565b88513d5f823e3d90fd5b613e459396508091929450903d1061324a57613237818361394c565b50939093915f80613d4a565b89513d5f823e3d90fd5b99613d0a90613d02565b613e7d9192508b3d8d116108495761083b818361394c565b905f613cce565b8c513d5f823e3d90fd5b809d508a8092503d8311613ebc575b613ea7818361394c565b8101031261056557878c519c01519b5f613ca5565b503d613e9d565b613ed990883d8a1161059057610580818361394c565b505f613c6c565b5050505050505050505090505f905f90565b601187634e487b7160e01b5f525260245ffd5b50505050505050505050505090505f905f90565b601555505f9a508a9998505050505050505050565b505050505050505050505090505f905f90565b9096508881813d8311613f69575b613f59818361394c565b810103126105655751955f613ba8565b503d613f4f565b8a513d5f823e3d90fd5b905081613f94929a503d8b1161324a57613237818361394c565b509890985f613b7e565b86513d5f823e3d90fd5b5f91508190565b600260409160018060a01b0384511660018060a01b03198254161781556020840151600182015501910151151560ff80198354169116179055565b9284926080959260018060a01b03168552602085015260606040850152816060850152848401375f828201840152601f01601f1916010190565b9081526001600160a01b03909116602082015260400190565b600454908082106132c45761405191613b2c565b600455565b801561409b576001808201808311612bfd5760011c90825b83831061407b5750505090565b909192506140928361408d8184613982565b613a32565b821c919061406e565b505f90565b604051906140ad82613902565b5f60e0838281528260208201528260408201528260608201528260808201528260a08201528260c08201520152565b8051156138d35760200190565b8051600110156138d35760400190565b80518210156138d35760209160051b010190565b90610171808302908382041483151715612bfd5761271061413091048093613b2c565b90565b61415e916020916141438261403d565b60405163a9059cbb60e01b81529384928392600484016139b8565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af190811561055a575f916141a3575b50156107d157565b6141bc915060203d60201161059057610580818361394c565b5f61419b565b90600554908082106132c4576141fc926141de82602094613b2c565b60055560405163a9059cbb60e01b81529384928392600484016139b8565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af190811561055a575f916141a35750156107d157565b80549190600160401b831015612dc9578261426391600161427b950181556138be565b90919082549060031b91821b915f19901b1916179055565b565b9392919061428c600a54613aab565b809281600a55819685905f5b80156143e7575060409586928351926142b084613902565b898452600760208501948986528681019388855260608201905f82526080830190815260a083019143835260c084019660018060a01b03998a8098169b8c8a5260e087019a8d8c525f52600b6020525f209551865551600186015551600285015551600384015551600483015551600582015582600682019451169360018060a01b031994858254161790550192511690825416179055815f52600c60205261435b84875f20614240565b614366600d54613aab565b600d557f00000000000000000000000000000000000000000000000000000000000000001693843b15610565575f9460a49386928851998a978896637c9441d160e11b8852600488015260248701526044860152606485015260848401525af19081156143de57506143d55750565b61427b9061391e565b513d5f823e3d90fd5b929350906127109004613cc390818102918183041490151715612bfd5761440e9092613aab565b9085939291614298565b60095460ff81168061448b575b61442c5750565b614434615440565b906004548281111561444557505050565b7ff71aa291a8223231f47fc103fc8005baa4baabeb91dcd95b0c863f978895ff189261010060609361ffff191617600955604051916001835260208301526040820152a1565b5060ff8160081c1615614425565b905f8092805f52602090600b82526040805f209160018060a01b039081600685015416156147a4576010855260ff835f205416156147f857805f526011855260ff835f2054166147e7575f5260118452815f20600160ff198254161790556144ff613b39565b505081516375c8e9bf60e11b8152600490858183817f000000000000000000000000000000000000000000000000000000000000000087165afa9081156146d1575f916147b2575b50670de0b6b3a764000061456968056bc75e2d6310000092600288015461396f565b04106147a457601354906115b391828102908082048414811517156146eb57620186a080920490816146fe575b5050601454928084029084820414841517156146eb57049182614613575b505050841580159061460a575b6145ce575b505050509190565b60077ff36c60116d16bc34ac57a184a0335dc52564c0a24fd629b0d22a5d6b86b0fa85930154169286825191868352820152a25f8080806145c6565b508615156145c1565b90809293995054908382106146db5761463991614631858093613b2c565b601455613b2c565b815561466085838a6007880154168651938492839263a9059cbb60e01b84528784016139b8565b03815f8d7f0000000000000000000000000000000000000000000000000000000000000000165af19081156146d1575f916146b4575b50156146a65750955f80806145b4565b82516312171d8360e31b8152fd5b6146cb9150863d881161059057610580818361394c565b5f614696565b84513d5f823e3d90fd5b8451631e9acf1760e31b81528390fd5b601183634e487b7160e01b5f525260245ffd5b819299509061470c91613b2c565b60135561473487828660078a0154168851938492839263a9059cbb60e01b84528884016139b8565b03815f897f0000000000000000000000000000000000000000000000000000000000000000165af1908115613f9e575f91614787575b501561477857965f80614596565b5083516312171d8360e31b8152fd5b61479e9150883d8a1161059057610580818361394c565b5f61476a565b50505050505090505f905f90565b90508581813d83116147e0575b6147c9818361394c565b810103126105655751670de0b6b3a7640000614547565b503d6147bf565b8251630e50c52360e21b8152600490fd5b8251623c461f60e31b8152600490fd5b8015612bfd575f190190565b600154604051630240bc6b60e21b81526001600160a01b039182169291606082600481875afa93841561055a575f925f956148e0575b5090602060049260405193848092630dfe168160e01b82525afa91821561055a575f926148bf575b50807f0000000000000000000000000000000000000000000000000000000000000000169116145f146148af576001600160701b03908116921690565b6001600160701b03928316921690565b6148d991925060203d6020116108495761083b818361394c565b905f614872565b600492919550602093506149029060603d60601161324a57613237818361394c565b509390939591925061484a565b6001600160401b038111612dc95760051b60200190565b906149308261490f565b61493d604051918261394c565b828152809261494e601f199161490f565b0190602036910137565b93909796959260c0959260e086019986526020860152604085015260018060a01b03166060840152608083015260a08201520152565b9397929690959491945f985f986149bb5f996149b5816149b05f9c5f9c613b2c565b613b2c565b90613a32565b915f80925f926153e1575b50801515806153d8575b6152f6575b61523857505060ff6009541680615226575b8061521d575b6151d1575b80158015614a02575b5050505050565b95919293956111b15760405163095ea7b360e01b815260208180614a4a857f0000000000000000000000000000000000000000000000000000000000000000600484016139b8565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1801561055a576151b2575b50604051614a9181613931565b6002815260403660208301377f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316614ad0826140dc565b52614ada816140e9565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316905242610e10810110612bfd57906040519182916338ed173960e01b835260048301526001602483015260a0604483015280518060a4840152602060c484019201905f5b8181106151905750503060648401525042610e100160848301525f919081900381837f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1801561055a57614bad915f9161516e575b506140e9565b5180156111b157614bcc91614bc482600454613a32565b600455613a32565b93614bd5614814565b838510156149fb57614be785846140f9565b5191825f52600b60205260405f209160018060a01b03600684015416158015615162575b61515557670de0b6b3a764000090818102918183041490151715612bfd57600491614c3591613982565b9101541161514f575f818152600b6020526040812060068101549091906001600160a01b031615610e3757600382015491600281015491600f83105f14615136576002820154925f60028401555b60018060a01b036006840154169182875f52600f6020528560ff600260405f200154165f146150fd575050865f52600f602052614ce760208660018060a01b0360405f205416956040516323b872dd60e01b81529384928392309060048501613a89565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1801561055a576150de575b506005614d2f868254613a32565b9055614d3a85615655565b91614d448661403d565b60405163a9059cbb60e01b815260208180614d638789600484016139b8565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af190811561055a575f916150bf575b50156107d157614db283600754613a32565b600755600f81101561503b577f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163b1561054357604051637ad80e6360e01b815260048101899052602481018790528281604481837f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af18015610cd357908391615027575b50505b600f80821015918261500d575b1090614fff575b614ece575b509260c0926001979695925f80516020615cc683398151915295614e87614418565b600260048401549301549360405195865260208601526040850152888060a01b03166060840152608083015260a0820152a2614ec1614814565b9290945b01939091614bd5565b6006840154614ee6906001600160a01b03168861545c565b6006840180546001600160a01b031990811690915560078501805490911690557f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03163b15610535576040516311cead4b60e31b8152600481018890528181602481837f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af1801561053857614feb575b50509260c0926001979695925f80516020615cc683398151915295600d805480614fd9575b5050875f80516020615ce683398151915260206002860154604051908152a2929550929596975092614e65565b614fe290614808565b90555f80614fac565b614ff5829161391e565b6105355780614f87565b50600a600285015410614e60565b61501d610ca86003890154613aab565b6004880155614e59565b6150309061391e565b61054357815f614e49565b60048501547f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316803b1561056557615097915f9189838d60405196879586948593631e45ec4360e31b85526004850161384f565b03925af1801561055a576150ac575b50614e4c565b6150b791925061391e565b5f905f6150a6565b6150d8915060203d60201161059057610580818361394c565b5f614da0565b6150f69060203d60201161059057610580818361394c565b505f614d21565b9093600e60205260405f2060ff600282015416615122575b50614ce7916020916109aa565b546001600160a01b03169450614ce7615115565b60038304926151458482613b2c565b6002840155614c83565b806149fb565b9392509460019150614ec5565b50600283015415614c0b565b61518a91503d805f833e615182818361394c565b8101906155dc565b5f614ba7565b82516001600160a01b0316845285945060209384019390920191600101614b48565b6151ca9060203d60201161059057610580818361394c565b505f614a84565b610e6a808202908282041482151715612bfd576127109004806151f5575b506149f2565b61520e9198506152179750615208614814565b91615997565b80979198613b2c565b5f6151ef565b508015156149ed565b50600454615232615440565b106149e7565b96509650505050509394925081615256575b505091905f905f905f90565b6020826152749261526c61414395989698615655565b94859261403d565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af190811561055a575f916152d7575b50156107d1576152cf916152c782600754613a32565b600755613a32565b915f8061524a565b6152f0915060203d60201161059057610580818361394c565b5f6152b1565b9a50919a50898b105f146153d1578a915b6153158b612ad7858561396f565b9b60405163a9059cbb60e01b8152602081806153358887600484016139b8565b03815f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03165af190811561055a575f916153b2575b50156107d1578c9b8493846007549061538a91613a32565b600755849d61539891613b2c565b936153a291613b2c565b936153ac91613b2c565b926149d5565b6153cb915060203d60201161059057610580818361394c565b5f615372565b8991615307565b508315156149d0565b925050506040810151602082015191608060018060a01b0391015116905f6149c6565b905f905b8082111561541557505090565b90916127109004613cc390818102918183041490151715612bfd5761543a9092613aab565b90615408565b6005546a115eec47f6cf7e35000000908103908111612bfd5790565b906001600160a01b03168061546f575050565b5f52600c60205260405f205f5b815480821015615516578361549183856138be565b919054600392831b1c146154a957505060010161547c565b9293505f19929190838101908111612bfd576154d8926154cc61426392876138be565b9054911b1c91856138be565b815480156155025701906154ff6154ef83836138be565b8154905f199060031b1b19169055565b55565b634e487b7160e01b5f52603160045260245ffd5b50505050565b906001600160a01b0390811680615550575b50909116908161553c575050565b61427b915f52600c60205260405f20614240565b5f52600c60205260405f20925f5b8454808210156155d1578461557383886138be565b919054600392831b1c1461558b57505060010161555e565b90915f96939495961992838101908111612bfd576155b0926154cc61426392876138be565b815480156155025701906155c76154ef83836138be565b555b91905f61552e565b5050909192506155c9565b6020908181840312610565578051906001600160401b03821161056557019180601f840112156105655782516156118161490f565b9361561f604051958661394c565b818552838086019260051b820101928311610565578301905b828210615646575050505090565b81518152908301908301615638565b9081156111b15761566e615667614814565b90846158f2565b605f810290808204605f1490151715612bfd576040805163095ea7b360e01b815290937f0000000000000000000000000000000000000000000000000000000000000000916001600160a01b03917f0000000000000000000000000000000000000000000000000000000000000000831691602091908281806156f5858a600484016139b8565b03815f885af18015613e5157615802575b5087519161571383613931565b600283528083019389368637615728846140dc565b52615732836140e9565b857f0000000000000000000000000000000000000000000000000000000000000000169052610e10420192834211612bfd57916064979593928a979597519889976338ed173960e01b895260a489019360048a015204602488015260a060448801525180915260c4860196915f5b8281106157e957505050508391825f96879330606484015260848301520393165af19081156157df576157db9293505f9161516e57506140e9565b5190565b83513d5f823e3d90fd5b83518616895297810197899750928101926001016157a0565b61581890833d851161059057610580818361394c565b505f615706565b811592670de0b6b3a764000092918383028381048514861715612bfd57615847828492613982565b10156158e9576107cd9485840295848704141715612bfd5761586d83916158729361396f565b61396f565b916103e89182840293808504841490151715612bfd57806158929161396f565b828102928184041490151715612bfd576158ac9204613b2c565b906158b7818061396f565b90610f949280840293840403612bfd576149b06131a76107ca946158da94613a32565b0460018101809111612bfd5790565b50505050505f90565b916103e592838102938185041490151715612bfd57615911908361396f565b906103e890818102918183041490151715612bfd576141309261593391613a32565b90613982565b90815f52600f60205260ff600260405f200154165f1461596e57505f908152600f60205260409020546001600160a01b031690565b919050600e60205260405f2060ff6002820154166159895750565b546001600160a01b03169150565b929060ff6009541615615c9c576159ac615440565b600490815481811115615c9057906159c391613b2c565b946159d285612ad7858461396f565b928360011b9584870460021485151715615c7d57878711615c5e575b5050600283108015615c54575b615c49576040805163095ea7b360e01b80825260209592947f0000000000000000000000000000000000000000000000000000000000000000926001600160a01b03917f000000000000000000000000000000000000000000000000000000000000000091898180615a708a8a8388016139b8565b03815f8888165af18015613e5157615c2c575b507f0000000000000000000000000000000000000000000000000000000000000000938851908152898180615abb8b8a8784016139b8565b03815f888a165af18015613e5157615c0f575b50605f8602868104605f1487151715615bfc57605f880290888204605f14891517156146eb57610e10420192834211613ddd57925f9492615b329260609a999897958d519c8d9b8c9a8b9862e8e33760e81b8a526064803098049604948a016139ee565b0393165af1908115615bf2575f915f915f91615bc8575b50615b5f90615b578461403d565b600654613a32565b6006558196819610155f14615b9b577f39295663e152e1c6c375f069996f3e04a36a00f4173c65e4095b60d6ba0a882c938351928352820152a1565b7f0e2afe1247bc1a003b1493f74608702bd5a78b15077949481b7269aac1112d54938351928352820152a1565b9050615b5f9250615be8915060603d60601161327f5761326e818361394c565b9192909190615b49565b82513d5f823e3d90fd5b601182634e487b7160e01b5f525260245ffd5b615c25908a3d8c1161059057610580818361394c565b505f615ace565b615c42908a3d8c1161059057610580818361394c565b505f615a83565b505f94508493505050565b50600281106159fb565b9350945050615c768594612ad78760011c948561396f565b5f806159ee565b601184634e487b7160e01b5f525260245ffd5b505f9550859450505050565b505f925082915056fecb38ab18a1a079716fa2ffd5466032ff737a3cf54f5a2cf5d40969a81fee9659d345c20e4420993f6a7f7ad31c5184505afc149305d938b7d118aef1ff393fbd489856ffe28db7908da3a8af35a16e6c12c8cfe70f8106f2604fdd844daf057abe266970c8fc2284ffe75147697f09e015f1a7c133d0d417d36e0ac7fbfa4192a164736f6c6343000818000a000000000000000000000000b47fa3fda09e61a68a8089e1f4d0f44bd993e6b900000000000000000000000018e89dfc638a61ec010fb269f0c7289d71555d69000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb81400000000000000000000000097b4fec5214e99fc92452614f98c89db55584afe000000000000000000000000c4f586c1ad85e33276e70ea3b39dfa2291f75db40000000000000000000000000deed1486bc52aa0d3e6f8849cec5add6598a1620000000000000000000000009526b745052d259add5dc79bcdc61d0edc68f84b000000000000000000000000165c3410fc91ef562c50559f7d2289febed552d9

Deployed ByteCode

0x61016080604052600436101561001d575b50361561001b575f80fd5b005b5f3560e01c908163017d12691461374457508063083131f1146137155780630a9f88a6146136d05780630d9a34a2146136b55780630ea1df6f146136985780630ee5215c1461367c5780630f093fb81461363857806313f9243a1461361657806315420018146135d25780631a65893b146135b65780631db6fbd71461358e5780631f6f91c414611ada5780632f138c0a146133ee57806331eb318a146133aa5780633a0d171a146133855780633a8425d6146133685780633b7238e11461334c5780633b7d6d0b146133085780634097f54714612e03578063413a548c14611bc1578063429858e214611b4057806345a3042514611afc57806348423de514611adf5780634a1d97cc14611ada5780634aff6483146119395780634c519cf9146117ce578063515bce08146117b15780635c9707eb146117955780635e7761bb14611770578063722050801461174c57806378b022e81461172f5780637ac7a3d5146117075780637c8acb88146116ea5780637f046db4146116ce5780637f108e0a1461168a5780638004f9ad1461166f57806380d7624e146115b65780638781fbc91461137a578063880896241461132357806392f3c6411461127a5780639f56bf5314611258578063a3376a1b14611230578063a902dd72146111f8578063b237d2be146111d5578063b8357a8d14611001578063ba48ab7814610fb8578063babec6e814610f9c578063bf9b240114610f6d578063c1b8411a14610f29578063c687782214610ee5578063c69d9e3c14610ebd578063ca70307514610ea0578063cabe7eff14610e83578063ce75e84314610e66578063dbb97afa14610e49578063dc9dcaf7146108a3578063dd7dc9a914610888578063df8579381461086d578063e8e72ca614610850578063e99d0863146106ad578063eb4a659814610669578063ebd3687d14610644578063ebda16eb1461061f578063ec8a903014610603578063ed78a98b146105de5763ef598e49146102ff575f610010565b346105655761030d3661388f565b610318929192613ad8565b5f90825f52602093600e855260405f209460018060a01b039283875416968733036105cd57600281019182549460ff86166105bb57600b82106105a95760019161037a8660405183819483830196873781015f8382015203808452018261394c565b519020910154036105975760ff199092166001179091555f848152600b8252604090206006810180546007830180546001600160a01b03199081168a17909155811688179091556103fa929084169181906002906103d98a868b61551c565b0154809360405195869283926323b872dd60e01b8452309060048501613a89565b03815f887f00000000000000000000000018e89dfc638a61ec010fb269f0c7289d71555d69165af192831561055a5761044993610569575b505061044081600554613a32565b600555856141c2565b807f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb81416803b15610565575f8091602460405180948193636af39aa360e01b83528960048401525af1801561055a57610547575b507f00000000000000000000000097b4fec5214e99fc92452614f98c89db55584afe16803b1561054357816040518092632770a7eb60e21b82528183816104e78933600484016139b8565b03925af1801561053857610521575b50807ffb33a7af0d1bab03646542c2475651411e9d8add57afa25f63e67d6c13103e0e91a360015f55005b61052b829161391e565b610535575f6104f6565b80fd5b6040513d84823e3d90fd5b5080fd5b61055291925061391e565b5f905f61049c565b6040513d5f823e3d90fd5b5f80fd5b8161058892903d10610590575b610580818361394c565b8101906139a0565b505f80610432565b503d610576565b604051631b93fb8b60e31b8152600490fd5b6040516307540a0f60e31b8152600490fd5b60405163a741a04560e01b8152600490fd5b6040516282b42960e81b8152600490fd5b34610565575f36600319011261056557602060ff60095460081c166040519015158152f35b34610565575f366003190112610565576020604051610e6a8152f35b34610565575f3660031901126105655760206040516a017d2a320dd745550000008152f35b34610565575f3660031901126105655760206040516a0b4a7ffb93a0786f4000008152f35b34610565575f366003190112610565576040517f000000000000000000000000b47fa3fda09e61a68a8089e1f4d0f44bd993e6b96001600160a01b03168152602090f35b34610565576106bb36613865565b600354919290916001600160a01b039190821633036105cd57816001541690604051948592630dfe168160e01b845283600460209889935afa801561055a5784935f91610823575b507f000000000000000000000000b47fa3fda09e61a68a8089e1f4d0f44bd993e6b984169416840361081d5793925b604051868163a9059cbb60e01b93848252815f816107548c8a600484016139b8565b03925af190811561055a575f91610800575b50156107d15761078a9286925f8693604051968795869485938452600484016139b8565b03927f0000000000000000000000000deed1486bc52aa0d3e6f8849cec5add6598a162165af190811561055a575f916107e3575b50156107d1576040928351928352820152f35b6040516312171d8360e31b8152600490fd5b6107fa9150843d861161059057610580818361394c565b846107be565b6108179150873d891161059057610580818361394c565b87610766565b92610732565b6108439150873d8911610849575b61083b818361394c565b810190613ab9565b87610703565b503d610831565b34610565575f366003190112610565576020600654604051908152f35b34610565575f366003190112610565576020604051600a8152f35b34610565575f366003190112610565576020604051600f8152f35b34610565576020366003190112610565576108bc613ad8565b6004355f908152600b6020526040812060068101549091906001600160a01b031615610e375760038201546040516375c8e9bf60e11b81529092906020816004817f0000000000000000000000009526b745052d259add5dc79bcdc61d0edc68f84b6001600160a01b03165afa90811561055a575f91610e05575b50600482015411610df357600281015491600f831015610dda576002820154925f60028401555b60018060a01b0360068401541691826004355f52600f6020528560ff600260405f200154165f14610da15750506004355f52600f6020526109c760208660018060a01b0360405f205416955b6040516323b872dd60e01b81529384928392309060048501613a89565b03815f7f00000000000000000000000018e89dfc638a61ec010fb269f0c7289d71555d696001600160a01b03165af1801561055a57610d82575b50610a0e85600554613a32565b600555610a1a85615655565b91610a248661403d565b60405163a9059cbb60e01b815260208180610a438789600484016139b8565b03815f7f0000000000000000000000000deed1486bc52aa0d3e6f8849cec5add6598a1626001600160a01b03165af190811561055a575f91610d63575b50156107d157610a9283600754613a32565b600755600f811015610cde577f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8146001600160a01b03163b1561054357604051637ad80e6360e01b81526004803590820152602481018790528281604481837f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8146001600160a01b03165af18015610cd357908391610cbf575b50505b600f808210159182610c98575b1090610c8a575b610b9c575b50610b4f614418565b60026004840154930154936040519586526020860152604085015260018060a01b03166060840152608083015260a08201525f80516020615cc683398151915260c060043592a260015f55005b6006840154610bb6906001600160a01b031660043561545c565b6006840180546001600160a01b031990811690915560078501805490911690557f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8146001600160a01b0316803b15610543578180916024604051809481936311cead4b60e31b835260043560048401525af1801561053857610c76575b5050600d5480610c64575b5060028301546040519081525f80516020615ce6833981519152602060043592a285610b46565b610c6d90614808565b600d5585610c3d565b610c80829161391e565b6105355780610c32565b50600a600285015410610b41565b610cb5610ca86003890154613aab565b8060038a01558854615404565b6004880155610b3a565b610cc89061391e565b610543578188610b2a565b6040513d85823e3d90fd5b60048501547f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8146001600160a01b0316803b1561056557610d3b915f918983604051809681958294631e45ec4360e31b84526004356004850161384f565b03925af1801561055a57610d50575b50610b2d565b610d5b91925061391e565b5f9087610d4a565b610d7c915060203d60201161059057610580818361394c565b88610a80565b610d9a9060203d60201161059057610580818361394c565b5086610a01565b9093600e60205260405f2060ff600282015416610dc6575b506109c7916020916109aa565b546001600160a01b031694506109c7610db9565b6003830492610de98482613b2c565b600284015561095e565b60405163582515d760e11b8152600490fd5b90506020813d602011610e2f575b81610e206020938361394c565b81010312610565575184610937565b3d9150610e13565b60405163ad5679e160e01b8152600490fd5b34610565575f366003190112610565576020601554604051908152f35b34610565575f366003190112610565576020601354604051908152f35b34610565575f366003190112610565576020600554604051908152f35b34610565575f366003190112610565576020600854604051908152f35b34610565575f366003190112610565576001546040516001600160a01b039091168152602090f35b34610565575f366003190112610565576040517f000000000000000000000000165c3410fc91ef562c50559f7d2289febed552d96001600160a01b03168152602090f35b34610565575f366003190112610565576040517f000000000000000000000000bad37bea31ad0ac692080ea29b04b23a9ee7b0556001600160a01b03168152602090f35b34610565576020366003190112610565576004355f526010602052602060ff60405f2054166040519015158152f35b34610565575f3660031901126105655760206040516101f48152f35b34610565576020366003190112610565576004355f52600e60205260405f2060018060a01b03815416610ffd60ff600260018501549401541660405193849384613763565b0390f35b3461056557600661101136613804565b61101f959195939293613ad8565b845f52600b60205260405f2060018060a01b0392839101541633036105cd578183169586156111c35784156111b1575f94865f52600e60205260405f2060ff6002820154166105bb575484166105bb5760405161109e9161107f82613931565b89825260208201525f6040820152875f52600e60205260405f20613faf565b827f00000000000000000000000097b4fec5214e99fc92452614f98c89db55584afe1691823b15610565576110ee925f92836040518096819582946334ff261960e21b84528d8c60048601613fea565b03925af1801561055a5761119e575b507f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8141690813b1561119a5761114c8392839260405194858094819363574d799f60e11b83528a60048401614024565b03925af1801561053857611186575b50807f1c159cfd96672eca794ae8139b8b5d0d2c4363aaeddd3fa7df0a626daff3a38e91a360015f55005b611190829161391e565b610535578361115b565b8280fd5b6111a991935061391e565b5f91856110fd565b60405163162908e360e11b8152600490fd5b60405163d92e233d60e01b8152600490fd5b34610565575f36600319011261056557602060405168056bc75e2d631000008152f35b34610565576020366003190112610565576001600160a01b036112196137c1565b165f52600c602052602060405f2054604051908152f35b34610565575f366003190112610565576002546040516001600160a01b039091168152602090f35b34610565575f36600319011261056557602060ff601254166040519015158152f35b3461056557602036600319011261056557600435611296613ad8565b5f818152600b60205260409020600601546001600160a01b03168015610e3757815f52601060205260ff60405f2054161561131257815f52601160205260ff60405f2054166113005733036105cd576112f0604091614499565b60015f5582519182526020820152f35b604051630e50c52360e21b8152600490fd5b604051623c461f60e31b8152600490fd5b346105655760403660031901126105655761133c6137c1565b6001600160a01b03165f908152600c60205260409020805460243591908210156105655760209161136c916138be565b90546040519160031b1c8152f35b34610565576113883661388f565b611393929192613ad8565b5f90825f52602093600f855260405f209460018060a01b039283875416968733036105cd57600281019182549460ff86166105bb57600b82106105a9576001916113f58660405183819483830196873781015f8382015203808452018261394c565b519020910154036105975760ff199092166001179091555f848152600b8252604090206006810180546007830180546001600160a01b03199081168a1790915581168817909155611454929084169181906002906103d98a868b61551c565b03815f887f00000000000000000000000018e89dfc638a61ec010fb269f0c7289d71555d69165af192831561055a576114999361159857505061044081600554613a32565b807f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb81416803b15610565575f80916024604051809481936301ac002160e51b83528960048401525af1801561055a57611585575b507f000000000000000000000000c4f586c1ad85e33276e70ea3b39dfa2291f75db416803b1561054357816040518092632770a7eb60e21b82528183816115378933600484016139b8565b03925af1801561053857611571575b50807fa2107f39e66e914a571170508240a4ad588a19b13668f6b1a085924e9cc752a491a360015f55005b61157b829161391e565b6105355783611546565b61159091925061391e565b5f90846114ec565b816115ae92903d1061059057610580818361394c565b508680610432565b34610565576115c436613865565b91906115ce613ad8565b6002546001600160a01b0390811633036105cd5782169182156111c35781156111b157831561165e5781611644856116358360409561162d7f49da8a165df89c4dc7a071ab41d505985b3a3351edbbf5aada3ba64e16f2b3709861410d565b939082614133565b61163f83826141c2565b61427d565b9461164d614418565b82519182526020820152a360015f55005b60405162bfc92160e01b8152600490fd5b34610565575f36600319011261056557602060405160038152f35b34610565575f366003190112610565576040517f00000000000000000000000018e89dfc638a61ec010fb269f0c7289d71555d696001600160a01b03168152602090f35b34610565575f3660031901126105655760206040516115b38152f35b34610565575f366003190112610565576020600754604051908152f35b34610565575f366003190112610565576003546040516001600160a01b039091168152602090f35b34610565575f366003190112610565576020600454604051908152f35b34610565575f366003190112610565576020604051694e59bd7d27fbc34000008152f35b34610565575f3660031901126105655760206040516a0497421a5557c070c000008152f35b34610565575f366003190112610565576020604051613cc38152f35b34610565575f366003190112610565576020604051620186a08152f35b34610565576060366003190112610565576117e76137c1565b6024356001600160a01b0381811691829003610565576118056137ab565b90807f000000000000000000000000bad37bea31ad0ac692080ea29b04b23a9ee7b0551633036105cd5760ff60095460101c1661192757600254938185166105bb57600154908282166105bb5782169283156111c35784156111c35782169485156111c3576001600160a01b03199081168417600255908116841760015560038054909116851790557f000000000000000000000000b47fa3fda09e61a68a8089e1f4d0f44bd993e6b916803b15610565575f8091602460405180948193635116a41960e11b83528860048401525af1801561055a57611918575b506201000062ff00001960095416176009557f0a6d7e5d0a33f7178bd67452a2e7a76f4ed9b05f686a67be9668091c104a24075f80a4005b6119219061391e565b836118e0565b6040516317c3335f60e21b8152600490fd5b346105655760208060031936011261056557600435611956613ad8565b80156111b1576040516375c8e9bf60e11b81526001600160a01b0383826004817f0000000000000000000000009526b745052d259add5dc79bcdc61d0edc68f84b85165afa91821561055a575f92611aab575b508360405180926323b872dd60e01b8252815f816119cc89303360048501613a89565b03927f000000000000000000000000b47fa3fda09e61a68a8089e1f4d0f44bd993e6b9165af190811561055a575f91611a8e575b50156107d157611a2b90611a1683600454613a32565b600455611a2383336141c2565b82803361427d565b8091815f526010845260405f20600160ff198254161790557f276b7740d8e50775651f99441ea05a1838a931d5611055cd11bacf039c35d7b6611a6d83614499565b90611a7f60405192839233968461384f565b0390a360015f55604051908152f35b611aa59150843d861161059057610580818361394c565b84611a00565b9091508381813d8311611ad3575b611ac3818361394c565b81010312610565575190846119a9565b503d611ab9565b613786565b34610565575f366003190112610565576020600d54604051908152f35b34610565575f366003190112610565576040517f00000000000000000000000097b4fec5214e99fc92452614f98c89db55584afe6001600160a01b03168152602090f35b34610565576020366003190112610565576004355f52600b60205261010060405f208054906001810154906002810154600382015460048301549060058401549260018060a01b036007816006880154169601541695604051978852602088015260408701526060860152608085015260a084015260c083015260e0820152f35b3461056557608036600319011261056557611bda6137ab565b6001600160401b036064351161056557366023606435011215610565576001600160401b036004606435013511610565573660246064356004013560051b60643501011161056557611c2a613ad8565b6001600160a01b03811615612dfc575b600435156111b157602435156111b15733156111c3576101f460643560040135116111b1576040516323b872dd60e01b815260208180611c81600435303360048501613a89565b03815f7f0000000000000000000000000deed1486bc52aa0d3e6f8849cec5add6598a1626001600160a01b03165af190811561055a575f91612ddd575b50156107d157611ccf600854613aab565b600855604051906001600160401b0360c0830190811190831117612dc95760c082016040525f82525f60208301525f60408301525f60608301525f60808301525f60a0830152611d1d614814565b80670de0b6b3a7640000810204670de0b6b3a76400001481151715612bfd57611d5082670de0b6b3a76400008302613982565b84525f60c052611d6560643560040135614926565b60a052611d7760643560040135614926565b5f5b606435600401358110612c745750505f610140525f610100525f905f8061014052604051611da6816138e7565b5f81525f60208201525f60408201525f60608201525f6080820152905f92611dcf60c05161490f565b95611ddd604051978861394c565b60c0518752601f19611df060c05161490f565b015f5b818110612c5d57505060c051610100526004356080525f610120525b60c05161012051106128e6575b5050611e266140a0565b945f5b8581106122a557505090611e519291610100519260c0519260a051926101405160043561498e565b60808901526060880152604087015260e0840151611f75575b50611ea4925090611e82611e99926020870151613a32565b9081602087015260a0860152604085015190613a32565b608084015190613a32565b916024358310611f635782611edc611ec8611ec060209661410d565b919086614133565b611ed281866141c2565b835190838661427d565b928392611ee7614418565b8581015190608081015160408201519060a06060840151930151936040519660043588528a88015260408701526060860152608085015260a084015260c083015260018060a01b0316907f9c7bfec5ca88127b0c60e4320ac0b9ddc9f8365d5b2ab14ca209dc3d458a3fad60e03392a460015f55604051908152f35b6040516329b6a4e960e21b8152600490fd5b611f949060408501611f88848251613a32565b90526060850151613a32565b606084015282515f52600b60205260405f20926003840154611fbe60208301516002870154613b2c565b600286015560c0820151156121f857815160208301517f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8146001600160a01b03169190823b156105655760445f92836040519586948593637ad80e6360e01b8552600485015260248401525af1801561055a576121e9575b505b60c0820151156121c2575b60c0820151158015906121b4575b6120b8575b91815f80516020615ca6833981519152611ea496611e9996956120ad611e82965194602081015193606082015191608060018060a01b0360a08301511691015190600260048401549301549360405197889788614958565b0390a2919250611e6a565b929181516120d4600687019160018060a01b038354169061545c565b80546001600160a01b0319908116909155600786018054909116905581517f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8146001600160a01b03169290833b15610565575f936024859260405196879384926311cead4b60e31b845260048401525af1801561055a57611ea496611e9996611e82955f80516020615ca6833981519152936121a5575b50612176600d54614808565b600d5583515f80516020615ce683398151915260206002850154604051908152a2939596509650509150612055565b6121ae9061391e565b8b61216a565b50600a600286015410612050565b6121df6121d26003870154613aab565b8060038801558654615404565b6004860155612042565b6121f29061391e565b87612035565b81516020830151608084015191907f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8146001600160a01b03163b15610565575f916122566040519485938493631e45ec4360e31b85526004850161384f565b0381837f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8146001600160a01b03165af1801561055a57612296575b50612037565b61229f9061391e565b87612290565b60e06122b182846140f9565b5101516126f7576122c281836140f9565b519081515f52600b60205260405f2091602081015161230760208260018060a01b0360068801541660405193849283926323b872dd60e01b8452309060048501613a89565b03815f7f00000000000000000000000018e89dfc638a61ec010fb269f0c7289d71555d696001600160a01b03165af1801561055a576126d8575b5061234e81600554613a32565b60055560a0820151608083015160405163a9059cbb60e01b81529160209183918291612386916001600160a01b0316600484016139b8565b03815f7f0000000000000000000000000deed1486bc52aa0d3e6f8849cec5add6598a1626001600160a01b03165af190811561055a575f916126b9575b50156107d1576123d7906002850154613b2c565b60028401556123ec6080820151600754613a32565b600755600383015460c08201511561263157815160208301517f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8146001600160a01b03169190823b156105655760445f92836040519586948593637ad80e6360e01b8552600485015260248401525af1801561055a57612622575b505b60c0820151156125fb575b60c0820151158015906125ed575b6124fe575b6001935f80516020615ca6833981519152916124d384519460208101519360808201519160608a8060a01b0360a08301511691015190600260048401549301549360405197889788614958565b0390a26124f260206124e583866140f9565b51015160208c0151613a32565b60208b01525b01611e29565b612516825160018060a01b036006870154169061545c565b6006840180546001600160a01b0319908116909155600785018054909116905581517f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8146001600160a01b03169490853b15610565575f956024879260405198899384926311cead4b60e31b845260048401525af191821561055a576001955f80516020615ca6833981519152936125de575b50600d6125b58154614808565b905583515f80516020615ce683398151915260206002850154604051908152a291509350612486565b6125e79061391e565b8e6125a8565b50600a600285015410612481565b61261861260b6003860154613aab565b8060038701558554615404565b6004850155612473565b61262b9061391e565b8c612466565b8151602083015160608401519091907f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8146001600160a01b0316803b1561056557612695935f809460405196879586948593631e45ec4360e31b85526004850161384f565b03925af1801561055a576126aa575b50612468565b6126b39061391e565b8c6126a4565b6126d2915060203d60201161059057610580818361394c565b8d6123c3565b6126f09060203d60201161059057610580818361394c565b508c612341565b955061270386826140f9565b5161270c6140a0565b5080515f908152600b6020908152604091829020600601548284015192516323b872dd60e01b815292839182916127519130906001600160a01b031660048501613a89565b03815f7f00000000000000000000000018e89dfc638a61ec010fb269f0c7289d71555d696001600160a01b03165af1801561055a576128c7575b5061279c6040820151600554613a32565b60055560a0810151608082015160405163a9059cbb60e01b815291602091839182916127d4916001600160a01b0316600484016139b8565b03815f7f0000000000000000000000000deed1486bc52aa0d3e6f8849cec5add6598a1626001600160a01b03165af190811561055a575f916128a8575b50156107d157806080600192015161282c6007918254613a32565b90558051906040810151906020810151608082015160608301519160c0878060a01b0360a0860151169401511515946040519661286888613902565b8752602087015260408601526060850152608084015260a083015260c08201528160e08201529661289e60206124e583866140f9565b60208b01526124f8565b6128c1915060203d60201161059057610580818361394c565b8a612811565b6128df9060203d60201161059057610580818361394c565b508961278b565b90946128f76101205160a0516140f9565b515f818152600b6020526040902060068101546001600160a01b0316158015612c51575b612c4957600481015494612930868a8761581f565b95600283015492600f84105f14612c3f5783925b670de0b6b3a7640000612957848661396f565b0460e052886080511080159081612c29575b612a7e57506129a69061298961298160e0518c613a32565b608051613b2c565b6080526129998a61014051613a32565b6101405260e05190613a32565b9780612a41575b5060070154612a2e959493600f93909290916129d2906001600160a01b031686615939565b91604051956129e087613902565b86528060208701526040860152606085015260e051608085015260018060a01b031660a08401521060c08201525f60e0820152612a1d828a6140f9565b52612a2881896140f9565b50613aab565b945b600161012051016101205290611e0f565b916129d2612a708d612a6a8b9f96612a64600f99979d612a2e9d9c9b99836158f2565b92613a32565b9e613b2c565b9892945050919394956129ad565b9b939a9196949b999098929795995f14612c11575050600161012051016101205111612bfd57612b7793600161012051016101005281612bb5575b505060e051159050612baf57612adc60e051612ad76080518a61396f565b613982565b600f612ae8828a613b2c565b99612b1c612b12612afd60805160e051613b2c565b6007909801546001600160a01b03168b615939565b9860805190613a32565b9960405193612b2a85613902565b8a8552602085015260408401526060830187905260808051908401526001600160a01b03881660a08401521060c0820152600160e0820152612b6c828b6140f9565b52612a28818a6140f9565b9560405194612b85866138e7565b85526020850152604084015260608301526001600160a01b031660808201526001915b8780611e1c565b5f612adc565b8281612bd284612bcc612bd895612bf198836158f2565b93613a32565b50613b2c565b50612be581608051613b2c565b60805261014051613a32565b610140528b8080612ab9565b634e487b7160e01b5f52601160045260245ffd5b98509850505096505050506101205161010052612ba8565b5060e051612c398b608051613b2c565b10612969565b6003840492612944565b505094612a30565b5060028101541561291b565b602090612c686140a0565b82828c01015201611df3565b606435600582901b01602401355f908152600b6020526040902060068101546001600160a01b0316158015612dbd575b612d84575f805b60c0518110612d8d575b50612d8457600401549060c05191825b612d05575b612cef60019360248460051b606435010135612ce88260a0516140f9565b52856140f9565b52612cfb60c051613aab565b60c0525b01611d79565b5f19830192808411612bfd57612d28612d208560a0516140f9565b5194866140f9565b51828110612d685782811480612d70575b612d6857612d60929394612d4f8360a0516140f9565b52612d5a82876140f9565b52614808565b919082612cc5565b509250612cca565b5060248460051b6064350101358510612d39565b50600190612cff565b60248460051b606435010135612da58260a0516140f9565b5114612db357600101612cab565b5050600188612cb5565b50600281015415612ca4565b634e487b7160e01b5f52604160045260245ffd5b612df6915060203d60201161059057610580818361394c565b82611cbe565b5033611c3a565b3461056557608036600319011261056557612e1c613ad8565b6002546001600160a01b031633036105cd5760ff601254166105bb57600435156111b1576040516370a0823160e01b81523060048201527f0000000000000000000000000deed1486bc52aa0d3e6f8849cec5add6598a1626001600160a01b031690602081602481855afa90811561055a575f916132d6575b50600435116132c457670de0b6b3a7640000600435026004358104670de0b6b3a764000003612bfd5766c55a6e4145e0008104156111b15766c55a6e4145e0008104600454106132c457605f66c55a6e4145e00082040266c55a6e4145e00082048104605f03612bfd576064900460243510611f6357605f600435026004358104605f03612bfd576064900460443510611f635760405160208163095ea7b360e01b94858252815f81612f6e6004357f000000000000000000000000165c3410fc91ef562c50559f7d2289febed552d9600484016139b8565b03925af1801561055a576132a5575b5060405191825260208280612fbf66c55a6e4145e00085047f000000000000000000000000165c3410fc91ef562c50559f7d2289febed552d9600484016139b8565b03815f7f000000000000000000000000b47fa3fda09e61a68a8089e1f4d0f44bd993e6b96001600160a01b03165af190811561055a5761307892606092613286575b506040518093819262e8e33760e81b8352606435903090604435906024359066c55a6e4145e00060043591047f0000000000000000000000000deed1486bc52aa0d3e6f8849cec5add6598a1627f000000000000000000000000b47fa3fda09e61a68a8089e1f4d0f44bd993e6b960048a016139ee565b03815f7f000000000000000000000000165c3410fc91ef562c50559f7d2289febed552d96001600160a01b03165af1801561055a575f905f925f91613251575b506130c28261403d565b600160ff1960125416176012556130db81600654613a32565b600655600154604051630240bc6b60e21b81526001600160a01b039091169190606081600481865afa801561055a576004935f925f92613218575b50602090604051958680926318160ddd60e01b82525afa93841561055a575f946131e4575b508361318d575b5050610ffd91507f2c4c1392b0b55915ca15a44ebd78091450f67f2a338fe85e3e3b4c64b53003076040518061317a8487898461384f565b0390a160015f556040519384938461384f565b6131ac916131a7916001600160701b03908116911661396f565b614056565b670de0b6b3a7640000810290808204670de0b6b3a76400001490151715612bfd57610ffd926131da91613982565b6015558480613142565b9093506020813d602011613210575b816132006020938361394c565b810103126105655751928661313b565b3d91506131f3565b6020935061323f91925060603d60601161324a575b613237818361394c565b810190613a53565b509290929190613116565b503d61322d565b915050613276915060603d60601161327f575b61326e818361394c565b8101906139d3565b919091836130b8565b503d613264565b61329e9060203d60201161059057610580818361394c565b5083613001565b6132bd9060203d60201161059057610580818361394c565b5082612f7d565b604051631e9acf1760e31b8152600490fd5b90506020813d602011613300575b816132f16020938361394c565b81010312610565575182612e95565b3d91506132e4565b34610565575f366003190112610565576040517f000000000000000000000000c4f586c1ad85e33276e70ea3b39dfa2291f75db46001600160a01b03168152602090f35b34610565575f3660031901126105655760206040516118a68152f35b34610565575f366003190112610565576020601454604051908152f35b34610565575f36600319011261056557602060ff60095460101c166040519015158152f35b34610565575f366003190112610565576040517f0000000000000000000000009526b745052d259add5dc79bcdc61d0edc68f84b6001600160a01b03168152602090f35b346105655760066133fe36613804565b9061340a959295613ad8565b845f52600b60205260405f2060018060a01b0393849101541633036105cd578284169586156111c35780156111b157855f52600f60205260405f2060ff6002820154166105bb575484166105bb576040516134879161346882613931565b88825260208201525f6040820152865f52600f60205260405f20613faf565b827f000000000000000000000000c4f586c1ad85e33276e70ea3b39dfa2291f75db41691823b15610565576134d7925f92836040518096819582946334ff261960e21b84528c8c60048601613fea565b03925af1801561055a5761357f575b507f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb81416803b1561056557604051638547b8a160e01b8152915f918391829084908290613536908960048401614024565b03925af1801561055a57613570575b507fe2401087f6532bdc9af5c991e44926ff764d533c514eba06d830d971f5baba4d5f80a360015f55005b6135799061391e565b82613545565b6135889061391e565b846134e6565b34610565575f366003190112610565576135a6613ad8565b6135ae613b39565b505060015f55005b34610565575f36600319011261056557602060405161012c8152f35b34610565575f366003190112610565576040517f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8146001600160a01b03168152602090f35b34610565575f36600319011261056557602060ff600954166040519015158152f35b34610565575f366003190112610565576040517f0000000000000000000000000deed1486bc52aa0d3e6f8849cec5add6598a1626001600160a01b03168152602090f35b34610565575f3660031901126105655760206040516127108152f35b34610565575f366003190112610565576020600a54604051908152f35b34610565575f36600319011261056557602060405160378152f35b34610565576020366003190112610565576004355f52600f60205260405f2060018060a01b03815416610ffd60ff600260018501549401541660405193849384613763565b34610565576020366003190112610565576004355f526011602052602060ff60405f2054166040519015158152f35b34610565575f366003190112610565578066c55a6e4145e00060209252f35b6001600160a01b0390911681526020810191909152901515604082015260600190565b34610565575f3660031901126105655760206040516a115eec47f6cf7e350000008152f35b604435906001600160a01b038216820361056557565b600435906001600160a01b038216820361056557565b9181601f84011215610565578235916001600160401b038311610565576020838186019501011161056557565b90608060031983011261056557600435916024356001600160a01b0381168103610565579160443591606435906001600160401b0382116105655761384b916004016137d7565b9091565b6040919493926060820195825260208201520152565b6060906003190112610565576004356001600160a01b038116810361056557906024359060443590565b9060406003198301126105655760043591602435906001600160401b0382116105655761384b916004016137d7565b80548210156138d3575f5260205f2001905f90565b634e487b7160e01b5f52603260045260245ffd5b60a081019081106001600160401b03821117612dc957604052565b61010081019081106001600160401b03821117612dc957604052565b6001600160401b038111612dc957604052565b606081019081106001600160401b03821117612dc957604052565b601f909101601f19168101906001600160401b03821190821017612dc957604052565b81810292918115918404141715612bfd57565b811561398c570490565b634e487b7160e01b5f52601260045260245ffd5b90816020910312610565575180151581036105655790565b6001600160a01b039091168152602081019190915260400190565b90816060910312610565578051916040602083015192015190565b9490989796929360e096929461010087019a60018060a01b039687809216895216602088015260408701526060860152608085015260a08401521660c08201520152565b91908201809211612bfd57565b51906001600160701b038216820361056557565b9081606091031261056557613a6781613a3f565b916040613a7660208401613a3f565b92015163ffffffff811681036105655790565b6001600160a01b03918216815291166020820152604081019190915260600190565b5f198114612bfd5760010190565b9081602091031261056557516001600160a01b03811681036105655790565b60025f5414613ae75760025f55565b60405162461bcd60e51b815260206004820152601f60248201527f5265656e7472616e637947756172643a207265656e7472616e742063616c6c006044820152606490fd5b91908203918211612bfd57565b600654908115613fa85760015460408051630240bc6b60e21b80825290946001600160a01b03938416939060046060808983818a5afa8015613f9e575f995f91613f7a575b5086516318160ddd60e01b8082529a602097919590888787818e5afa968715613f70575f97613f41575b508615613f2e576001600160701b0393613bca916131a791861690861661396f565b95670de0b6b3a76400009687810290808204891490151715613ef25790613bf091613982565b60155415613f195760155480821115613f0557613c0d9082613b2c565b87810290808204891490151715613ef25791613c2d613c33928994613982565b9061396f565b04988915613ee05787613c5d5f928c8c5194858094819363a9059cbb60e01b8352858d84016139b8565b03925af18015613e5157613ec3575b5085600154169a885f604482519e8f9283916327fc84a360e01b8352308b8401523060248401525af19b8c15613e51575f905f9d613e8e575b506001548a51630dfe168160e01b8152908916988a8289818d5afa918215613e84575f92613e65575b50807f000000000000000000000000b47fa3fda09e61a68a8089e1f4d0f44bd993e6b9169116145f14613e5b57613d0a909c9a5b600654613b2c565b600655613d198c601454613a32565b601455613d288a601354613a32565b6013558851918252838286818a5afa918215613e51579088915f955f94613e29575b5050858a518099819382525afa958615613e1f575f96613df0575b5085613d91575b505050505050905f80516020615d068339815191529185825191858352820152a19190565b91816131a792613da39416911661396f565b828102928184041490151715613ddd57505f80516020615d06833981519152939291613dce91613982565b60155590915f80808080613d6c565b601190634e487b7160e01b5f525260245ffd5b9095508681813d8311613e18575b613e08818361394c565b810103126105655751945f613d65565b503d613dfe565b88513d5f823e3d90fd5b613e459396508091929450903d1061324a57613237818361394c565b50939093915f80613d4a565b89513d5f823e3d90fd5b99613d0a90613d02565b613e7d9192508b3d8d116108495761083b818361394c565b905f613cce565b8c513d5f823e3d90fd5b809d508a8092503d8311613ebc575b613ea7818361394c565b8101031261056557878c519c01519b5f613ca5565b503d613e9d565b613ed990883d8a1161059057610580818361394c565b505f613c6c565b5050505050505050505090505f905f90565b601187634e487b7160e01b5f525260245ffd5b50505050505050505050505090505f905f90565b601555505f9a508a9998505050505050505050565b505050505050505050505090505f905f90565b9096508881813d8311613f69575b613f59818361394c565b810103126105655751955f613ba8565b503d613f4f565b8a513d5f823e3d90fd5b905081613f94929a503d8b1161324a57613237818361394c565b509890985f613b7e565b86513d5f823e3d90fd5b5f91508190565b600260409160018060a01b0384511660018060a01b03198254161781556020840151600182015501910151151560ff80198354169116179055565b9284926080959260018060a01b03168552602085015260606040850152816060850152848401375f828201840152601f01601f1916010190565b9081526001600160a01b03909116602082015260400190565b600454908082106132c45761405191613b2c565b600455565b801561409b576001808201808311612bfd5760011c90825b83831061407b5750505090565b909192506140928361408d8184613982565b613a32565b821c919061406e565b505f90565b604051906140ad82613902565b5f60e0838281528260208201528260408201528260608201528260808201528260a08201528260c08201520152565b8051156138d35760200190565b8051600110156138d35760400190565b80518210156138d35760209160051b010190565b90610171808302908382041483151715612bfd5761271061413091048093613b2c565b90565b61415e916020916141438261403d565b60405163a9059cbb60e01b81529384928392600484016139b8565b03815f7f000000000000000000000000b47fa3fda09e61a68a8089e1f4d0f44bd993e6b96001600160a01b03165af190811561055a575f916141a3575b50156107d157565b6141bc915060203d60201161059057610580818361394c565b5f61419b565b90600554908082106132c4576141fc926141de82602094613b2c565b60055560405163a9059cbb60e01b81529384928392600484016139b8565b03815f7f00000000000000000000000018e89dfc638a61ec010fb269f0c7289d71555d696001600160a01b03165af190811561055a575f916141a35750156107d157565b80549190600160401b831015612dc9578261426391600161427b950181556138be565b90919082549060031b91821b915f19901b1916179055565b565b9392919061428c600a54613aab565b809281600a55819685905f5b80156143e7575060409586928351926142b084613902565b898452600760208501948986528681019388855260608201905f82526080830190815260a083019143835260c084019660018060a01b03998a8098169b8c8a5260e087019a8d8c525f52600b6020525f209551865551600186015551600285015551600384015551600483015551600582015582600682019451169360018060a01b031994858254161790550192511690825416179055815f52600c60205261435b84875f20614240565b614366600d54613aab565b600d557f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8141693843b15610565575f9460a49386928851998a978896637c9441d160e11b8852600488015260248701526044860152606485015260848401525af19081156143de57506143d55750565b61427b9061391e565b513d5f823e3d90fd5b929350906127109004613cc390818102918183041490151715612bfd5761440e9092613aab565b9085939291614298565b60095460ff81168061448b575b61442c5750565b614434615440565b906004548281111561444557505050565b7ff71aa291a8223231f47fc103fc8005baa4baabeb91dcd95b0c863f978895ff189261010060609361ffff191617600955604051916001835260208301526040820152a1565b5060ff8160081c1615614425565b905f8092805f52602090600b82526040805f209160018060a01b039081600685015416156147a4576010855260ff835f205416156147f857805f526011855260ff835f2054166147e7575f5260118452815f20600160ff198254161790556144ff613b39565b505081516375c8e9bf60e11b8152600490858183817f0000000000000000000000009526b745052d259add5dc79bcdc61d0edc68f84b87165afa9081156146d1575f916147b2575b50670de0b6b3a764000061456968056bc75e2d6310000092600288015461396f565b04106147a457601354906115b391828102908082048414811517156146eb57620186a080920490816146fe575b5050601454928084029084820414841517156146eb57049182614613575b505050841580159061460a575b6145ce575b505050509190565b60077ff36c60116d16bc34ac57a184a0335dc52564c0a24fd629b0d22a5d6b86b0fa85930154169286825191868352820152a25f8080806145c6565b508615156145c1565b90809293995054908382106146db5761463991614631858093613b2c565b601455613b2c565b815561466085838a6007880154168651938492839263a9059cbb60e01b84528784016139b8565b03815f8d7f000000000000000000000000b47fa3fda09e61a68a8089e1f4d0f44bd993e6b9165af19081156146d1575f916146b4575b50156146a65750955f80806145b4565b82516312171d8360e31b8152fd5b6146cb9150863d881161059057610580818361394c565b5f614696565b84513d5f823e3d90fd5b8451631e9acf1760e31b81528390fd5b601183634e487b7160e01b5f525260245ffd5b819299509061470c91613b2c565b60135561473487828660078a0154168851938492839263a9059cbb60e01b84528884016139b8565b03815f897f0000000000000000000000000deed1486bc52aa0d3e6f8849cec5add6598a162165af1908115613f9e575f91614787575b501561477857965f80614596565b5083516312171d8360e31b8152fd5b61479e9150883d8a1161059057610580818361394c565b5f61476a565b50505050505090505f905f90565b90508581813d83116147e0575b6147c9818361394c565b810103126105655751670de0b6b3a7640000614547565b503d6147bf565b8251630e50c52360e21b8152600490fd5b8251623c461f60e31b8152600490fd5b8015612bfd575f190190565b600154604051630240bc6b60e21b81526001600160a01b039182169291606082600481875afa93841561055a575f925f956148e0575b5090602060049260405193848092630dfe168160e01b82525afa91821561055a575f926148bf575b50807f000000000000000000000000b47fa3fda09e61a68a8089e1f4d0f44bd993e6b9169116145f146148af576001600160701b03908116921690565b6001600160701b03928316921690565b6148d991925060203d6020116108495761083b818361394c565b905f614872565b600492919550602093506149029060603d60601161324a57613237818361394c565b509390939591925061484a565b6001600160401b038111612dc95760051b60200190565b906149308261490f565b61493d604051918261394c565b828152809261494e601f199161490f565b0190602036910137565b93909796959260c0959260e086019986526020860152604085015260018060a01b03166060840152608083015260a08201520152565b9397929690959491945f985f986149bb5f996149b5816149b05f9c5f9c613b2c565b613b2c565b90613a32565b915f80925f926153e1575b50801515806153d8575b6152f6575b61523857505060ff6009541680615226575b8061521d575b6151d1575b80158015614a02575b5050505050565b95919293956111b15760405163095ea7b360e01b815260208180614a4a857f000000000000000000000000165c3410fc91ef562c50559f7d2289febed552d9600484016139b8565b03815f7f0000000000000000000000000deed1486bc52aa0d3e6f8849cec5add6598a1626001600160a01b03165af1801561055a576151b2575b50604051614a9181613931565b6002815260403660208301377f0000000000000000000000000deed1486bc52aa0d3e6f8849cec5add6598a1626001600160a01b0316614ad0826140dc565b52614ada816140e9565b7f000000000000000000000000b47fa3fda09e61a68a8089e1f4d0f44bd993e6b96001600160a01b0316905242610e10810110612bfd57906040519182916338ed173960e01b835260048301526001602483015260a0604483015280518060a4840152602060c484019201905f5b8181106151905750503060648401525042610e100160848301525f919081900381837f000000000000000000000000165c3410fc91ef562c50559f7d2289febed552d96001600160a01b03165af1801561055a57614bad915f9161516e575b506140e9565b5180156111b157614bcc91614bc482600454613a32565b600455613a32565b93614bd5614814565b838510156149fb57614be785846140f9565b5191825f52600b60205260405f209160018060a01b03600684015416158015615162575b61515557670de0b6b3a764000090818102918183041490151715612bfd57600491614c3591613982565b9101541161514f575f818152600b6020526040812060068101549091906001600160a01b031615610e3757600382015491600281015491600f83105f14615136576002820154925f60028401555b60018060a01b036006840154169182875f52600f6020528560ff600260405f200154165f146150fd575050865f52600f602052614ce760208660018060a01b0360405f205416956040516323b872dd60e01b81529384928392309060048501613a89565b03815f7f00000000000000000000000018e89dfc638a61ec010fb269f0c7289d71555d696001600160a01b03165af1801561055a576150de575b506005614d2f868254613a32565b9055614d3a85615655565b91614d448661403d565b60405163a9059cbb60e01b815260208180614d638789600484016139b8565b03815f7f0000000000000000000000000deed1486bc52aa0d3e6f8849cec5add6598a1626001600160a01b03165af190811561055a575f916150bf575b50156107d157614db283600754613a32565b600755600f81101561503b577f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8146001600160a01b03163b1561054357604051637ad80e6360e01b815260048101899052602481018790528281604481837f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8146001600160a01b03165af18015610cd357908391615027575b50505b600f80821015918261500d575b1090614fff575b614ece575b509260c0926001979695925f80516020615cc683398151915295614e87614418565b600260048401549301549360405195865260208601526040850152888060a01b03166060840152608083015260a0820152a2614ec1614814565b9290945b01939091614bd5565b6006840154614ee6906001600160a01b03168861545c565b6006840180546001600160a01b031990811690915560078501805490911690557f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8146001600160a01b03163b15610535576040516311cead4b60e31b8152600481018890528181602481837f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8146001600160a01b03165af1801561053857614feb575b50509260c0926001979695925f80516020615cc683398151915295600d805480614fd9575b5050875f80516020615ce683398151915260206002860154604051908152a2929550929596975092614e65565b614fe290614808565b90555f80614fac565b614ff5829161391e565b6105355780614f87565b50600a600285015410614e60565b61501d610ca86003890154613aab565b6004880155614e59565b6150309061391e565b61054357815f614e49565b60048501547f000000000000000000000000a0044761afc6d07cd16b46d8859a948e0e9cb8146001600160a01b0316803b1561056557615097915f9189838d60405196879586948593631e45ec4360e31b85526004850161384f565b03925af1801561055a576150ac575b50614e4c565b6150b791925061391e565b5f905f6150a6565b6150d8915060203d60201161059057610580818361394c565b5f614da0565b6150f69060203d60201161059057610580818361394c565b505f614d21565b9093600e60205260405f2060ff600282015416615122575b50614ce7916020916109aa565b546001600160a01b03169450614ce7615115565b60038304926151458482613b2c565b6002840155614c83565b806149fb565b9392509460019150614ec5565b50600283015415614c0b565b61518a91503d805f833e615182818361394c565b8101906155dc565b5f614ba7565b82516001600160a01b0316845285945060209384019390920191600101614b48565b6151ca9060203d60201161059057610580818361394c565b505f614a84565b610e6a808202908282041482151715612bfd576127109004806151f5575b506149f2565b61520e9198506152179750615208614814565b91615997565b80979198613b2c565b5f6151ef565b508015156149ed565b50600454615232615440565b106149e7565b96509650505050509394925081615256575b505091905f905f905f90565b6020826152749261526c61414395989698615655565b94859261403d565b03815f7f0000000000000000000000000deed1486bc52aa0d3e6f8849cec5add6598a1626001600160a01b03165af190811561055a575f916152d7575b50156107d1576152cf916152c782600754613a32565b600755613a32565b915f8061524a565b6152f0915060203d60201161059057610580818361394c565b5f6152b1565b9a50919a50898b105f146153d1578a915b6153158b612ad7858561396f565b9b60405163a9059cbb60e01b8152602081806153358887600484016139b8565b03815f7f0000000000000000000000000deed1486bc52aa0d3e6f8849cec5add6598a1626001600160a01b03165af190811561055a575f916153b2575b50156107d1578c9b8493846007549061538a91613a32565b600755849d61539891613b2c565b936153a291613b2c565b936153ac91613b2c565b926149d5565b6153cb915060203d60201161059057610580818361394c565b5f615372565b8991615307565b508315156149d0565b925050506040810151602082015191608060018060a01b0391015116905f6149c6565b905f905b8082111561541557505090565b90916127109004613cc390818102918183041490151715612bfd5761543a9092613aab565b90615408565b6005546a115eec47f6cf7e35000000908103908111612bfd5790565b906001600160a01b03168061546f575050565b5f52600c60205260405f205f5b815480821015615516578361549183856138be565b919054600392831b1c146154a957505060010161547c565b9293505f19929190838101908111612bfd576154d8926154cc61426392876138be565b9054911b1c91856138be565b815480156155025701906154ff6154ef83836138be565b8154905f199060031b1b19169055565b55565b634e487b7160e01b5f52603160045260245ffd5b50505050565b906001600160a01b0390811680615550575b50909116908161553c575050565b61427b915f52600c60205260405f20614240565b5f52600c60205260405f20925f5b8454808210156155d1578461557383886138be565b919054600392831b1c1461558b57505060010161555e565b90915f96939495961992838101908111612bfd576155b0926154cc61426392876138be565b815480156155025701906155c76154ef83836138be565b555b91905f61552e565b5050909192506155c9565b6020908181840312610565578051906001600160401b03821161056557019180601f840112156105655782516156118161490f565b9361561f604051958661394c565b818552838086019260051b820101928311610565578301905b828210615646575050505090565b81518152908301908301615638565b9081156111b15761566e615667614814565b90846158f2565b605f810290808204605f1490151715612bfd576040805163095ea7b360e01b815290937f000000000000000000000000165c3410fc91ef562c50559f7d2289febed552d9916001600160a01b03917f000000000000000000000000b47fa3fda09e61a68a8089e1f4d0f44bd993e6b9831691602091908281806156f5858a600484016139b8565b03815f885af18015613e5157615802575b5087519161571383613931565b600283528083019389368637615728846140dc565b52615732836140e9565b857f0000000000000000000000000deed1486bc52aa0d3e6f8849cec5add6598a162169052610e10420192834211612bfd57916064979593928a979597519889976338ed173960e01b895260a489019360048a015204602488015260a060448801525180915260c4860196915f5b8281106157e957505050508391825f96879330606484015260848301520393165af19081156157df576157db9293505f9161516e57506140e9565b5190565b83513d5f823e3d90fd5b83518616895297810197899750928101926001016157a0565b61581890833d851161059057610580818361394c565b505f615706565b811592670de0b6b3a764000092918383028381048514861715612bfd57615847828492613982565b10156158e9576107cd9485840295848704141715612bfd5761586d83916158729361396f565b61396f565b916103e89182840293808504841490151715612bfd57806158929161396f565b828102928184041490151715612bfd576158ac9204613b2c565b906158b7818061396f565b90610f949280840293840403612bfd576149b06131a76107ca946158da94613a32565b0460018101809111612bfd5790565b50505050505f90565b916103e592838102938185041490151715612bfd57615911908361396f565b906103e890818102918183041490151715612bfd576141309261593391613a32565b90613982565b90815f52600f60205260ff600260405f200154165f1461596e57505f908152600f60205260409020546001600160a01b031690565b919050600e60205260405f2060ff6002820154166159895750565b546001600160a01b03169150565b929060ff6009541615615c9c576159ac615440565b600490815481811115615c9057906159c391613b2c565b946159d285612ad7858461396f565b928360011b9584870460021485151715615c7d57878711615c5e575b5050600283108015615c54575b615c49576040805163095ea7b360e01b80825260209592947f000000000000000000000000165c3410fc91ef562c50559f7d2289febed552d9926001600160a01b03917f000000000000000000000000b47fa3fda09e61a68a8089e1f4d0f44bd993e6b991898180615a708a8a8388016139b8565b03815f8888165af18015613e5157615c2c575b507f0000000000000000000000000deed1486bc52aa0d3e6f8849cec5add6598a162938851908152898180615abb8b8a8784016139b8565b03815f888a165af18015613e5157615c0f575b50605f8602868104605f1487151715615bfc57605f880290888204605f14891517156146eb57610e10420192834211613ddd57925f9492615b329260609a999897958d519c8d9b8c9a8b9862e8e33760e81b8a526064803098049604948a016139ee565b0393165af1908115615bf2575f915f915f91615bc8575b50615b5f90615b578461403d565b600654613a32565b6006558196819610155f14615b9b577f39295663e152e1c6c375f069996f3e04a36a00f4173c65e4095b60d6ba0a882c938351928352820152a1565b7f0e2afe1247bc1a003b1493f74608702bd5a78b15077949481b7269aac1112d54938351928352820152a1565b9050615b5f9250615be8915060603d60601161327f5761326e818361394c565b9192909190615b49565b82513d5f823e3d90fd5b601182634e487b7160e01b5f525260245ffd5b615c25908a3d8c1161059057610580818361394c565b505f615ace565b615c42908a3d8c1161059057610580818361394c565b505f615a83565b505f94508493505050565b50600281106159fb565b9350945050615c768594612ad78760011c948561396f565b5f806159ee565b601184634e487b7160e01b5f525260245ffd5b505f9550859450505050565b505f925082915056fecb38ab18a1a079716fa2ffd5466032ff737a3cf54f5a2cf5d40969a81fee9659d345c20e4420993f6a7f7ad31c5184505afc149305d938b7d118aef1ff393fbd489856ffe28db7908da3a8af35a16e6c12c8cfe70f8106f2604fdd844daf057abe266970c8fc2284ffe75147697f09e015f1a7c133d0d417d36e0ac7fbfa4192a164736f6c6343000818000a