ETH Price: $2,869.23 (-2.71%)

Contract

0x8442B217b9ae078f42bf77Fc04e23e33e320D3fc

Overview

ETH Balance

0 ETH

ETH Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Block
From
To

There are no matching entries

Please try again later

Parent Transaction Hash Block From To
View All Internal Transactions

Cross-Chain Transactions
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
PrintrCore

Compiler Version
v0.8.27+commit.40a35a09

Optimization Enabled:
Yes with 2000 runs

Other Settings:
prague EvmVersion
File 1 of 47 : PrintrCore.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import { IPrintrCore } from "./interfaces/IPrintrCore.sol";

import { PrintrPrinting } from "./printr/PrintrPrinting.sol";
import { PrintrStorage } from "./printr/PrintrStorage.sol";
import { PrintrTrading } from "./printr/PrintrTrading.sol";

/**
 * @title PrintrCore Implementation Contract
 * @notice Core contract for the Printr protocol that serves as the main entry point
 * @dev Combines functionality from multiple specialized contracts through inheritance
 *      and implements a proxy pattern with fallback delegation to the teleport contract
 */
contract PrintrCore is IPrintrCore, PrintrStorage, PrintrPrinting, PrintrTrading {

    /// @notice Version of the Printr protocol
    string public constant VERSION = "v1.0.0";

    /// @notice Address of the teleport contract
    address public immutable teleport;

    /**
     * @notice Initializes the Printr contract with required dependencies
     * @dev Sets up core protocol addresses and disables initializers for upgradeable pattern
     * @param storageParams Deployment parameters containing all required addresses and configurations
     * @param teleport_ Address of the teleport contract
     * @custom:oz-upgrades-unsafe-allow constructor Used safely with initializer pattern
     */
    constructor(
        DeploymentParams memory storageParams,
        address teleport_
    ) PrintrStorage(storageParams) {
        teleport = teleport_;
    }

    /**
     * @dev Fallback function that delegates calls to the teleport contract
     */
    fallback() external payable virtual {
        address teleportImplementation = teleport;
        assembly {
            // Copy msg.data. We take full control of memory in this inline assembly
            // block because it will not return to Solidity code. We overwrite the
            // Solidity scratch pad at memory position 0.
            calldatacopy(0, 0, calldatasize())

            // Call the implementation.
            // out and outsize are 0 because we don't know the size yet.
            let result := delegatecall(gas(), teleportImplementation, 0, calldatasize(), 0, 0)

            // Copy the returned data.
            returndatacopy(0, 0, returndatasize())

            switch result
            // delegatecall returns 0 on error.
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }

    receive() external payable { }

}

File 2 of 47 : IPrintrCore.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import { IPrintrPrinting } from "./printr/IPrintrPrinting.sol";
import { IPrintrStorage } from "./printr/IPrintrStorage.sol";
import { IPrintrTrading } from "./printr/IPrintrTrading.sol";

/**
 * @title IPrintrCore
 * @notice Comprehensive interface combining all Printr protocol functionality
 * @dev Aggregates specialized interfaces for storage, printing, trading, cross-chain, and admin features
 *
 * Components:
 * - IPrintrStorage: Core storage and state management
 * - IPrintrPrinting: Token printing and curve management
 * - IPrintrTrading: Token trading and price calculations
 */
interface IPrintrCore is IPrintrStorage, IPrintrPrinting, IPrintrTrading { }

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

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

import { ILiquidityModule } from "../interfaces/liquidity/ILiquidityModule.sol";
import { IPrintrPrinting } from "../interfaces/printr/IPrintrPrinting.sol";
import { IPrintrTrading } from "../interfaces/printr/IPrintrTrading.sol";
import { IPrintrDev } from "../interfaces/telecoin/IPrintrDev.sol";

import { IPrintrTeleportingTelecoin } from "../interfaces/telecoin/IPrintrTeleportingTelecoin.sol";
import { ITelecoin } from "../interfaces/telecoin/ITelecoin.sol";
import { ITelecoinFactory } from "../interfaces/telecoin/ITelecoinFactory.sol";

import { AddressBytes32 } from "../libs/AddressBytes32.sol";
import { Bytes32ToString } from "../libs/Bytes32String.sol";

import { PrintrStorage } from "./PrintrStorage.sol";

/**
 * @title Printr Printing
 * @notice Implementation of token printing and curve management functionality
 * @dev Implements UUPS proxy pattern with namespaced storage for upgradeability
 *      Uses ERC7201 for structured storage layout
 *      All price calculations use PRECISION (1e18) for accurate floating point math
 *      Supports multi-chain token deployment through token factory integration
 */
abstract contract PrintrPrinting is IPrintrPrinting, PrintrStorage {

    using Bytes32ToString for bytes32;
    using AddressBytes32 for bytes32;

    bytes1 public constant EVM_ADDRESS_PREFIX = 0x0;

    // Struct to hold local variables and avoid stack too deep error
    struct PrintingData {
        bytes32 telecoinId;
        uint256 chainIndex;
        address creatorAddress;
        uint256 completionPrice;
    }

    /**
     * @notice Prints a new token with a bonding curve across multiple chains
     * @dev Deploys token with bonding curve on the current chain and initiates remote deployments
     *      Sets up liquidity pool and performs initial token purchase
     * @param initialSpending Initial amount of base currency to commit. Pass type(uint256).max to use
     *                        the maximum available amount (all approved tokens or all sent ETH)
     * @param telecoinParams Parameters for the token curve deployment
     * @return tokenAddress Address of the newly created token
     * @return telecoinId Unique identifier for cross-chain deployment
     */
    function print(
        uint256 initialSpending,
        TelecoinParams calldata telecoinParams
    ) external payable whenNotPaused returns (address tokenAddress, bytes32 telecoinId) {
        Storage storage $ = _storage();
        PrintingData memory tokenData;

        tokenData.telecoinId = _getTelecoinId(telecoinParams);
        tokenData.chainIndex = _getCurrentChainIndex(telecoinParams.chains);
        tokenData.creatorAddress = _getCreatorEvmAddress(telecoinParams.creatorAddresses);
        UnpackedParams memory unpacked = _unpackParams(telecoinParams.packedParams);

        // Validate parameter lengths
        if (telecoinParams.chains.length != telecoinParams.basePairs.length) {
            revert InvalidLength();
        }
        if (telecoinParams.chains.length != telecoinParams.basePrices.length / 16) {
            revert InvalidLength();
        }

        for (uint256 i; i < telecoinParams.chains.length; ++i) {
            uint256 basePrice = _getBasePriceAt(telecoinParams.basePrices, i);
            if (basePrice == 0) {
                revert InvalidBasePrices();
            }
            if (telecoinParams.basePairs[i] == bytes32(0)) {
                revert InvalidBasePairs();
            }
        }

        // Use the universal token ID
        telecoinId = tokenData.telecoinId;
        // isTeleporting is false for single chain token deployed on that chain
        tokenAddress = _deployToken(
            telecoinParams,
            tokenData.telecoinId,
            unpacked.completionThreshold == 0 ? tokenData.creatorAddress : address(treasury),
            telecoinParams.chains.length == 1 && tokenData.chainIndex == 0,
            tokenData.chainIndex != type(uint256).max
        );

        // Skip curve creation if current chain is not in the list or all supply is minted to creator
        if (tokenData.chainIndex == type(uint256).max || unpacked.completionThreshold == 0) {
            _refundNativeValue(tokenData.creatorAddress);
            return (tokenAddress, telecoinId);
        }

        Curve memory curve;
        {
            address basePair = telecoinParams.basePairs[tokenData.chainIndex].toAddress();
            uint256 basePrice = _getBasePriceAt(telecoinParams.basePrices, tokenData.chainIndex);
            uint256 maxSupply = 10 ** unpacked.maxTokenSupplyE * PRECISION;

            // Validate parameters
            if (unpacked.initialPrice == 0) {
                revert InvalidInitialPrice();
            }
            if (unpacked.completionThreshold >= BIPS_SCALAR) {
                revert TooHighThreshold();
            }
            try IERC20(basePair).balanceOf(address(treasury)) returns (uint256) { }
            catch {
                revert InvalidBasePairs();
            }

            // Calculate curve parameters
            unpacked.completionThreshold =
                unpacked.completionThreshold * PRECISION / BIPS_SCALAR / telecoinParams.chains.length;
            unpacked.initialPrice = PRECISION * unpacked.initialPrice / basePrice;

            {
                // Calculate required initial spending adjusted for chain-specific base price
                uint256 convertedInitialSpending =
                    PRECISION * unpacked.initialBuySpending / basePrice / telecoinParams.chains.length;

                try IERC20Metadata(basePair).decimals() returns (uint8 decimals) {
                    if (decimals < 18) {
                        convertedInitialSpending = convertedInitialSpending / 10 ** (18 - decimals);
                        unpacked.initialPrice = unpacked.initialPrice / 10 ** (18 - decimals);
                    }
                    if (decimals > 18) {
                        convertedInitialSpending = convertedInitialSpending * 10 ** (decimals - 18);
                        unpacked.initialPrice = unpacked.initialPrice * 10 ** (decimals - 18);
                    }
                } catch {
                    revert InvalidBasePairDecimals();
                }

                // Handle max amount for initial spending
                if (initialSpending == type(uint256).max) {
                    initialSpending = _getMaxAmount(basePair, msg.sender);
                }

                if (initialSpending < convertedInitialSpending) {
                    revert InsufficientInitialBuy();
                }
            }

            {
                uint256 initialTokenReserve = maxSupply / telecoinParams.chains.length;
                uint256 virtualReserve = initialTokenReserve * unpacked.initialPrice / PRECISION;
                uint256 completionTokenReserve =
                    initialTokenReserve - maxSupply * unpacked.completionThreshold / PRECISION;
                // Early division prevents overflow with small base prices
                tokenData.completionPrice = (PRECISION * virtualReserve / completionTokenReserve) * initialTokenReserve
                    / completionTokenReserve;

                // Initialize bonding curve parameters
                curve = Curve({
                    basePair: basePair,
                    totalCurves: uint16(telecoinParams.chains.length),
                    maxTokenSupplyE: unpacked.maxTokenSupplyE,
                    virtualReserveE: 0,
                    virtualReserve: 0,
                    reserve: 0,
                    completionThreshold: uint64(unpacked.completionThreshold)
                });

                // Compress virtualReserve if it exceeds uint64.max
                while (virtualReserve > type(uint64).max) {
                    virtualReserve /= 10;
                    curve.virtualReserveE++;
                }
                curve.virtualReserve = uint64(virtualReserve);
            }

            emit CurveCreated(tokenData.creatorAddress, tokenAddress, telecoinId);
            emit IPrintrTrading.TokenTrade(
                tokenAddress, tokenData.creatorAddress, true, 0, 0, unpacked.initialPrice, 0, 0
            );
        }

        // Mint Dev NFT to the creator
        IPrintrDev(printrDev).mint(telecoinId, tokenData.creatorAddress);

        // Store curve configuration
        $.curves[tokenAddress] = curve;

        // Create and configure liquidity pool
        {
            (bool success, bytes memory data) = liquidityModule.delegatecall(
                abi.encodeWithSelector(
                    ILiquidityModule.createPool.selector, curve, tokenAddress, tokenData.completionPrice
                )
            );
            if (!success) {
                // Bubble up the actual liquidity module error
                if (data.length == 0) {
                    revert PoolCreationFailed();
                }
                assembly {
                    revert(add(data, 0x20), mload(data))
                }
            }
            address pool = abi.decode(data, (address));
            IPrintrTeleportingTelecoin(tokenAddress).setRestrictedPool(pool);
        }

        // Perform initial token purchase if specified
        if (initialSpending != 0) {
            uint256 tokenAmount = _quoteTokenAmount(curve, initialSpending, tradingFee);
            _buy(
                curve,
                IPrintrTrading.TradeParams(
                    msg.sender, tokenData.creatorAddress, tokenAddress, tokenAmount, 0, tradingFee
                )
            );
        }

        _refundNativeValue(tokenData.creatorAddress);
    }

    /**
     * @dev Deploys a new token contract via the TelecoinFactory
     * @param telecoinParams TelecoinParams struct containing deployment configuration
     * @param telecoinId Unique identifier for deterministic deployment address calculation
     * @param isMainTelecoin Boolean indicating whether token is main telecoin (true) or teleporting telecoin (false)
     * @return tokenAddress Address of the newly deployed token contract
     */
    function _deployToken(
        TelecoinParams calldata telecoinParams,
        bytes32 telecoinId,
        address mintTo,
        bool isMainTelecoin,
        bool hasLocalSupply
    ) internal returns (address tokenAddress) {
        UnpackedParams memory unpacked = _unpackParams(telecoinParams.packedParams);

        // Build token deployment params
        ITelecoin.TelecoinDeployParams memory deployParams = ITelecoin.TelecoinDeployParams({
            telecoinId: telecoinId,
            name: telecoinParams.name.toTrimmedString(),
            symbol: telecoinParams.symbol.toTrimmedString(),
            maxSupply: 10 ** unpacked.maxTokenSupplyE * PRECISION,
            printr: address(this),
            interchainTokenService: interchainTokenService,
            itsTokenManager: address(0), // filled by factory
            interchainTokenId: _getInterchainTokenId(telecoinId, telecoinParams.chains[0].toTrimmedString())
        });

        address factory = isMainTelecoin ? mainTelecoinFactory : teleportingTelecoinFactory;
        uint256 initialSupply =
            hasLocalSupply ? 10 ** unpacked.maxTokenSupplyE * PRECISION / telecoinParams.chains.length : 0;

        (bool success, bytes memory data) = factory.delegatecall(
            abi.encodeWithSelector(
                ITelecoinFactory.deployToken.selector,
                deployParams,
                mintTo,
                initialSupply // initialSupply
            )
        );

        if (!success) {
            assembly {
                revert(add(data, 32), data)
            }
        }

        tokenAddress = abi.decode(data, (address));

        // Emit with universal telecoinId
        emit TelecoinPrinted(tokenAddress, telecoinId);
    }

    /**
     * @dev Returns the index of the current chain in the provided chains array
     * @param chains Array of chain names to search through
     * @return index Index of the current chain in the array, or max uint256 if not found
     */
    function _getCurrentChainIndex(
        bytes32[] memory chains
    ) internal view returns (uint256 index) {
        if (chains.length == 0) {
            revert InvalidLength();
        }

        for (uint256 i; i < chains.length; ++i) {
            if (keccak256(bytes(chains[i].toTrimmedString())) == currentChainHash) {
                return i;
            }
        }

        return type(uint256).max;
    }

    /**
     * @dev Extracts EVM creator address from encoded creator addresses
     * @param creatorAddresses Encoded creator addresses bytes (20 bytes for EVM-only or prefixed format)
     * @return creatorEvmAddress The extracted EVM address of the token creator
     */
    function _getCreatorEvmAddress(
        bytes calldata creatorAddresses
    ) internal pure returns (address) {
        // EVM only address encoded as 20 bytes
        if (creatorAddresses.length == 20) {
            return address(bytes20(creatorAddresses));
        }

        if (creatorAddresses.length > 20 && creatorAddresses[0] == 0x0) {
            return address(bytes20(creatorAddresses[1:21]));
        }

        revert InvalidCreatorAddress();
    }

    /**
     * @dev Retrieves the base price for a specific chain
     * @param basePrices Encoded base prices for all chains
     * @param index Index of the chain to retrieve the price for
     * @return basePrice Base price for the specified chain
     */
    function _getBasePriceAt(
        bytes calldata basePrices,
        uint256 index
    ) internal pure returns (uint256) {
        return uint128(bytes16(basePrices[index * 16:(index + 1) * 16]));
    }

}

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

import "@openzeppelin/contracts-upgradeable/utils/PausableUpgradeable.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import { ITreasury } from "../interfaces/ITreasury.sol";
import { IPrintrStorage } from "../interfaces/printr/IPrintrStorage.sol";
import { IPrintrTrading } from "../interfaces/printr/IPrintrTrading.sol";
import { IPrintrTeleportingTelecoin } from "../interfaces/telecoin/IPrintrTeleportingTelecoin.sol";
import { ITelecoinFactory } from "../interfaces/telecoin/ITelecoinFactory.sol";

import { Bytes32ToString } from "../libs/Bytes32String.sol";

/**
 * @title PrintrStorage Base Contract
 * @notice Base storage contract for creating and managing tokens with linear bonding curves
 * @dev Implements upgradeable pattern with ERC7201 namespaced storage for state isolation
 *      Provides immutable configuration and shared constants for all Printr contracts
 */
abstract contract PrintrStorage is IPrintrStorage, PausableUpgradeable {

    using Bytes32ToString for bytes32;

    /// @notice Scalar used for price calculations to handle decimals
    /// @dev Set to 1e18 to maintain precision in calculations
    uint256 public constant PRECISION = 10 ** 18;

    /// @notice Scalar for fee calculations (100% = 10000)
    uint256 public constant BIPS_SCALAR = 10_000;

    /// @dev Salt for generating interchain token IDs
    bytes32 internal constant PREFIX_INTERCHAIN_TOKEN_ID = keccak256("its-interchain-token-id");
    /// @dev Salt for generating custom token deployment salts
    bytes32 internal constant PREFIX_CUSTOM_TOKEN_SALT = keccak256("custom-token-salt");

    /**
     * @notice Main storage structure for the Printr contract
     * @custom:storage-location erc7201:printr.storage.PrintrStorage
     */
    struct Storage {
        /// @notice Mapping of token addresses to their configuration and state
        mapping(address => Curve) curves;
        /// @notice Mapping of base pair addresses to their collected protocol fees
        mapping(address => uint256) collectedFees;
        /// @notice Mapping of token addresses to their liquidity lock parameters
        mapping(address => uint256[2]) liquidityLocks;
        /// @notice Bitmap of globally enabled teleport protocols (bit position = TeleportProtocol enum value)
        bytes32 teleportProtocolWhitelist;
        /// @notice Mapping from telecoin ID to bitmap of disabled protocols (blacklist)
        mapping(bytes32 => bytes32) telecoinProtocolBlacklist;
    }

    /// @dev ERC7201 storage location for Storage
    /// @dev keccak256(abi.encode(uint256(keccak256("printr.storage.PrintrStorage")) - 1)) &
    /// ~bytes32(uint256(0xff))
    bytes32 private constant PRINTR_STORAGE_LOCATION =
        0x4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb00;

    /**
     * @dev Retrieves pointer to the contract's storage location
     * @return $ Storage pointer to PrintrStorage
     */
    function _storage() internal pure returns (Storage storage $) {
        assembly {
            $.slot := PRINTR_STORAGE_LOCATION
        }
    }

    /// @notice Hash of the current chain's name, used for cross-chain deployment routing
    bytes32 public immutable currentChainHash;

    /// @notice Trading fee percentage in basis points (BIPs)
    uint16 public immutable tradingFee;
    /// @notice Treasury contract that holds all tokens
    ITreasury public immutable treasury;
    /// @notice Legacy treasury contract for migration (address(0) if no migration needed)
    ITreasury public immutable legacyTreasury;

    /// @notice Address of the main token factory contract (for lock/unlock tokens)
    address public immutable mainTelecoinFactory;
    /// @notice Address of the teleporting token factory contract (for mint/burn tokens)
    address public immutable teleportingTelecoinFactory;
    /// @notice Address of the Interchain Token Service contract
    address public immutable interchainTokenService;
    /// @notice Address of the ITS factory contract
    address public immutable itsFactory;
    /// @notice Address of the wrapped native token contract
    address public immutable wrappedNativeToken;
    /// @notice Dev NFT contract
    address public immutable printrDev;
    /// @notice First legacy dev NFT contract for migration from V1 - oldest original (address(0) if no migration
    /// needed)
    address public immutable legacyPrintrDev;
    /// @notice Second legacy dev NFT contract for migration from V2 - without base64 fix (address(0) if no migration
    /// needed)
    address public immutable legacyPrintrDev2;
    /// @notice Address of the liquidity module contract
    address public immutable liquidityModule;
    /// @notice Address of the locker contract
    address public immutable locker;
    /// @notice Address of the CREATE3 deployer used for deterministic LZChannel deployments
    address public immutable create3Deployer;

    // Fee distribution addresses and configuration
    /// @notice Memecoin Growth Fund address
    address public immutable growthFund;
    /// @notice $PRINT Buyback address
    address public immutable buybackFund;
    /// @notice Team Treasury address
    address public immutable teamTreasuryFund;
    /// @notice Staking address for token LP fees
    address public immutable stakingFund;

    // Fee distribution percentages (immutable for gas efficiency)
    /// @notice Fee percentage for growth fund in basis points (BIPs)
    uint256 public immutable feePercentGrowth;
    /// @notice Fee percentage for buyback in basis points (BIPs)
    uint256 public immutable feePercentBuyback;
    /// @notice Fee percentage for team treasury in basis points (BIPs)
    uint256 public immutable feePercentTeam;
    /// @notice Fee percentage for creator in basis points (BIPs)
    uint256 public immutable feePercentCreator;

    /**
     * @dev Constructor that sets up all immutable addresses and configuration values
     * @param params DeploymentParams struct containing:
     *        - chainName: Name of the current blockchain
     *        - treasury: Treasury contract address
     *        - tokenFactory: TelecoinFactory contract address
     *        - its: Interchain Token Service address
     *        - liquidityModule: Module for DEX interactions
     *        - Fee distribution addresses and percentages
     * @custom:oz-upgrades-unsafe-allow constructor
     */
    constructor(
        DeploymentParams memory params
    ) {
        _disableInitializers();

        currentChainHash = keccak256(bytes(params.chainName));
        treasury = ITreasury(params.treasury);
        legacyTreasury = ITreasury(params.legacyTreasury);
        mainTelecoinFactory = params.mainTelecoinFactory;
        teleportingTelecoinFactory = params.teleportingTelecoinFactory;
        interchainTokenService = params.its;
        itsFactory = params.itsFactory;
        wrappedNativeToken = params.wrappedNativeToken;
        locker = params.locker;
        liquidityModule = params.liquidityModule;
        create3Deployer = params.create3Deployer;
        growthFund = params.growthFund;
        buybackFund = params.buybackFund;
        teamTreasuryFund = params.teamTreasuryFund;
        stakingFund = params.stakingFund;
        printrDev = params.printrDev;
        legacyPrintrDev = params.legacyPrintrDev;
        legacyPrintrDev2 = params.legacyPrintrDev2;
        tradingFee = params.tradingFee;

        // Validate trading fee
        if (params.tradingFee > BIPS_SCALAR / 100) {
            revert FeeIsTooHigh(params.tradingFee);
        }

        // Set fee percentages
        feePercentGrowth = params.feePercentGrowth;
        feePercentBuyback = params.feePercentBuyback;
        feePercentTeam = params.feePercentTeam;
        feePercentCreator = params.feePercentCreator;

        // Ensure fee percentages add up to 10000 basis points (100%)
        if (feePercentGrowth + feePercentBuyback + feePercentTeam + feePercentCreator != BIPS_SCALAR) {
            revert FeePercentagesMustSum();
        }
    }

    /**
     * @notice Generates a unique identifier for a telecoin based on its parameters
     * @dev Creates a deterministic hash of the token parameters for cross-chain identification
     * @param tokenParams The TelecoinParams struct including name, symbol, pricing, and chain configurations
     * @return bytes32 The unique telecoin identifier hash
     */
    function _getTelecoinId(
        TelecoinParams calldata tokenParams
    ) internal pure returns (bytes32) {
        return keccak256(abi.encode(tokenParams));
    }

    /**
     * @notice Computes the deployed token address for a given telecoin ID
     * @dev Both factories use CREATE3 with the same salt, so they compute the same address
     * @param telecoinId The unique telecoin identifier
     * @return tokenAddress The address where the token is deployed
     */
    function _getTokenAddress(
        bytes32 telecoinId
    ) internal view returns (address tokenAddress) {
        // Both factories use CREATE3 with the same salt, so they compute the same address
        // We can query either factory - using mainTelecoinFactory here
        tokenAddress = ITelecoinFactory(mainTelecoinFactory).tokenAddress(address(this), telecoinId);
    }

    /**
     * @notice Unpacks compressed bonding curve parameters from a bytes32 value
     * @dev Efficiently extracts multiple parameters from a single storage slot using bit shifting
     * @param packedParams The compressed parameters as a bytes32 value
     * @return unpacked The unpacked parameters struct containing:
     *         - maxTokenSupplyE: Maximum token supply exponent (8 bits)
     *         - completionThreshold: Completion threshold in basis points (16 bits)
     *         - initialPrice: Initial token price in wei (112 bits)
     *         - initialBuySpending: Initial buy spending amount in wei (120 bits)
     */
    function _unpackParams(
        bytes32 packedParams
    ) internal pure returns (UnpackedParams memory unpacked) {
        unpacked.maxTokenSupplyE = uint8(uint256(packedParams));
        unpacked.completionThreshold = uint16(uint256(packedParams) >> 8);
        unpacked.initialPrice = uint112(uint256(packedParams) >> 24);
        unpacked.initialBuySpending = uint120(uint256(packedParams) >> 136);
    }

    /**
     * @notice Generates a unique interchain token ID based on the telecoinId and home chain name
     * @dev Uses a linked salt to ensure uniqueness across chains
     * @param telecoinId Universal token identifier
     * @param homeChain Name of the home chain where the token is deployed
     * @return interchainTokenId Unique identifier for the interchain token
     */
    function _getInterchainTokenId(
        bytes32 telecoinId,
        string memory homeChain
    ) internal view returns (bytes32 interchainTokenId) {
        bytes32 linkedSalt =
            keccak256(abi.encode(PREFIX_CUSTOM_TOKEN_SALT, keccak256(bytes(homeChain)), address(this), telecoinId));

        interchainTokenId = keccak256(abi.encode(PREFIX_INTERCHAIN_TOKEN_ID, address(0), linkedSalt));
    }

    /**
     * @notice Refunds any remaining native value to the recipient
     * @dev Transfers any remaining ETH to the recipient
     * @param recipient Address to receive the refund
     */
    function _refundNativeValue(
        address recipient
    ) internal {
        uint256 balance = address(this).balance;

        if (balance > 0) {
            // slither-disable-next-line arbitrary-send-eth
            (bool success,) = recipient.call{ value: balance }("");
            require(success, RefundFailed());
        }
    }

    /**
     * @notice Gets the maximum amount available for spending when amount is uint256.max
     * @dev Handles both native ETH and ERC20 tokens
     * @param token The token address to check
     * @param sender The address of the sender
     * @return The maximum amount available to spend (limited by balance and allowance)
     */
    function _getMaxAmount(
        address token,
        address sender
    ) internal view returns (uint256) {
        // If token is wrapped native and ETH was sent, use the contract's ETH balance
        if (token == wrappedNativeToken && msg.value > 0) {
            return address(this).balance;
        } else {
            uint256 balance = IERC20(token).balanceOf(sender);
            uint256 allowance = IERC20(token).allowance(sender, address(this));
            return allowance < balance ? allowance : balance;
        }
    }

    /**
     * @notice Calculates the token amount based on the bonding curve and base spend
     * @dev Must be implemented by derived contracts to handle specific curve logic
     * @param curve The bonding curve parameters
     * @param baseSpend The amount of base currency being spent
     * @param tradingFee The trading fee percentage in basis points
     * @return tokenAmount The calculated amount of tokens to be received
     */
    function _quoteTokenAmount(
        Curve memory curve,
        uint256 baseSpend,
        uint16 tradingFee
    ) internal pure virtual returns (uint256 tokenAmount);

    /**
     * @notice Executes a buy operation on the bonding curve
     * @dev Virtual function that must be implemented by derived contracts to handle token purchases
     * @param curve The bonding curve parameters and state
     * @param params The trade parameters including amounts, recipient, and cross-chain routing
     */
    function _buy(
        Curve memory curve,
        IPrintrTrading.TradeParams memory params
    ) internal virtual;

    /**
     * @notice Migrates token balance from legacy treasury to new treasury
     * @dev Automatically called before any treasury withdrawal to ensure tokens are in new treasury
     *      Only performs migration if legacy treasury exists and has non-zero balance
     *      Handles both ERC20 tokens and native/wrapped tokens
     * @param token Address of the token to migrate (address(0) for native token)
     */
    function _migrateTreasury(
        address token
    ) internal {
        // Skip migration if no legacy treasury configured
        if (address(legacyTreasury) == address(0)) {
            return;
        }

        uint256 balance = IERC20(token).balanceOf(address(legacyTreasury));

        // Only migrate if there's a non-zero balance
        if (balance > 0) {
            // Pull tokens from legacy treasury to new treasury
            legacyTreasury.withdraw(token, address(treasury), balance);
        }
    }

}

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

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";

import { ILiquidityModule } from "../interfaces/liquidity/ILiquidityModule.sol";
import { IPrintrTrading } from "../interfaces/printr/IPrintrTrading.sol";
import { IInterchainStandard } from "../interfaces/telecoin/IInterchainStandard.sol";
import { IPrintrTeleportingTelecoin } from "../interfaces/telecoin/IPrintrTeleportingTelecoin.sol";
import { IWETH } from "../interfaces/telecoin/IWETH.sol";

import { SqrtPriceMath } from "../libs/SqrtPriceMath.sol";

import { PrintrStorage } from "./PrintrStorage.sol";

/**
 * @title Printr Trading
 * @notice Implements trading functionality for tokens with linear bonding curves
 * @dev Implements:
 *      - Bonding curve calculations for token pricing
 *      - Token buying and selling mechanics
 *      - Cross-chain trading operations
 *      - Liquidity pool interactions
 *      - Fee collection and distribution
 */
abstract contract PrintrTrading is IPrintrTrading, PrintrStorage {

    using SafeERC20 for IERC20;
    using Math for uint256;
    using SqrtPriceMath for uint256;

    /// @notice Gas reserved for graceful exit operations during token graduation
    /// @dev Worst-case breakdown: event emission (~2,100 gas), ERC20 balanceOf (~2,600 gas),
    ///      WETH withdraw (~8,000 gas), ERC20 safeTransfer cold SSTORE (~20,100 gas), safety buffer (~25,200 gas)
    /// @dev Applied to liquidity deployment and post-graduation swaps to allow token refunds on failure
    uint256 private constant GRACEFUL_EXIT_GAS_RESERVE = 63_000;

    /**
     * @notice Estimates the cost of buying a specific amount of tokens
     * @dev Uses linear bonding curve for price discovery when curve is active,
     *      or queries liquidity pool when token has graduated to DEX trading.
     *      All calculations are scaled by PRECISION for decimal accuracy.
     * @param token Address of the token to buy
     * @param tokenAmount Number of tokens to purchase
     * @return availableAmount Amount of tokens available for purchase (may be less than requested)
     * @return cost Total cost in base currency including all fees
     * @return fee Trading fee amount in base currency
     * @return priceAfter Final price per token after the trade execution
     * @return issuedSupply Total tokens issued from curve after the trade
     */
    function estimateTokenCost(
        address token,
        uint256 tokenAmount
    ) external returns (uint256 availableAmount, uint256 cost, uint256 fee, uint256 priceAfter, uint256 issuedSupply) {
        Storage storage $ = _storage();
        Curve memory curve = $.curves[token];

        require(curve.basePair != address(0), TokenNotFound());
        // Check if liquidity is deployed
        if (curve.completionThreshold == 0 && curve.totalCurves > 0) {
            return
                _quoteLiquidityExactOutput(
                    TradeParams(address(0), address(0), token, tokenAmount, 0, tradingFee), curve
                );
        }

        // Disable graduation trigger for estimating
        curve.completionThreshold = uint64(PRECISION / curve.totalCurves);
        // Use bonding curve pricing
        return _estimateTokenCost(curve, tokenAmount, tradingFee, 0);
    }

    /**
     * @notice Quote the amount of tokens receivable for a specific amount of base currency
     * @dev Calculates token amount based on bonding curve (if active) or liquidity pool pricing.
     *      Uses reverse bonding curve calculation to determine optimal token output.
     * @param token Address of the token to purchase
     * @param baseAmount Amount of base currency (ETH/WETH/other) to spend
     * @return tokenAmount Amount of tokens that would be received
     * @return cost Actual amount in base currency required (should equal baseAmount)
     * @return fee Trading fee amount deducted in base currency
     * @return priceAfter Final price per token after the trade
     * @return issuedSupply Total tokens issued from curve after the trade
     */
    function quoteTokenAmount(
        address token,
        uint256 baseAmount
    ) external returns (uint256 tokenAmount, uint256 cost, uint256 fee, uint256 priceAfter, uint256 issuedSupply) {
        Storage storage $ = _storage();
        Curve memory curve = $.curves[token];

        require(curve.basePair != address(0), TokenNotFound());
        // Check if liquidity is deployed
        if (curve.completionThreshold == 0 && curve.totalCurves > 0) {
            return _quoteLiquidityExactInput(
                ILiquidityModule.SwapParams({
                    tokenIn: curve.basePair,
                    tokenOut: token,
                    amountIn: baseAmount,
                    amountOutMinimum: 0,
                    sqrtPriceLimitX96: 0,
                    recipient: address(0)
                }),
                curve
            );
        }

        // Disable graduation trigger for estimating
        curve.completionThreshold = uint64(PRECISION / curve.totalCurves);
        // Use bonding curve calculation
        tokenAmount = _quoteTokenAmount(curve, baseAmount, tradingFee);
        return _estimateTokenCost(curve, tokenAmount, tradingFee, 0);
    }

    /**
     * @notice Calculates refund amount for selling tokens back to the curve or pool
     * @dev Uses reverse bonding curve calculations when curve is active,
     *      or queries liquidity pool pricing when token has graduated
     * @param token Address of the token to sell
     * @param tokenAmount Amount of tokens to sell
     * @return tokenAmountIn Actual amount of tokens that can be sold (limited by available supply)
     * @return refund Amount of base currency to be returned to seller
     * @return fee Trading fee amount deducted from refund
     * @return priceAfter Final price per token received in the sale
     * @return issuedSupply Total tokens issued from curve after the trade
     */
    function estimateTokenRefund(
        address token,
        uint256 tokenAmount
    ) external returns (uint256 tokenAmountIn, uint256 refund, uint256 fee, uint256 priceAfter, uint256 issuedSupply) {
        Storage storage $ = _storage();
        Curve memory curve = $.curves[token];

        require(curve.basePair != address(0), TokenNotFound());
        // Check if liquidity is deployed
        if (curve.completionThreshold == 0 && curve.totalCurves > 0) {
            // Use the token as input, get base currency as output - amountOut becomes refund
            (refund, tokenAmountIn, fee, priceAfter, issuedSupply) = _quoteLiquidityExactInput(
                ILiquidityModule.SwapParams({
                    tokenIn: token,
                    tokenOut: curve.basePair,
                    amountIn: tokenAmount,
                    amountOutMinimum: 0,
                    sqrtPriceLimitX96: 0,
                    recipient: address(0)
                }),
                curve
            );
            return (tokenAmountIn, refund, fee, priceAfter, issuedSupply);
        }

        return _estimateTokenRefund(curve, tokenAmount, tradingFee, 0);
    }

    /**
     * @notice Buys tokens according to the bonding curve or from liquidity pool
     * @dev Handles both curve-based purchases (pre-graduation) and pool-based purchases (post-graduation)
     *      Includes slippage protection via maxPrice parameter
     * @param token Address of the token to purchase
     * @param recipient Address that will receive the purchased tokens
     * @param amount Amount of tokens to buy (exact amount)
     * @param maxPrice Maximum acceptable price per token (0 = no limit)
     * @return TradeParams struct containing executed trade details
     * @custom:throws TokenNotFound if token curve doesn't exist
     * @custom:throws InsufficientPayment if ETH sent is insufficient
     */
    function buy(
        address token,
        address recipient,
        uint256 amount,
        uint256 maxPrice
    ) external payable whenNotPaused returns (TradeParams memory) {
        Storage storage $ = _storage();
        Curve memory curve = $.curves[token];

        require(amount != 0, ZeroAmount());
        require(curve.basePair != address(0), TokenNotFound());

        TradeParams memory params = TradeParams(msg.sender, recipient, token, amount, maxPrice, tradingFee);

        // Route to liquidity pool if already deployed (threshold = 0)
        if (curve.completionThreshold == 0) {
            // Quote the actual cost to buy the desired amount of tokens
            (, uint256 actualCost,,,) = _quoteLiquidityExactOutput(params, curve);
            // Get the maximum amount user can spend
            uint256 maxAvailable = _getMaxAmount(curve.basePair, msg.sender);
            // Use the smaller of actualCost and maxAvailable
            params.amount = actualCost < maxAvailable ? actualCost : maxAvailable;

            _spendInLiquidityPool(curve, params, false);
            _refundNativeValue(recipient);
            return params;
        }

        _buy(curve, params);
        // Refund excess ETH payment if any
        _refundNativeValue(recipient);

        return params;
    }

    /**
     * @notice Buys tokens with a specified amount of base currency
     * @dev Calculates optimal token amount for given base currency input
     *      Handles both ETH and ERC20 base currencies
     * @param token Address of the token to buy
     * @param recipient Address to receive the tokens
     * @param baseAmount Amount of base currency to spend. Pass type(uint256).max to use
     *                   the maximum available amount (all approved tokens or all sent ETH)
     * @param maxPrice Maximum acceptable price per token
     */
    function spend(
        address token,
        address recipient,
        uint256 baseAmount,
        uint256 maxPrice
    ) public payable whenNotPaused returns (TradeParams memory) {
        Storage storage $ = _storage();
        Curve memory curve = $.curves[token];

        require(baseAmount != 0, ZeroAmount());
        require(curve.basePair != address(0), TokenNotFound());

        if (baseAmount == type(uint256).max) {
            baseAmount = _getMaxAmount(curve.basePair, msg.sender);
        }

        TradeParams memory params = TradeParams(msg.sender, recipient, token, baseAmount, maxPrice, tradingFee);

        // Route to liquidity pool if already deployed (threshold = 0)
        if (curve.completionThreshold == 0) {
            params.amount = _spendInLiquidityPool(curve, params, false);
            _refundNativeValue(recipient);
            return params;
        }

        params.amount = _quoteTokenAmount($.curves[token], baseAmount, tradingFee);

        _buy(curve, params);
        // Refund excess ETH payment if any
        _refundNativeValue(recipient);

        return params;
    }

    /**
     * @notice Sells tokens and returns base currency according to the bonding curve
     * @dev Includes slippage protection via minPrice parameter
     *      Handles both curve-based and pool-based sales
     * @param token Address of the token to sell
     * @param recipient Address to receive the refund
     * @param amount Amount of tokens to sell. Pass type(uint256).max to sell all tokens
     * @param minPrice Minimum acceptable price per token
     */
    function sell(
        address token,
        address recipient,
        uint256 amount,
        uint256 minPrice
    ) public whenNotPaused returns (TradeParams memory) {
        Storage storage $ = _storage();
        Curve memory curve = $.curves[token];

        require(amount != 0, ZeroAmount());
        require(curve.basePair != address(0), TokenNotFound());

        if (amount == type(uint256).max) {
            amount = _getMaxAmount(token, msg.sender);
        }

        TradeParams memory params = TradeParams(msg.sender, recipient, token, amount, minPrice, tradingFee);

        _sell(curve, params);
        // Refund ETH value for the token sold
        _refundNativeValue(recipient);

        return params;
    }

    /**
     * @notice Sells tokens and returns base currency according to the bonding curve
     * @dev Includes slippage protection via minPrice parameter
     *      Handles both curve-based and pool-based sales
     * @param token Address of the token to sell
     * @param recipient Address to receive the refund
     * @param amount Amount of tokens to sell. Pass type(uint256).max to sell all tokens
     * @param minPrice Minimum acceptable price per token
     */
    function witnessSell(
        address signer,
        address token,
        address recipient,
        uint256 amount,
        uint256 minPrice
    ) public whenNotPaused returns (TradeParams memory) {
        require(msg.sender == token, UnauthorizedCaller());

        Storage storage $ = _storage();
        Curve memory curve = $.curves[token];

        require(curve.basePair != address(0), TokenNotFound());
        if (amount == type(uint256).max) {
            amount = _getMaxAmount(token, signer);
        }
        require(amount != 0, ZeroAmount());

        TradeParams memory params = TradeParams(signer, recipient, token, amount, minPrice, tradingFee);

        _sell(curve, params);
        // Refund ETH value for the token sold
        _refundNativeValue(recipient);

        return params;
    }

    /**
     * @notice Internal function to calculate token purchase costs using linear bonding curve
     * @dev Implements the main bonding curve calculation:
     *      - Uses constant product formula: (v+r)*t = k where v=virtual_reserve, r=reserve, t=token_reserve
     *      - Calculates cost including trading fees
     *      - Rounds up costs to prevent precision attacks
     * @param curve The bonding curve parameters
     * @param tokenAmount Amount of tokens to purchase
     * @param tradingFee Fee percentage (basis points)
     * @return availableAmount Amount of tokens available for purchase
     * @return cost Total cost in base currency including fees
     * @return fee Trading fee amount in base currency
     * @return priceAfter Price per token after the trade
     * @return issuedSupply Total tokens issued from curve after the trade
     */
    function _estimateTokenCost(
        Curve memory curve,
        uint256 tokenAmount,
        uint16 tradingFee,
        uint256 priceLimit
    )
        internal
        pure
        returns (uint256 availableAmount, uint256 cost, uint256 fee, uint256 priceAfter, uint256 issuedSupply)
    {
        // Calculate available token reserve based on max supply and current reserve
        uint256 initialTokenReserve;
        {
            uint256 maxTokenSupply = 10 ** curve.maxTokenSupplyE * PRECISION;
            initialTokenReserve = maxTokenSupply / curve.totalCurves;
        }
        uint256 virtualReserve = uint256(curve.virtualReserve) * 10 ** curve.virtualReserveE;
        uint256 curveConstant = virtualReserve * initialTokenReserve;
        uint256 tokenReserve = curveConstant / (virtualReserve + curve.reserve);
        issuedSupply = initialTokenReserve - tokenReserve;

        {
            // Calculate maximum amount based on price limit if specified
            if (priceLimit != 0) {
                // Calculate the maximum tokenReserve that would result in priceAfter <= priceLimit
                // priceLimit = PRECISION * curveConstant / (tokenReserve - tokenAmount)^2
                // tokenAmount = tokenReserve - sqrt(PRECISION * curveConstant / priceLimit)
                // Early division prevents overflow: sqrt((PRECISION * virtualReserve / priceLimit) *
                // initialTokenReserve)

                uint256 sqrtValue = Math.sqrt((PRECISION * virtualReserve / priceLimit) * initialTokenReserve);
                if (sqrtValue < tokenReserve) {
                    uint256 maxAmountFromPriceLimit = tokenReserve - sqrtValue;
                    // Take the minimum between requested amount and price limit amount
                    if (maxAmountFromPriceLimit < tokenAmount) {
                        tokenAmount = maxAmountFromPriceLimit;
                    }
                } else {
                    // Current price already exceeds the price limit - zero fill
                    tokenAmount = 0;
                }
            }

            uint256 completionAmount = (10 ** curve.maxTokenSupplyE) * curve.completionThreshold;
            if (issuedSupply + tokenAmount > completionAmount) {
                // Adjust amount if it would exceed completion threshold
                availableAmount = completionAmount > issuedSupply ? completionAmount - issuedSupply : 0;
                tokenAmount = availableAmount;
            } else {
                availableAmount = tokenAmount;
            }
        }

        // Apply constant product formula: (virtual_reserve + reserve) * token_reserve = k
        uint256 curveCost = curveConstant / (tokenReserve - tokenAmount) - virtualReserve - curve.reserve;

        // Round up the cost if there's any remainder to prevent precision attacks
        if ((curveConstant / (tokenReserve - tokenAmount)) * (tokenReserve - tokenAmount) < curveConstant) {
            curveCost += 1;
        }

        // Calculate projected issuedSupply based on new curve reserve
        issuedSupply = initialTokenReserve - (tokenReserve - availableAmount);

        // Calculate and round up minimum fee (1 wei) if percentage would round to 0
        fee = (curveCost * tradingFee) / BIPS_SCALAR;
        if (fee == 0 && tradingFee > 0) {
            fee = 1;
        }

        cost = curveCost + fee;

        // Calculate final token price: base_reserve / token_reserve^2
        // Early division prevents overflow with large reserve values
        uint256 newTokenReserve = tokenReserve - tokenAmount;
        priceAfter = (PRECISION * virtualReserve / newTokenReserve) * initialTokenReserve / newTokenReserve;
    }

    /**
     * @notice Internal function to calculate token amount for given base currency input
     * @dev Implements reverse bonding curve calculation to determine token output
     *      Includes fee calculations and smart rounding protection
     *      Returns tokenAmount - 1 when it would prevent rounding discrepancies
     * @param curve Bonding curve configuration
     * @param baseSpend Amount of base currency to spend
     * @param tradingFee Fee percentage to apply
     * @return tokenAmount Amount of tokens to be received
     */
    function _quoteTokenAmount(
        Curve memory curve,
        uint256 baseSpend,
        uint16 tradingFee
    ) internal pure override returns (uint256 tokenAmount) {
        // Calculate available token reserve based on max supply and current reserve
        uint256 maxTokenSupply = 10 ** curve.maxTokenSupplyE * PRECISION;
        uint256 initialTokenReserve = maxTokenSupply / curve.totalCurves;
        uint256 virtualReserve = uint256(curve.virtualReserve) * 10 ** curve.virtualReserveE;
        uint256 curveConstant = virtualReserve * initialTokenReserve;
        uint256 tokenReserve = curveConstant / (virtualReserve + curve.reserve);

        // curveBudget = 100 % / 101 %, excluding trading fee
        uint256 curveBudget = (BIPS_SCALAR * baseSpend) / (BIPS_SCALAR + tradingFee);
        tokenAmount = tokenReserve - curveConstant / (virtualReserve + curve.reserve + curveBudget);

        if (
            (curveConstant / (tokenReserve - tokenAmount)) * (tokenReserve - tokenAmount) < curveConstant
                && tokenAmount != 0
        ) {
            // Prevent precision attacks by rounding down
            tokenAmount -= 1;
        }
    }

    /**
     * @notice Calculates refund amount for selling tokens using the bonding curve
     * @dev Uses constant product formula k = (v+r)*t for price calculation
     *      Where: v = virtual reserve, r = actual reserve, t = token reserve
     * @param curve Token's bonding curve configuration
     * @param tokenAmount Amount of tokens to sell
     * @param tradingFee Fee percentage in basis points (1 = 0.01%)
     * @param priceLimit Minimum acceptable price per token (0 = no limit)
     * @return tokenAmountIn Amount of tokens that can be sold (adjusted for price limit)
     * @return refund Amount of base currency to return to user
     * @return fee Trading fee to be deducted
     * @return priceAfter Price per token after the sell operation
     * @return issuedSupply New total tokens issued from curve after the trade
     */
    function _estimateTokenRefund(
        Curve memory curve,
        uint256 tokenAmount,
        uint16 tradingFee,
        uint256 priceLimit
    )
        internal
        pure
        returns (uint256 tokenAmountIn, uint256 refund, uint256 fee, uint256 priceAfter, uint256 issuedSupply)
    {
        // Calculate available token reserve based on max supply and current reserve
        uint256 initialTokenReserve;
        {
            uint256 maxTokenSupply = 10 ** curve.maxTokenSupplyE * PRECISION;
            initialTokenReserve = maxTokenSupply / curve.totalCurves;
        }
        uint256 virtualReserve = uint256(curve.virtualReserve) * 10 ** curve.virtualReserveE;
        uint256 curveConstant = virtualReserve * initialTokenReserve;
        uint256 tokenReserve = curveConstant / (virtualReserve + curve.reserve);
        uint256 currentIssuedSupply = initialTokenReserve - tokenReserve;

        {
            // Calculate minimum amount based on price limit if specified
            if (priceLimit != 0) {
                // For selling, we need to find the maximum tokenAmount that results in priceAfter >= priceLimit
                // priceLimit = PRECISION * curveConstant / (tokenReserve + tokenAmount)^2
                // tokenAmount = sqrt(PRECISION * curveConstant / priceLimit) - tokenReserve
                // Early division prevents overflow: sqrt((PRECISION * virtualReserve / priceLimit) *
                // initialTokenReserve)

                uint256 sqrtValue = Math.sqrt((PRECISION * virtualReserve / priceLimit) * initialTokenReserve);
                if (sqrtValue > tokenReserve) {
                    uint256 maxAmountFromPriceLimit = sqrtValue - tokenReserve;
                    // Take the minimum between requested amount and price limit amount
                    if (maxAmountFromPriceLimit < tokenAmount) {
                        tokenAmount = maxAmountFromPriceLimit;
                    }
                } else {
                    // Current price already below the minimum price limit - zero fill
                    tokenAmount = 0;
                }
            }

            if (tokenAmount > currentIssuedSupply) {
                tokenAmount = currentIssuedSupply;
            }
        }

        tokenAmountIn = tokenAmount;
        issuedSupply = currentIssuedSupply - tokenAmount;

        // Calculate refund using bonding curve formula
        uint256 curveRefund = virtualReserve + curve.reserve - curveConstant / (tokenReserve + tokenAmount);

        if (
            (curveConstant / (tokenReserve + tokenAmount)) * (tokenReserve + tokenAmount) < curveConstant
                && curveRefund != 0
        ) {
            // Round down the refund to prevent precision attacks
            curveRefund -= 1;
        }

        // Calculate and ensure minimum fee if applicable
        fee = (curveRefund * tradingFee) / BIPS_SCALAR;

        if (fee == 0 && tradingFee != 0 && curveRefund != 0) {
            fee = 1;
        }

        // Calculate final refund after deducting fee
        refund = curveRefund - fee;

        // Calculate price after sell operation using constant product formula
        // Early division prevents overflow with large reserve values
        uint256 newTokenReserve = tokenReserve + tokenAmount;
        priceAfter = (PRECISION * virtualReserve / newTokenReserve) * initialTokenReserve / newTokenReserve;
    }

    struct BuyContext {
        uint256 completionAmount;
        uint256 buyAmount;
        uint256 cost;
        uint256 issuedSupply;
        uint256 priceAfter;
    }

    /**
     * @dev Internal buy implementation handling both curve and pool-based purchases
     * @param curve Token's bonding curve configuration
     * @param params Trading parameters including amounts and limits
     * @notice This function handles two different trading mechanisms:
     *         1. Bonding curve trades before completion threshold
     *         2. Liquidity pool trades after deployment
     * @notice Also manages:
     *         - Fee collection
     *         - Token transfers from treasury
     *         - ETH refunds
     *         - Liquidity deployment when threshold reached
     */
    function _buy(
        Curve memory curve,
        TradeParams memory params
    ) internal override {
        Storage storage $ = _storage();
        BuyContext memory ctx;

        // Calculate and enforce completion threshold
        ctx.completionAmount = (10 ** curve.maxTokenSupplyE) * curve.completionThreshold;

        // Calculate costs and fees
        {
            uint256 fee;
            (ctx.buyAmount, ctx.cost, fee, ctx.priceAfter, ctx.issuedSupply) =
                _estimateTokenCost(curve, params.amount, params.tradingFee, params.priceLimit);

            // Revert if slippage resulted in zero tokens while not graduating
            if (ctx.buyAmount == 0 && ctx.issuedSupply < ctx.completionAmount) {
                revert ZeroAmount();
            }

            // Update reserve balance and collect fees
            curve.reserve += uint192(ctx.cost - fee);
            $.curves[params.token].reserve = curve.reserve;
            $.collectedFees[params.token] += fee;

            emit TokenTrade(
                params.token,
                params.recipient,
                true,
                ctx.buyAmount,
                ctx.cost,
                ctx.priceAfter,
                ctx.issuedSupply,
                curve.reserve
            );
        }

        address treasuryAddr = address(treasury);
        // Handle payment collection
        if (curve.basePair == wrappedNativeToken && msg.value > 0) {
            if (address(this).balance < ctx.cost) {
                revert InsufficientPayment();
            }
            // Wrap ETH
            IWETH(wrappedNativeToken).deposit{ value: ctx.cost }();
            // Transfer ERC20 tokens to treasury
            IERC20(curve.basePair).safeTransfer(treasuryAddr, ctx.cost);
        } else {
            // Transfer ERC20 tokens to treasury
            IERC20(curve.basePair).safeTransferFrom(params.account, treasuryAddr, ctx.cost);
        }

        // Migrate token from legacy treasury if needed
        _migrateTreasury(params.token);

        // Transfer tokens from treasury to recipient
        treasury.withdraw(params.token, params.recipient, ctx.buyAmount);

        // Check if liquidity deployment threshold is reached
        if (ctx.issuedSupply >= ctx.completionAmount) {
            // Reset completion threshold to zero to indicate deployed liquidity
            $.curves[params.token].completionThreshold = 0;

            // Migrate token from legacy treasury if needed
            _migrateTreasury(curve.basePair);

            uint256 gasLeft = gasleft();

            if (gasLeft < GRACEFUL_EXIT_GAS_RESERVE) {
                // Revert token graduation by restoring completion threshold
                $.curves[params.token].completionThreshold = curve.completionThreshold;
                params.amount = ctx.buyAmount;
                // Graceful failure: liquidity deployment failed, skip post-swap
                emit TokenGraduationPartialFailure(params.token, 0, gasleft());
                return;
            }

            // Deploy liquidity via module with gas limit for graceful failure
            (bool success, bytes memory data) = liquidityModule.delegatecall{
                gas: gasLeft - GRACEFUL_EXIT_GAS_RESERVE
            }(
                abi.encodeCall(
                    ILiquidityModule.deployLiquidity,
                    (ILiquidityModule.LiquidityDeployParams({
                            curve: curve, token: params.token, issuedSupply: ctx.completionAmount, treasury: treasury
                        }))
                )
            );

            if (!success) {
                // Revert token graduation by restoring completion threshold
                $.curves[params.token].completionThreshold = curve.completionThreshold;
                params.amount = ctx.buyAmount;
                // Graceful failure: liquidity deployment failed, skip post-swap
                emit TokenGraduationPartialFailure(params.token, 0, gasleft());
                return;
            }

            // Store liquidity lock IDs and handle collected fees
            (uint256 lockId0, uint256 lockId1, uint256 collectedFees) = abi.decode(data, (uint256, uint256, uint256));
            $.liquidityLocks[params.token] = [lockId0, lockId1];

            if (collectedFees > 0) {
                $.collectedFees[params.token] += collectedFees;
                IERC20(curve.basePair).safeTransfer(address(treasury), collectedFees);
            }

            // NOTE: remaining spend should not be affected by liquidity deployment
            //       as native value was not wrapped and ERC20 not transferred from user

            // Route the rest of the trade to deployed liquidity
            uint256 remainingTokens = params.amount - ctx.buyAmount;

            if (remainingTokens > 0) {
                params.amount = remainingTokens;
                // Set completion threshold to 100% to bypass graduation logic
                curve.completionThreshold = uint64(PRECISION / curve.totalCurves);
                (, uint256 originalCost,,,) =
                    _estimateTokenCost(curve, remainingTokens, params.tradingFee, params.priceLimit);

                // Get max available funds from user
                uint256 maxAvailable = _getMaxAmount(curve.basePair, params.account);

                // Use minimum of actualCost and maxAvailable
                uint256 remainingSpend = originalCost < maxAvailable ? originalCost : maxAvailable;

                if (remainingSpend > 0) {
                    params.amount = remainingSpend;
                    params.amount = ctx.buyAmount + _spendInLiquidityPool(curve, params, true);
                } else {
                    params.amount = ctx.buyAmount;
                }
            } else {
                params.amount = ctx.buyAmount;
            }

            emit TokenGraduated(params.token, ctx.completionAmount);
        }
    }

    /**
     * @notice Internal implementation of token selling logic
     * @dev Handles both curve-based and pool-based token sales
     *      Includes slippage protection and fee collection
     * @param curve Curve configuration for the token
     * @param params Parameters for the token sale
     */
    function _sell(
        Curve memory curve,
        TradeParams memory params
    ) internal {
        Storage storage $ = _storage();

        // Route to liquidity pool if available
        if (curve.completionThreshold == 0) {
            _sellToLiquidityPool(curve, params);
            return;
        }

        // Calculate refund amount and fees
        (uint256 availableAmount, uint256 refund, uint256 fee, uint256 priceAfter, uint256 issuedSupply) =
            _estimateTokenRefund(curve, params.amount, params.tradingFee, params.priceLimit);
        params.amount = availableAmount;

        // Revert if slippage resulted in zero refund
        require(refund != 0, ZeroAmount());

        // Transfer tokens from seller to treasury
        IERC20(params.token).safeTransferFrom(params.account, address(treasury), params.amount);

        // Update reserve balance and collect fees
        curve.reserve -= uint192(refund + fee);
        $.curves[params.token].reserve = curve.reserve;
        $.collectedFees[params.token] += fee;

        emit TokenTrade(
            params.token, params.recipient, false, params.amount, refund, priceAfter, issuedSupply, curve.reserve
        );

        // Process refund payment
        if (refund > 0) {
            // Migrate token from legacy treasury if needed
            _migrateTreasury(curve.basePair);

            if (curve.basePair == wrappedNativeToken) {
                treasury.withdraw(curve.basePair, address(this), refund);
                // Unwrap WETH into ETH
                IWETH(wrappedNativeToken).withdraw(refund);
                // ETH is refunded later in sell function
            } else {
                treasury.withdraw(curve.basePair, params.recipient, refund);
            }
        }
    }

    /**
     * @notice Converts price limit to sqrtPriceLimitX96 format for AMM swaps
     * @dev Uses token ordering to determine if price inversion is needed
     * @param basePair Quote token address
     * @param token Telecoin address
     * @param priceLimit Price limit in PRECISION units (basePair per token)
     * @return sqrtPriceLimitX96 Price limit in X96 sqrt format, 0 if no limit
     */
    function _convertPriceLimitToSqrtX96(
        address basePair,
        address token,
        uint256 priceLimit
    ) internal pure returns (uint160 sqrtPriceLimitX96) {
        if (priceLimit == 0) {
            return 0;
        }

        uint256 adjustedPriceLimit;
        if (token > basePair) {
            // Token ordering reversed, invert price
            adjustedPriceLimit = (PRECISION * PRECISION) / priceLimit;
        } else {
            // Token ordering matches, use price as-is
            adjustedPriceLimit = priceLimit;
        }

        // Calculate square root price in X96 format
        sqrtPriceLimitX96 = adjustedPriceLimit.toSqrtPriceX96();
    }

    /**
     * @notice Executes a buy order through the liquidity pool
     * @dev Handles token swaps via the liquidity module
     *      Uses sqrtPriceLimitX96 for partial fills instead of amountOutMinimum
     * @param curve Curve configuration containing pool details
     * @param params Trading parameters and limits
     * @param gracefulFailure If true, return 0 on failure instead of reverting (for graduation flow)
     */
    function _spendInLiquidityPool(
        Curve memory curve,
        TradeParams memory params,
        bool gracefulFailure
    ) internal returns (uint256 amountOut) {
        // Calculate sqrtPriceLimitX96 from price limit for partial fills
        uint160 sqrtPriceLimitX96 = _convertPriceLimitToSqrtX96(curve.basePair, params.token, params.priceLimit);

        // Setup swap parameters for liquidity module
        ILiquidityModule.SwapParams memory swapParams = ILiquidityModule.SwapParams({
            tokenIn: curve.basePair,
            tokenOut: params.token,
            amountIn: params.amount,
            amountOutMinimum: 0, // Set to 0 to allow partial fills
            sqrtPriceLimitX96: sqrtPriceLimitX96,
            recipient: params.recipient
        });

        // If input token is WETH and we received ETH, wrap it first
        if (curve.basePair == wrappedNativeToken && msg.value > 0) {
            // value should be sufficient for the amount or be equal to 0
            if (msg.value < params.amount) {
                revert InsufficientPayment();
            }
            IWETH(wrappedNativeToken).deposit{ value: params.amount }();
        } else {
            IERC20(curve.basePair).safeTransferFrom(params.account, address(this), params.amount);
        }

        {
            // Execute swap via delegatecall with gas limiting for graceful failures
            uint256 gasLeft = gasleft();
            bool success;
            bytes memory data;
            if (gasLeft > GRACEFUL_EXIT_GAS_RESERVE) {
                uint256 gasLimit = gracefulFailure ? gasLeft - GRACEFUL_EXIT_GAS_RESERVE : gasLeft;
                (success, data) = liquidityModule.delegatecall{ gas: gasLimit }(
                    abi.encodeWithSelector(ILiquidityModule.executeSwap.selector, swapParams)
                );
            }

            if (success) {
                (amountOut) = abi.decode(data, (uint256));
            } else {
                if (gracefulFailure) {
                    emit TokenGraduationPartialFailure(params.token, 1, gasleft());
                } else {
                    // Bubble up the actual AMM error
                    if (data.length == 0) {
                        revert SwapFailed();
                    }
                    assembly {
                        revert(add(data, 0x20), mload(data))
                    }
                }
            }
        }

        // Handle leftover refund if needed
        uint256 balance = IERC20(curve.basePair).balanceOf(address(this));

        if (balance > 0) {
            if (curve.basePair == wrappedNativeToken) {
                // Unwrap WETH into ETH
                IWETH(wrappedNativeToken).withdraw(balance);
                // ETH is refunded later in buy function
            } else {
                IERC20(curve.basePair).safeTransfer(params.recipient, balance);
            }
        }
    }

    /**
     * @notice Executes a sell order through the liquidity pool
     * @dev Transfers tokens from seller and executes swap
     *      Uses sqrtPriceLimitX96 for partial fills instead of amountOutMinimum
     * @param curve Curve configuration containing pool details
     * @param params Trading parameters and limits
     */
    function _sellToLiquidityPool(
        Curve memory curve,
        TradeParams memory params
    ) internal {
        // Transfer tokens from seller to contract
        IERC20(params.token).safeTransferFrom(params.account, address(this), params.amount);

        // Calculate sqrtPriceLimitX96 from price limit for partial fills
        uint160 sqrtPriceLimitX96 = _convertPriceLimitToSqrtX96(curve.basePair, params.token, params.priceLimit);

        // Setup swap parameters
        ILiquidityModule.SwapParams memory swapParams = ILiquidityModule.SwapParams({
            tokenIn: params.token,
            tokenOut: curve.basePair,
            amountIn: params.amount,
            amountOutMinimum: 0, // Set to 0 to allow partial fills
            sqrtPriceLimitX96: sqrtPriceLimitX96,
            recipient: curve.basePair == wrappedNativeToken ? address(this) : params.recipient
        });

        // Execute swap via delegatecall
        (bool success, bytes memory data) =
            liquidityModule.delegatecall(abi.encodeWithSelector(ILiquidityModule.executeSwap.selector, swapParams));

        if (!success) {
            // Bubble up the actual AMM error
            if (data.length == 0) {
                revert SwapFailed();
            }
            assembly {
                revert(add(data, 0x20), mload(data))
            }
        }

        if (curve.basePair == wrappedNativeToken) {
            uint256 amountOut = abi.decode(data, (uint256));
            // Unwrap WETH into ETH
            IWETH(wrappedNativeToken).withdraw(amountOut);
            // ETH is refunded later in sell function
        }

        // Handle token leftover if needed
        uint256 balance = IERC20(params.token).balanceOf(address(this));

        if (balance > 0) {
            IERC20(params.token).safeTransfer(params.recipient, balance);
        }
    }

    /**
     * @notice Internal function to get liquidity quote for exact output amount
     * @dev Queries liquidity module for swap pricing with exact output
     * @param params Trade parameters including token, amount, and price limit
     * @param curve Bonding curve configuration for issued supply calculation
     * @return availableAmount Amount of tokens available for purchase
     * @return cost Total cost in base currency
     * @return fee Trading fee (0 for liquidity trades)
     * @return priceAfter Final price per token
     * @return issuedSupply Approximated issued supply
     */
    function _quoteLiquidityExactOutput(
        TradeParams memory params,
        Curve memory curve
    ) internal returns (uint256 availableAmount, uint256 cost, uint256 fee, uint256 priceAfter, uint256 issuedSupply) {
        // Build swap parameters for quoting (include price limit to simulate user's constraints)
        ILiquidityModule.SwapParams memory swapParams = ILiquidityModule.SwapParams({
            tokenIn: curve.basePair,
            tokenOut: params.token,
            amountIn: 0,
            amountOutMinimum: params.amount,
            sqrtPriceLimitX96: _convertPriceLimitToSqrtX96(curve.basePair, params.token, params.priceLimit),
            recipient: address(0)
        });

        // Get quote from liquidity module
        (bool success, bytes memory data) =
            liquidityModule.delegatecall(abi.encodeWithSelector(ILiquidityModule.quoteExactOutput.selector, swapParams));

        if (success && data.length > 0) {
            uint256 ammFeeBips;
            (cost, priceAfter, ammFeeBips) = abi.decode(data, (uint256, uint256, uint256));

            // Validate quote result
            if (cost == 0 || cost == type(uint256).max) {
                revert InvalidQuoteResult();
            }

            // The liquidity module returns price in token0/token1 terms
            // We want the price as "basePair per Telecoin"
            if (params.token > curve.basePair) {
                // basePair is token1, Telecoin is token0: need to invert
                priceAfter = (PRECISION * PRECISION) / priceAfter;
            }

            // Calculate the fee based on the AMM's fee tier in basePair terms
            if (swapParams.tokenIn == curve.basePair) {
                // Buying with basePair: fee is a percentage of the cost
                fee = (cost * ammFeeBips) / BIPS_SCALAR;
            } else {
                // Selling for exact output: cost is in Telecoin, convert fee to basePair
                uint256 feeInTokenIn = (cost * ammFeeBips) / BIPS_SCALAR;
                fee = (feeInTokenIn * priceAfter) / PRECISION;
            }
            availableAmount = swapParams.amountOutMinimum;

            // For graduated tokens, ALL supply for this chain has been issued
            uint256 maxTokenSupply = 10 ** curve.maxTokenSupplyE * PRECISION;
            issuedSupply = maxTokenSupply / curve.totalCurves;

            return (availableAmount, cost, fee, priceAfter, issuedSupply);
        } else {
            revert InvalidQuoteResult();
        }
    }

    /**
     * @notice Internal function to get liquidity quote for exact input amount
     * @dev Queries liquidity module for swap pricing with exact input
     * @param swapParams Swap parameters with tokenIn, tokenOut, and amounts
     * @param curve Bonding curve configuration for issued supply calculation
     * @return amountOut Amount of output tokens received
     * @return amountIn Amount of input tokens required
     * @return fee Trading fee (0 for liquidity trades)
     * @return priceAfter Final price per token
     * @return issuedSupply Approximated issued supply
     */
    function _quoteLiquidityExactInput(
        ILiquidityModule.SwapParams memory swapParams,
        Curve memory curve
    ) internal returns (uint256 amountOut, uint256 amountIn, uint256 fee, uint256 priceAfter, uint256 issuedSupply) {
        // Get quote from liquidity module
        (bool success, bytes memory data) =
            liquidityModule.delegatecall(abi.encodeWithSelector(ILiquidityModule.quoteExactInput.selector, swapParams));

        if (success && data.length > 0) {
            uint256 ammFeeBips;
            (amountOut, priceAfter, ammFeeBips) = abi.decode(data, (uint256, uint256, uint256));
            amountIn = swapParams.amountIn;

            // The liquidity module returns price in token0/token1 terms
            // We want the price as "basePair per Telecoin"
            if (swapParams.tokenIn == curve.basePair) {
                // Buying Telecoin with basePair (spend)
                if (swapParams.tokenOut > swapParams.tokenIn) {
                    // basePair is token1, Telecoin is token0: need to invert
                    priceAfter = (PRECISION * PRECISION) / priceAfter;
                }
            } else {
                // Selling Telecoin for basePair
                if (swapParams.tokenIn > swapParams.tokenOut) {
                    // basePair is token0, Telecoin is token1: need to invert to get basePair/Telecoin
                    priceAfter = (PRECISION * PRECISION) / priceAfter;
                }
            }

            // Calculate the fee based on the AMM's fee tier in basePair terms
            if (swapParams.tokenOut == curve.basePair) {
                // The fee was taken from the swap, calculate based on what we received
                fee = (amountOut * ammFeeBips) / (BIPS_SCALAR - ammFeeBips);
            } else {
                // Buying Telecoin with basePair: input is basePair
                fee = (amountIn * ammFeeBips) / BIPS_SCALAR;
            }

            // For graduated tokens, ALL supply for this chain has been issued
            uint256 maxTokenSupply = 10 ** curve.maxTokenSupplyE * PRECISION;
            issuedSupply = maxTokenSupply / curve.totalCurves;

            return (amountOut, amountIn, fee, priceAfter, issuedSupply);
        } else {
            // If liquidity is deployed but quote fails, revert
            revert InvalidQuoteResult();
        }
    }

}

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

import { IPrintrStorage } from "./IPrintrStorage.sol";

/**
 * @title Printr Printing Interface
 * @notice Interface for managing token printing and liquidity curve operations
 * @dev Extends IPrintrStorage to handle token deployment and curve management across chains
 */
interface IPrintrPrinting is IPrintrStorage {

    /**
     * @notice Event emitted when a new token is printed
     * @param token Address of the new token contract
     * @param telecoinId Unique identifier for cross-chain deployment
     */
    event TelecoinPrinted(address indexed token, bytes32 indexed telecoinId);

    /**
     * @notice Emitted when a new token is created
     * @param creator Address that created the token
     * @param token Address of the new token contract
     * @param telecoinId Unique identifier for cross-chain deployment
     */
    event CurveCreated(address indexed creator, address indexed token, bytes32 indexed telecoinId);

    /**
     * @notice Emitted when token liquidity is deployed to Uniswap
     * @param token Address of the token contract
     * @param tokenAmount Amount of tokens added to the liquidity pool
     * @param baseAmount Amount of base currency added to the liquidity pool
     */
    event LiquidityDeployed(address indexed token, uint256 tokenAmount, uint256 baseAmount);

    /**
     * @notice Emitted when liquidity position is locked
     * @param token Address of the token contract
     * @param positionManager Address of the liquidity position manager
     * @param positionId Unique identifier for the liquidity position
     * @param lockId Unique identifier for the lock
     */
    event LiquidityLocked(address indexed token, address indexed positionManager, uint256 positionId, uint256 lockId);

    /**
     * @notice Prints a new token with a bonding curve across multiple chains
     * @dev Deploys token with bonding curve on the current chain and initiates remote deployments
     * @param initialSpending Initial amount of base currency to commit. Pass type(uint256).max to use
     *                        the maximum available amount (all approved tokens or all sent ETH)
     * @param telecoinParams Parameters for the token curve deployment including across-chain configuration
     * @return tokenAddress The address of the newly created token
     * @return telecoinId Unique identifier for cross-chain deployment
     */
    function print(
        uint256 initialSpending,
        TelecoinParams calldata telecoinParams
    ) external payable returns (address tokenAddress, bytes32 telecoinId);

}

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

import { ITreasury } from "../ITreasury.sol";

/**
 * @title Printr Storage Interface
 * @notice Interface for the Printr contract, which manages tokens with linear bonding curves
 * @dev All price calculations use PRECISION_SCALAR (1e18) for accurate floating point math
 *      Implements cross-chain token deployment through ITelecoinFactory integration
 *      This interface defines the core storage and view functions for the Printr system
 */
interface IPrintrStorage {

    /// @notice Custom errors for the Printr contract
    error FeeIsTooHigh(uint256 fee);
    error WrongChainName();
    error InsufficientPayment();
    error InsufficientInitialBuy();
    error InvalidBasePairDecimals();
    error InvalidBasePrices();
    error InvalidBasePairs();
    error InvalidCreatorAddress();
    error InvalidInitialPrice();
    error InvalidLength();
    error TooHighThreshold();
    error ZeroAmount();
    error PoolCreationFailed();
    error TokenNotFound();
    error SwapFailed();
    error LiquidityAlreadyDeployed();
    error LiquidityDeploymentFailed();
    error InvalidQuoteResult();
    error PriceExceedsLimit();
    error RefundFailed();
    error RenounceOwnershipDisabled();
    error InvalidImplementation();
    error FeePercentagesMustSum();
    error UnauthorizedCaller();
    error Create3AddressMismatch();

    /**
     * @notice Parameters structure for telecoin deployment
     * @dev Uses bytes32 for efficient storage and cross-chain compatibility
     * @param salt Unique identifier for deployment
     * @param creatorAddresses Creator addresses for different chains encoded as bytes
     * @param name Telecoin name encoded as bytes32, 31 characters max
     * @param symbol Telecoin symbol encoded as bytes32, 31 characters max
     * @param packedParams Packed parameters bit layout:
     *                     [0-7]     uint8   maxTokenSupplyE (power of 10)
     *                     [8-23]    uint16  completionThreshold (basis points, max 10000)
     *                     [24-135]  uint112 initialPrice, assuming 18 decimal places for both tokens
     *                     [136-255] uint120 initialBuySpending, assuming 18 decimal places
     * @param chains Chain names encoded as bytes32, first chain is the home chain
     * @param basePairs Base currency token addresses encoded as bytes32
     * @param basePrices Initial prices in base currency per chain as uint128 packed in bytes, 18 decimals for both
     */
    struct TelecoinParams {
        bytes32 salt;
        bytes creatorAddresses;
        bytes32 name;
        bytes32 symbol;
        bytes32 packedParams;
        bytes32[] chains;
        bytes32[] basePairs;
        bytes basePrices;
    }

    /**
     * @notice Unpacked version of the packedParams for easier handling
     * @dev Packed bytes32 converted into individual parameters
     * @param maxTokenSupplyE Maximum token supply as power of 10
     * @param completionThreshold Completion threshold in basis points (max 10000) of max supply
     * @param initialPrice Initial token price, assuming 18 decimal places for both tokens
     * @param initialBuySpending Initial buy amount, assuming 18 decimal places
     */
    struct UnpackedParams {
        uint8 maxTokenSupplyE;
        uint256 completionThreshold;
        uint256 initialPrice;
        uint256 initialBuySpending;
    }

    /**
     * @notice Comprehensive information about a token's configuration and state
     * @dev Whole struct is packed into 2 storage slots for efficiency
     * @param basePair Address of the base currency token (160 bits)
     * @param totalCurves Number of curves across all chains (16 bits)
     * @param maxTokenSupplyE Maximum token supply as pow of 10 (8 bits)
     * @param virtualReserveE Precision scalar for virtualReserve (8 bits)
     * @param virtualReserve Compressed virtual reserve balance for bonding curve (64 bits)
     * @param reserve Current reserve balance of the base currency token (192 bits)
     * @param completionThreshold Threshold for curve completion as percentage of total supply (64 bits)
     */
    struct Curve {
        /// @dev Slot 1: immutable (160 + 16 + 8 + 8 + 64 = 256 bits, fits in 1 slot)
        address basePair;
        uint16 totalCurves;
        uint8 maxTokenSupplyE;
        uint8 virtualReserveE;
        uint64 virtualReserve;
        /// @dev Slot 2: mutable (192 + 64 = 256 bits)
        uint192 reserve;
        uint64 completionThreshold;
    }

    /**
     * @notice Readable format of curve information
     * @param basePair Base currency token address
     * @param totalCurves Total number of curves across chains
     * @param maxTokenSupply Maximum token supply
     * @param virtualReserve Virtual reserve for curve calculations (already scaled with virtualReserveE)
     * @param reserve Current base currency reserve
     * @param completionThreshold Completion threshold value
     */
    struct CurveInfo {
        address basePair;
        uint16 totalCurves;
        uint256 maxTokenSupply;
        uint256 virtualReserve;
        uint256 reserve;
        uint256 completionThreshold;
    }

    /**
     * @notice Parameters for deploying Printr contracts
     * @dev Used to avoid stack too deep errors in constructor
     * @param chainName Name of the blockchain network
     * @param treasury Address of the treasury contract
     * @param legacyTreasury Address of the legacy treasury contract (for migration)
     * @param mainTelecoinFactory Address of the main token factory contract
     * @param teleportingTelecoinFactory Address of the teleporting token factory contract
     * @param its Address of the Interchain Token Service
     * @param itsFactory Address of the ITS factory
     * @param wrappedNativeToken Address of wrapped native token (e.g., WETH)
     * @param locker Address of the locker contract
     * @param liquidityModule Address of the liquidity module
     * @param create3Deployer Address of the CREATE3 deployer for deterministic LZChannel deployments
     * @param growthFundFund Address of the growth fund
     * @param buybackFund Address of the buyback
     * @param teamTreasuryFund Address of the team treasury
     * @param stakingFund Address of the staking
     * @param printrDev Address of the dev NFT contract
     * @param legacyPrintrDev Address of the first legacy dev NFT contract (for migration from V1 - oldest original)
     * @param legacyPrintrDev2 Address of the second legacy dev NFT contract (for migration from V2 - without base64
     * fix)
     * @param feePercentGrowth Fee percentage for growth fund in basis points
     * @param feePercentBuyback Fee percentage for buyback in basis points
     * @param feePercentTeam Fee percentage for team treasury in basis points
     * @param feePercentCreator Fee percentage for creator in basis points
     * @param tradingFee Trading fee percentage in basis points
     */
    struct DeploymentParams {
        string chainName;
        address treasury;
        address legacyTreasury;
        address mainTelecoinFactory;
        address teleportingTelecoinFactory;
        address its;
        address itsFactory;
        address wrappedNativeToken;
        address locker;
        address liquidityModule;
        address create3Deployer;
        address growthFund;
        address buybackFund;
        address teamTreasuryFund;
        address stakingFund;
        address printrDev;
        address legacyPrintrDev;
        address legacyPrintrDev2;
        uint256 feePercentGrowth;
        uint256 feePercentBuyback;
        uint256 feePercentTeam;
        uint256 feePercentCreator;
        uint16 tradingFee;
    }

    /// @notice Returns the current chain's hash identifier
    function currentChainHash() external view returns (bytes32);

    /// @notice Returns the treasury contract address
    function treasury() external view returns (ITreasury);

    /// @notice Returns the main token factory contract address
    function mainTelecoinFactory() external view returns (address);

    /// @notice Returns the teleporting token factory contract address
    function teleportingTelecoinFactory() external view returns (address);

    /// @notice Returns the ITS contract address
    function interchainTokenService() external view returns (address);

    /// @notice Returns the ITS factory contract address
    function itsFactory() external view returns (address);

    /// @notice Returns the wrapped native token address
    function wrappedNativeToken() external view returns (address);

    /// @notice Returns the liquidity module contract address
    function liquidityModule() external view returns (address);

    /// @notice Returns the locker contract address
    function locker() external view returns (address);

    /// @notice Returns the CREATE3 deployer address
    function create3Deployer() external view returns (address);

    /// @notice Returns the growth fund address
    function growthFund() external view returns (address);

    /// @notice Returns the buyback address
    function buybackFund() external view returns (address);

    /// @notice Returns the team treasury address
    function teamTreasuryFund() external view returns (address);

    /// @notice Returns the staking address
    function stakingFund() external view returns (address);

    /// @notice Returns the creator NFT contract address
    function printrDev() external view returns (address);

    /// @notice Returns the fee percentage for growth fund
    function feePercentGrowth() external view returns (uint256);

    /// @notice Returns the fee percentage for buyback
    function feePercentBuyback() external view returns (uint256);

    /// @notice Returns the fee percentage for team treasury
    function feePercentTeam() external view returns (uint256);

    /// @notice Returns the fee percentage for creator
    function feePercentCreator() external view returns (uint256);

    /// @notice Returns the trading fee percentage in basis points
    function tradingFee() external view returns (uint16);

}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.27;

import { IPrintrStorage } from "./IPrintrStorage.sol";

/**
 * @title Printr Trading Interface
 * @notice Interface for trading operations on tokens with linear bonding curves
 * @dev All price calculations use PRECISION_SCALAR (1e18) for accurate floating point math
 *      Handles buying, selling, and cross-chain trading operations
 */
interface IPrintrTrading is IPrintrStorage {

    /**
     * @notice Parameters for token trading operations
     * @param account Account performing the trade
     * @param token Address of the token being traded
     * @param amount Amount of tokens in the trade
     * @param priceLimit Maximum/minimum price limit for slippage protection
     * @param tokenSupply Current token supply before the trade
     * @param tradingFee Fee percentage applied to the trade
     */
    struct TradeParams {
        address account;
        address recipient;
        address token;
        uint256 amount;
        uint256 priceLimit;
        uint16 tradingFee;
    }

    /**
     * @notice Emitted when tokens are traded through the bonding curve
     * @param token Address of the token contract
     * @param trader Address that performed the trade
     * @param isBuy True if tokens were bought, false if sold
     * @param amount Number of tokens traded
     * @param cost Amount of base currency involved in the trade
     * @param priceAfter Price per token achieved in the trade
     * @param issuedSupply New total supply after the trade
     * @param reserve New reserve balance after the trade
     */
    event TokenTrade(
        address indexed token,
        address indexed trader,
        bool isBuy,
        uint256 amount,
        uint256 cost,
        uint256 priceAfter,
        uint256 issuedSupply,
        uint256 reserve
    );

    /**
     * @notice Emitted when a token graduates from bonding curve to liquidity pool
     * @param token Address of the token that graduated
     * @param totalSupply Total supply at graduation
     */
    event TokenGraduated(address indexed token, uint256 totalSupply);

    /**
     * @notice Emitted when a token graduation process encounters a partial failure
     * @dev Used for graceful degradation when liquidity deployment or post-graduation swap fails
     * @param token Address of the token that experienced partial graduation failure
     * @param stage Stage at which failure occurred (0=liquidity deployment, 1=post-graduation swap)
     */
    event TokenGraduationPartialFailure(address indexed token, uint8 stage, uint256 gasLeft);

    /**
     * @notice Estimates the cost of issuing a specific amount of tokens
     * @dev Uses linear bonding curve. All calculations are scaled by PRECISION_SCALAR
     * @param token Address of the token
     * @param tokenAmount Number of tokens to issue
     * @return availableAmount Amount of tokens available for issuing
     * @return cost Cost in base currency to issue the specified amount of tokens
     * @return fee Trading fee amount in base currency
     * @return priceAfter Effective price per token in base currency
     * @return issuedSupply New total supply after the issue
     */
    function estimateTokenCost(
        address token,
        uint256 tokenAmount
    ) external returns (uint256 availableAmount, uint256 cost, uint256 fee, uint256 priceAfter, uint256 issuedSupply);

    /**
     * @notice Quote the amount of tokens receivable for a specific amount of base currency
     * @dev Calculates tokens received including trading fees
     * @param token The token address
     * @param baseAmount The amount of base currency to spend
     * @return tokenAmount The amount of tokens receivable
     * @return cost The actual amount in base currency required for the purchase
     * @return fee The trading fee amount in base currency
     * @return priceAfter The effective price per token
     * @return issuedSupply The new total supply after the purchase
     */
    function quoteTokenAmount(
        address token,
        uint256 baseAmount
    ) external returns (uint256 tokenAmount, uint256 cost, uint256 fee, uint256 priceAfter, uint256 issuedSupply);

    /**
     * @notice Estimates the refund amount for redeeming a specific amount of tokens
     * @dev Uses the same linear bonding curve as issue, but in reverse
     * @param token Address of the token
     * @param tokenAmount Number of tokens to redeem
     * @return tokenAmountIn Amount of tokens that can actually be sold (capped by supply)
     * @return refund Refund amount in base currency for redeeming the specified tokens
     * @return fee Trading fee amount in base currency
     * @return priceAfter Effective price per token in base currency
     * @return issuedSupply New total supply after the redemption
     */
    function estimateTokenRefund(
        address token,
        uint256 tokenAmount
    ) external returns (uint256 tokenAmountIn, uint256 refund, uint256 fee, uint256 priceAfter, uint256 issuedSupply);

    /**
     * @notice Issues tokens according to the bonding curve
     * @param token Address of the token to issue
     * @param recipient Address to receive the issued tokens
     * @param amount Amount of tokens to issue
     * @param maxPrice Maximum acceptable price per token for slippage protection
     */
    function buy(
        address token,
        address recipient,
        uint256 amount,
        uint256 maxPrice
    ) external payable returns (TradeParams memory params);

    /**
     * @notice Buys tokens with a specified amount of base currency
     * @param token Address of the token to buy
     * @param recipient Address to receive the tokens
     * @param baseAmount Amount of base currency to spend. Pass type(uint256).max to use
     *                   the maximum available amount (all approved tokens or all sent ETH)
     * @param maxPrice Maximum acceptable price per token for slippage protection
     */
    function spend(
        address token,
        address recipient,
        uint256 baseAmount,
        uint256 maxPrice
    ) external payable returns (TradeParams memory params);

    /**
     * @notice Redeems tokens and returns base currency according to the bonding curve
     * @param token Address of the token to redeem
     * @param recipient Address to receive the refunded base currency
     * @param amount Amount of tokens to redeem. Pass type(uint256).max to sell all tokens
     * @param minPrice Minimum acceptable refund per token for slippage protection
     */
    function sell(
        address token,
        address recipient,
        uint256 amount,
        uint256 minPrice
    ) external returns (TradeParams memory params);

    /**
     * @notice Sells tokens and returns base currency according to the bonding curve
     * @dev Called by the Telecoin contract via permitWitnessCall
     * @param account Address of the account selling tokens
     * @param token Address of the token to sell
     * @param recipient Address to receive the refund
     * @param amount Amount of tokens to sell. Pass type(uint256).max to sell all tokens
     * @param minPrice Minimum acceptable price per token
     */
    function witnessSell(
        address account,
        address token,
        address recipient,
        uint256 amount,
        uint256 minPrice
    ) external returns (TradeParams memory params);

}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.20;

/**
 * @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 value of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

    /**
     * @dev Returns the value of tokens owned by `account`.
     */
    function balanceOf(address account) external view returns (uint256);

    /**
     * @dev Moves a `value` amount of 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 value) 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 a `value` amount of tokens 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 value) external returns (bool);

    /**
     * @dev Moves a `value` amount of tokens from `from` to `to` using the
     * allowance mechanism. `value` 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 value) external returns (bool);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";

/**
 * @dev Interface for the optional metadata functions from the ERC20 standard.
 */
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);
}

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

import { IPrintr } from "../IPrintr.sol";
import { ITreasury } from "../ITreasury.sol";

/**
 * @title Liquidity Module Interface
 * @notice Interface for managing AMM liquidity pools and swap operations
 * @dev Defines core functionality for pool creation, liquidity deployment, and token swaps
 */
interface ILiquidityModule {

    error InvalidTokenPair();
    error InvalidFeeTier();
    error InvalidQuoteResult();
    error QuoterRequired();
    error SignatureTooShort();
    error InsufficientOutputAmount();
    error PoolDoesNotExist();
    error Log2InvalidInput();
    error Exp2Overflow();

    /**
     * @notice Enumeration of supported liquidity module types
     * @dev Used to identify which DEX protocol implementation to use
     */
    enum ModuleType {
        UNISWAP_V3, // 0: Standard Uniswap V3 compatible DEX
        PANCAKE, // 1: PancakeSwap V3 compatible DEX
        ALGEBRA, // 2: Algebra/QuickSwap compatible DEX
        TRADERJOE, // 3: TraderJoe compatible DEX
        WAGMI, // 4: Wagmi compatible DEX
        LYNEX, // 5: Lynex compatible DEX
        UNISWAP_V3_WITH_LEGACY // 6: Uniswap V3 with legacy router support
    }

    /**
     * @notice Configuration for GoPlus locker integration
     * @param signature Signature for verifying custom fee structures
     * @param feeName Name of the fee structure to use
     * @param lpFee Fee for LP operations
     * @param collectFee Fee for collection operations (in basis points)
     */
    struct GoPlusConfig {
        bytes signature;
        string feeName;
        uint256 lpFee;
        uint256 collectFee;
    }

    /**
     * @notice Parameters for executing a token swap
     * @param tokenIn Address of token being sold
     * @param tokenOut Address of token being bought
     * @param amountIn Amount of input token to swap
     * @param amountOutMinimum Minimum amount of output token to receive
     * @param sqrtPriceLimitX96 Price limit for the swap in Q64.96 format
     * @param recipient Address to receive the output tokens
     */
    struct SwapParams {
        address tokenIn;
        address tokenOut;
        uint256 amountIn;
        uint256 amountOutMinimum;
        uint160 sqrtPriceLimitX96;
        address recipient;
    }

    /**
     * @notice Parameters for deploying liquidity to an AMM pool
     * @param curve Bonding curve parameters defining the pool
     * @param token Address of the token for liquidity provision
     * @param issuedSupply Current issued supply of the token from the bonding curve
     * @param treasury Treasury contract managing the assets
     */
    struct LiquidityDeployParams {
        IPrintr.Curve curve;
        address token;
        uint256 issuedSupply;
        ITreasury treasury;
    }

    /**
     * @notice Results from liquidity deployment
     * @param lockId0 ID of the first locked liquidity position
     * @param lockId1 ID of the second locked liquidity position
     * @param feeCollected Amount of fees collected during deployment
     */
    struct DeployResult {
        uint256 lockId0;
        uint256 lockId1;
        uint256 feeCollected;
    }

    /**
     * @notice Creates a new AMM pool for a token pair
     * @dev Initializes pool with specified price if it doesn't exist
     * @param curve Bonding curve parameters for the pool
     * @param tokenAddress Address of the token to pair with curve.basePair
     * @param completionPrice Initial price for pool initialization
     * @return poolAddress Address of the created or existing pool
     */
    function createPool(
        IPrintr.Curve memory curve,
        address tokenAddress,
        uint256 completionPrice
    ) external payable returns (address poolAddress);

    /**
     * @notice Executes a token swap through the AMM
     * @dev Handles both ERC20 and wrapped native token swaps
     * @param params Swap parameters including tokens and amounts
     * @return amountOut Amount of output tokens received from the swap
     */
    function executeSwap(
        SwapParams memory params
    ) external payable returns (uint256 amountOut);

    /**
     * @notice Deploys liquidity to AMM pools according to curve parameters
     * @dev Creates and locks liquidity positions in the AMM
     * @param params Parameters for liquidity deployment
     * @return result Structure containing lock IDs and fee information
     */
    function deployLiquidity(
        LiquidityDeployParams memory params
    ) external payable returns (DeployResult memory result);

    /**
     * @notice Gets a quote for exact input swap without execution
     * @dev Returns the expected output amount for a given input
     * @param params Swap parameters with exact input amount
     * @return amountOut Expected output amount
     * @return priceAfter The price after the swap
     * @return fee The fee tier of the pool in basis points
     */
    function quoteExactInput(
        SwapParams memory params
    ) external payable returns (uint256 amountOut, uint256 priceAfter, uint256 fee);

    /**
     * @notice Gets a quote for exact output swap without execution
     * @dev Returns the required input amount for a desired output
     *      Payable to support delegatecall from contexts with msg.value
     * @param params Swap parameters with exact output amount
     * @return amountIn Required input amount
     * @return priceAfter The price after the swap
     * @return fee The fee tier of the pool in basis points
     */
    function quoteExactOutput(
        SwapParams memory params
    ) external payable returns (uint256 amountIn, uint256 priceAfter, uint256 fee);

    /**
     * @notice Collects accumulated fees from a locked liquidity position
     * @dev Delegates to the appropriate locker contract (GoPlus, generic, etc.)
     *      Returns amounts in convention order: basePair first, token second
     * @param lockId Unique identifier of the locked LP position
     * @param token Address of the token in the trading pair
     * @param basePair Address of the base pair token
     * @param recipient Address that will receive the collected trading fees
     * @return baseAmount Amount of basePair token collected
     * @return tokenAmount Amount of token collected
     */
    function collectLiquidityFees(
        uint256 lockId,
        address token,
        address basePair,
        address recipient
    ) external returns (uint256 baseAmount, uint256 tokenAmount);

}

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

import { IERC721 } from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import { IERC721Metadata } from "@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata.sol";

/**
 * @title IPrintrDev
 * @notice Interface for the Dev NFT contract that tracks token creators
 * @dev Each PRINTR token gets one Dev NFT minted to its creator
 */
interface IPrintrDev is IERC721, IERC721Metadata {

    /// @notice Custom errors for the PrintrDev contract
    error OnlyPrintrCanMint();
    error PositionAlreadyCreated();

    /**
     * @notice Mint a new Dev NFT for a token
     * @dev Only callable by the Printr contract
     * @param telecoinId The PRINTR token ID (bytes32)
     * @param creator The creator who will receive the NFT
     * @return id The minted NFT token ID
     */
    function mint(
        bytes32 telecoinId,
        address creator
    ) external returns (uint256 id);

    /**
     * @notice Get the NFT token ID for a specific PRINTR token
     * @param token The PRINTR token address
     * @return id The NFT token ID (which is the token address as uint256)
     */
    function tokenToId(
        address token
    ) external view returns (uint256 id);

    /**
     * @notice Get the PRINTR token address for a specific NFT token ID
     * @param id The NFT token ID
     * @return token The PRINTR token address
     */
    function idToToken(
        uint256 id
    ) external view returns (address token);

}

File 13 of 47 : IPrintrTeleportingTelecoin.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import { IPrinted } from "./IPrinted.sol";
import { ITeleportingTelecoin } from "./ITeleporting.sol";

/**
 * @title IPrintrTeleportingTelecoin
 * @notice Interface for the PrintrTelecoin contract with teleport and curve completion functionality
 * @dev Extends ITelecoin interface with additional token management functions
 */
interface IPrintrTeleportingTelecoin is IPrinted, ITeleportingTelecoin { }

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

import { IOFT } from "../layerzero/IOFT.sol";
import { IPrintrTeleport } from "../printr/IPrintrTeleport.sol";
import { IERC20Witness } from "./IERC20Witness.sol";
import { IInterchainStandard } from "./IInterchainStandard.sol";

/**
 * @title ITelecoin
 * @notice Interface for the Telecoin base contract
 * @dev Extends IERC20 for base teleport functionality
 */
interface ITelecoin is IERC20Witness, IInterchainStandard, IOFT {

    /**
     * @notice Struct containing deployment parameters for telecoin contracts
     * @param name Token name
     * @param symbol Token symbol
     * @param maxSupply Maximum supply of the token
     * @param treasury Treasury address to receive initial supply
     * @param interchainTokenService Interchain Token Service address
     * @param itsTokenManager Token manager address
     * @param telecoinId Universal telecoin ID
     * @param interchainTokenId Interchain token ID for ITS compatibility
     */
    struct TelecoinDeployParams {
        bytes32 telecoinId;
        string name;
        string symbol;
        uint256 maxSupply;
        address printr;
        address interchainTokenService;
        address itsTokenManager;
        bytes32 interchainTokenId;
    }

    /**
     * @notice Error thrown when a zero address is provided where not allowed
     */
    error ZeroAddress();

    /**
     * @notice Error thrown when an unauthorized account attempts a restricted operation
     * @param account The address that attempted the unauthorized operation
     */
    error UnauthorizedAccount(address account);

    /**
     * @notice Error thrown when an invalid protocol is specified
     */
    error InvalidProtocol();

    /**
     * @notice Error thrown when a native token transfer fails
     */
    error TransferFailed();

    /**
     * @notice Emitted when tokens are teleported in from another chain
     * @param telecoinId The universal token ID
     * @param to The address receiving the tokens
     * @param value The amount of tokens teleported in
     */
    event TeleportIn(bytes32 indexed telecoinId, address indexed to, uint256 value);

    /**
     * @notice Emitted when tokens are teleported out to another chain
     * @param telecoinId The universal token ID
     * @param from The address from which tokens are teleported
     * @param value The amount of tokens teleported out
     */
    event TeleportOut(bytes32 indexed telecoinId, address indexed from, uint256 value);

    /**
     * @notice Returns the universal token ID (deploySalt)
     * @dev This value is immutable and set during contract construction
     * @return The universal token ID based on deployment salt
     */
    function telecoinId() external view returns (bytes32);

    /**
     * @notice Returns the address of the Printr contract that created this token
     * @dev This value is immutable and set during contract construction
     * @return The address of the Printr contract
     */
    function printr() external view returns (address);

    /**
     * @notice Returns the address of the ITS token manager for minting and burning
     * @dev This value is immutable and set during contract construction
     * @return The address of the ITS token manager
     */
    function itsTokenManager() external view returns (address);

    /**
     * @notice Quotes the total teleport fee for any protocol
     * @dev Delegates to PrintrTeleport's quoteTeleportFee for fee calculation
     * @param params The teleport parameters struct (includes protocol)
     * @return totalNativeFee The total fee in native currency (protocol fee + bridge fee)
     * @return basePairFee The fee in base pair tokens (0 if base pair is native)
     * @return basePair The base pair address (address(0) if native)
     * @return bridgeFee The bridge-specific gas fee (ITS gas fee or LZ messaging fee)
     */
    function quoteTeleportFee(
        IPrintrTeleport.TeleportParams memory params
    ) external returns (uint256 totalNativeFee, uint256 basePairFee, address basePair, uint256 bridgeFee);

    /**
     * @notice Teleports tokens from sender to a destination chain
     * @param params The teleport parameters struct (includes protocol)
     */
    function teleport(
        IPrintrTeleport.TeleportParams calldata params
    ) external payable;

    /**
     * @notice Teleports tokens from a specified sender to a destination chain
     * @param sender The sender of the tokens (must have approved msg.sender)
     * @param params The teleport parameters struct (includes protocol)
     */
    function teleportFrom(
        address sender,
        IPrintrTeleport.TeleportParams calldata params
    ) external payable;

}

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

import { ITelecoin } from "./ITelecoin.sol";

/**
 * @title Telecoin Factory Interface
 * @notice Interface for cross-chain telecoin deployment and management
 * @dev Provides functionality for deterministic token deployment and addressing across chains
 *      Integrates with Axelar's Interchain Token Service (ITS)
 */
interface ITelecoinFactory {

    /**
     * @notice Computes the deterministic address for a token deployment
     * @dev Uses CREATE3 address computation formula to ensure consistency across chains
     * @param telecoinId Universal token identifier (used as CREATE3 salt)
     * @return token The computed address where the token will be deployed
     */
    function tokenAddress(
        address deployer,
        bytes32 telecoinId
    ) external view returns (address token);

    /**
     * @notice Deploys a new token contract at a deterministic address
     * @dev Uses CREATE3 for deployment and handles initial ownership setup
     * @param params The deployment parameters for the telecoin
     * @param treasury Address to receive the initial token supply
     * @param initialSupply The initial supply to mint
     * @return token Address of the deployed token contract
     */
    function deployToken(
        ITelecoin.TelecoinDeployParams memory params,
        address treasury,
        uint256 initialSupply
    ) external payable returns (address token);

}

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

/**
 * @title AddressBytes32
 * @notice Utility library for converting between address and bytes32 types
 * @dev Provides type casting operations for address-bytes32 conversion without modifying data
 */
library AddressBytes32 {

    /**
     * @notice Converts a bytes32 value to an address
     * @dev Performs type casting by taking the last 20 bytes (address length) from bytes32
     * @param bytesAddress The bytes32 value to convert (only last 20 bytes are used)
     * @return addr The resulting address
     */
    function toAddress(
        bytes32 bytesAddress
    ) internal pure returns (address addr) {
        // Convert bytes32 to uint256, then take last 20 bytes by masking to uint160
        addr = address(uint160(uint256(bytesAddress)));
    }

    /**
     * @notice Converts an address to bytes32
     * @dev Performs type casting by zero-padding address to 32 bytes
     * @param addr The address to convert
     * @return bytesAddress The bytes32 representation (address zero-padded to 32 bytes)
     */
    function toBytes32(
        address addr
    ) internal pure returns (bytes32 bytesAddress) {
        // Convert address to uint160, then to uint256, then to bytes32
        bytesAddress = bytes32(uint256(uint160(addr)));
    }

}

// SPDX-License-Identifier: MIT
// https://github.com/axelarnetwork/axelar-gmp-sdk-solidity/blob/main/contracts/libs/Bytes32String.sol
pragma solidity ^0.8.27;

/**
 * @title StringToBytes32
 * @notice Utility library for converting strings to bytes32 with length preservation
 * @dev Encodes strings up to 31 bytes, storing length in the last byte
 */
library StringToBytes32 {

    /**
     * @notice Thrown when string length is 0 or exceeds 31 bytes
     * @dev Limited to 31 bytes as last byte is used to store length
     */
    error InvalidStringLength();

    /**
     * @notice Converts a string to bytes32 while preserving its length
     * @dev Stores string length in the last byte of bytes32
     * @param str The string to convert (must be 1-31 bytes)
     * @return The bytes32 representation with length encoded
     * @custom:throws InvalidStringLength if string is empty or longer than 31 bytes
     */
    function toBytes32(
        string memory str
    ) internal pure returns (bytes32) {
        // Convert string to bytes for length check and manipulation
        bytes memory stringBytes = bytes(str);

        // Verify length constraints (0 < length <= 31)
        if (stringBytes.length == 0 || stringBytes.length > 31) {
            revert InvalidStringLength();
        }

        // Convert string bytes to uint256 for bitwise operations
        uint256 stringNumber = uint256(bytes32(stringBytes));

        // Store length in last byte using bitwise OR
        stringNumber |= 0xff & stringBytes.length;
        return bytes32(stringNumber);
    }

}

/**
 * @title Bytes32ToString
 * @notice Utility library for converting bytes32 back to strings
 * @dev Recovers original string using length stored in last byte
 */
library Bytes32ToString {

    /**
     * @notice Converts bytes32 back to string, trimming to original length
     * @dev Uses assembly for efficient memory operations
     * @param stringData bytes32 value containing string data and length
     * @return converted The recovered string with correct length
     */
    function toTrimmedString(
        bytes32 stringData
    ) internal pure returns (string memory converted) {
        // Extract length from last byte
        uint256 length = 0xff & uint256(stringData);

        // Use assembly for efficient memory operations
        assembly {
            // Allocate memory for string
            converted := mload(0x40)
            // Update free memory pointer (add 64 bytes: 32 for length, 32 for data)
            mstore(0x40, add(converted, 0x40))
            // Store string length
            mstore(converted, length)
            // Store string data
            mstore(add(converted, 0x20), stringData)
        }
    }

}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Pausable.sol)

pragma solidity ^0.8.20;

import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.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 PausableUpgradeable is Initializable, ContextUpgradeable {
    /// @custom:storage-location erc7201:openzeppelin.storage.Pausable
    struct PausableStorage {
        bool _paused;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Pausable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant PausableStorageLocation = 0xcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f03300;

    function _getPausableStorage() private pure returns (PausableStorage storage $) {
        assembly {
            $.slot := PausableStorageLocation
        }
    }

    /**
     * @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);

    /**
     * @dev The operation failed because the contract is paused.
     */
    error EnforcedPause();

    /**
     * @dev The operation failed because the contract is not paused.
     */
    error ExpectedPause();

    /**
     * @dev Initializes the contract in unpaused state.
     */
    function __Pausable_init() internal onlyInitializing {
        __Pausable_init_unchained();
    }

    function __Pausable_init_unchained() internal onlyInitializing {
        PausableStorage storage $ = _getPausableStorage();
        $._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) {
        PausableStorage storage $ = _getPausableStorage();
        return $._paused;
    }

    /**
     * @dev Throws if the contract is paused.
     */
    function _requireNotPaused() internal view virtual {
        if (paused()) {
            revert EnforcedPause();
        }
    }

    /**
     * @dev Throws if the contract is not paused.
     */
    function _requirePaused() internal view virtual {
        if (!paused()) {
            revert ExpectedPause();
        }
    }

    /**
     * @dev Triggers stopped state.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    function _pause() internal virtual whenNotPaused {
        PausableStorage storage $ = _getPausableStorage();
        $._paused = true;
        emit Paused(_msgSender());
    }

    /**
     * @dev Returns to normal state.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    function _unpause() internal virtual whenPaused {
        PausableStorage storage $ = _getPausableStorage();
        $._paused = false;
        emit Unpaused(_msgSender());
    }
}

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

/**
 * @title ITreasury
 * @notice Interface for managing protocol fee collection and token withdrawals
 * @dev Handles LP fee collection and controlled withdrawals of tokens/native currency
 */
interface ITreasury {

    /**
     * @notice Thrown when an unauthorized address attempts to access treasury functions
     */
    error WrongAccess();

    /**
     * @notice Thrown when a withdrawal of native currency fails
     */
    error FailedWithdrawal();

    /**
     * @notice Thrown when a zero address is provided where a valid address is required
     */
    error ZeroAddress();

    /**
     * @notice Emitted when fees are collected from a liquidity position
     * @param token0 Address of the first token in the pair
     * @param token1 Address of the second token in the pair
     * @param recipient Address receiving the collected fees
     * @param amount0 Amount of token0 collected
     * @param amount1 Amount of token1 collected
     * @param lockId ID of the locked liquidity position
     */
    event CollectedLiquidityFees(
        address indexed token0,
        address indexed token1,
        address indexed recipient,
        uint256 amount0,
        uint256 amount1,
        uint256 lockId
    );

    /**
     * @notice Collects accumulated fees from a locked liquidity position by delegating to the liquidity module
     * @dev Only callable by authorized addresses. Delegates to the appropriate liquidity module
     *      which handles locker-specific logic (GoPlus, generic, etc.) and token ordering.
     * @param liquidityModule Address of the liquidity module that manages this position's DEX
     * @param lockId ID of the locked liquidity position
     * @param token Address of the first token in the pair
     * @param basePair Address of the second token in the pair (base currency)
     * @param recipient Address to receive the collected fees
     * @custom:throws WrongAccess if caller is not authorized
     */
    function collectLiquidityFees(
        address liquidityModule,
        uint256 lockId,
        address token,
        address basePair,
        address recipient
    ) external;

    /**
     * @notice Withdraws tokens or native currency from the treasury
     * @dev Only callable by authorized addresses
     * @param token Address of token to withdraw (address(0) for native currency)
     * @param recipient Address to receive the withdrawal
     * @param amount Amount to withdraw
     * @custom:throws WrongAccess if caller is not authorized
     * @custom:throws FailedWithdrawal if native currency transfer fails
     */
    function withdraw(
        address token,
        address recipient,
        uint256 amount
    ) external;

    /**
     * @notice Handles receipt of ERC721 tokens (LP position NFTs)
     * @dev Required for compatibility with ERC721 token transfers
     * @return bytes4 Function selector to indicate successful receipt
     */
    function onERC721Received(
        address, /* operator */
        address, /* from */
        uint256, /* tokenId */
        bytes calldata /* data */
    ) external pure returns (bytes4);

}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.20;

import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../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 An operation with an ERC20 token failed.
     */
    error SafeERC20FailedOperation(address token);

    /**
     * @dev Indicates a failed `decreaseAllowance` request.
     */
    error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);

    /**
     * @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.encodeCall(token.transfer, (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.encodeCall(token.transferFrom, (from, to, 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);
        forceApprove(token, spender, oldAllowance + value);
    }

    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
     * value, non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
        unchecked {
            uint256 currentAllowance = token.allowance(address(this), spender);
            if (currentAllowance < requestedDecrease) {
                revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
            }
            forceApprove(token, spender, currentAllowance - requestedDecrease);
        }
    }

    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
     * to be set to zero before setting it to a non-zero value, such as USDT.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
        bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));

        if (!_callOptionalReturnBool(token, approvalCall)) {
            _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
            _callOptionalReturn(token, approvalCall);
        }
    }

    /**
     * @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);
        if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
            revert SafeERC20FailedOperation(address(token));
        }
    }

    /**
     * @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(token).code.length > 0;
    }
}

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

pragma solidity ^0.8.20;

/**
 * @dev Standard math utilities missing in the Solidity language.
 */
library Math {
    /**
     * @dev Muldiv operation overflow.
     */
    error MathOverflowedMulDiv();

    enum Rounding {
        Floor, // Toward negative infinity
        Ceil, // Toward positive infinity
        Trunc, // Toward zero
        Expand // Away from zero
    }

    /**
     * @dev Returns the addition of two unsigned integers, with an overflow flag.
     */
    function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            uint256 c = a + b;
            if (c < a) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, with an overflow flag.
     */
    function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b > a) return (false, 0);
            return (true, a - b);
        }
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, with an overflow flag.
     */
    function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
            // benefit is lost if 'b' is also tested.
            // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
            if (a == 0) return (true, 0);
            uint256 c = a * b;
            if (c / a != b) return (false, 0);
            return (true, c);
        }
    }

    /**
     * @dev Returns the division of two unsigned integers, with a division by zero flag.
     */
    function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a / b);
        }
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
     */
    function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
        unchecked {
            if (b == 0) return (false, 0);
            return (true, a % b);
        }
    }

    /**
     * @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 towards infinity instead
     * of rounding towards zero.
     */
    function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
        if (b == 0) {
            // Guarantee the same behavior as in a regular Solidity division.
            return a / b;
        }

        // (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 = x * y; // Least significant 256 bits of the product
            uint256 prod1; // Most significant 256 bits of the product
            assembly {
                let mm := mulmod(x, y, not(0))
                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.
            if (denominator <= prod1) {
                revert MathOverflowedMulDiv();
            }

            ///////////////////////////////////////////////
            // 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.

            uint256 twos = denominator & (0 - denominator);
            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 (unsignedRoundsUp(rounding) && 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
     * towards zero.
     *
     * 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 + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 2 of a positive value rounded towards zero.
     * 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 + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 10 of a positive value rounded towards zero.
     * 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 + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
        }
    }

    /**
     * @dev Return the log in base 256 of a positive value rounded towards zero.
     * 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 + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
        }
    }

    /**
     * @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
     */
    function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
        return uint8(rounding) % 2 == 1;
    }
}

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import { IInterchainTokenService } from "../its/IInterchainTokenService.sol";

/**
 * @title IInterchainStandard Interface
 * @notice This interface defines functions for cross-chain token transfers.
 */
interface IInterchainStandard {

    /**
     * @notice Returns the Interchain Token Service instance.
     * @return The IInterchainTokenService contract instance.
     */
    function interchainTokenService() external view returns (IInterchainTokenService);

    /**
     * @notice Returns the interchain token identifier.
     * @return The bytes32 identifier of the interchain token.
     */
    function interchainTokenId() external view returns (bytes32);

    /**
     * @notice Implementation of the interchainTransfer method.
     * @dev We chose to either pass `metadata` as raw data on a remote contract call, or if no data is passed, just do a
     * transfer.
     * A different implementation could use metadata to specify a function to invoke, or for other purposes as well.
     * @param destinationChain The destination chain identifier.
     * @param recipient The bytes representation of the address of the recipient.
     * @param amount The amount of token to be transferred.
     * @param metadata Optional metadata for the call for additional effects (such as calling a destination contract).
     */
    function interchainTransfer(
        string calldata destinationChain,
        bytes calldata recipient,
        uint256 amount,
        bytes calldata metadata
    ) external payable;

    /**
     * @notice Implementation of the interchainTransferFrom method
     * @dev We chose to either pass `metadata` as raw data on a remote contract call, or, if no data is passed, just do
     * a transfer.
     * A different implementation could use metadata to specify a function to invoke, or for other purposes as well.
     * @param sender The sender of the tokens. They need to have approved `msg.sender` before this is called.
     * @param destinationChain The string representation of the destination chain.
     * @param recipient The bytes representation of the address of the recipient.
     * @param amount The amount of token to be transferred.
     * @param metadata Optional metadata for the call for additional effects (such as calling a destination contract.)
     */
    function interchainTransferFrom(
        address sender,
        string calldata destinationChain,
        bytes calldata recipient,
        uint256 amount,
        bytes calldata metadata
    ) external payable;

}

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

/**
 * @title IWETH
 * @notice Interface for Wrapped Ether (WETH) - an ERC20 token that wraps native ETH
 */
interface IWETH {

    /**
     * @notice Deposits native ETH into the contract to mint WETH tokens
     * @dev The amount of WETH minted equals the amount of ETH sent with the transaction
     */
    function deposit() external payable;

    /**
     * @notice Unwraps WETH back to ETH by burning tokens and transferring ETH
     * @param wad Amount of WETH to unwrap into ETH
     */
    function withdraw(
        uint256 wad
    ) external;

}

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

import "@openzeppelin/contracts/utils/math/Math.sol";

/**
 * @title SqrtPriceMath
 * @notice Pure math utility library for sqrt price conversions
 * @dev Provides shared price conversion logic to avoid code duplication across liquidity modules
 */
library SqrtPriceMath {

    using Math for uint256;

    /// @dev Precision scalar for price calculations (18 decimal places)
    /// @notice Used to maintain precision in price conversion calculations
    uint256 private constant PRECISION_SCALAR = 1e18;

    /**
     * @notice Converts price to sqrtPriceX96 format
     * @dev Converts a price value to Uniswap V3 sqrtPriceX96 format
     *      Formula: sqrtPriceX96 = sqrt(price * 2^192 / PRECISION_SCALAR)
     *      where 2^192 accounts for the Q64.96 fixed point representation
     * @param price Price value in PRECISION_SCALAR format (1e18)
     * @return sqrtPriceX96 The price in sqrtPriceX96 format as uint160
     */
    function toSqrtPriceX96(
        uint256 price
    ) internal pure returns (uint160 sqrtPriceX96) {
        return uint160(price.mulDiv(1 << 192, PRECISION_SCALAR).sqrt());
    }

    /**
     * @notice Converts sqrtPriceX96 to price format
     * @dev Converts a sqrtPriceX96 value back to regular price format
     *      Formula: price = (sqrtPriceX96^2 * PRECISION_SCALAR) / 2^192
     *      This is the inverse of toSqrtPriceX96 function
     * @param sqrtPriceX96 Price in sqrtPriceX96 format
     * @return price The price in PRECISION_SCALAR format (1e18)
     */
    function fromSqrtPriceX96(
        uint160 sqrtPriceX96
    ) internal pure returns (uint256 price) {
        return uint256(sqrtPriceX96).mulDiv(uint256(sqrtPriceX96) * PRECISION_SCALAR, 1 << 192);
    }

}

File 25 of 47 : IPrintr.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import { IModule } from "./IModule.sol";
import { IPrintrCore } from "./IPrintrCore.sol";
import { ITeleport } from "./ITeleport.sol";

/**
 * @title IPrintr
 * @notice Master interface combining all Printr protocol functionality
 * @dev Aggregates implementation, teleport, and module interfaces for complete protocol access
 *
 * Components:
 * - IPrintrCore: Core implementation interfaces (storage, printing, trading)
 * - ITeleport: Teleport interfaces (interchain, teleport)
 * - IModule: Module interfaces (getters, fee distribution, owner) - accessed via fallback delegation
 */
interface IPrintr is IPrintrCore, ITeleport, IModule { }

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

pragma solidity ^0.8.20;

import {IERC165} from "../../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 address zero.
     *
     * 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);
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC721/extensions/IERC721Metadata.sol)

pragma solidity ^0.8.20;

import {IERC721} from "../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);
}

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

/**
 * @title IPrintrTeleportingTelecoin
 * @notice Interface for the PrintrTelecoin contract with teleport and curve completion functionality
 * @dev Extends ITelecoin interface with additional token management functions
 */
interface IPrinted {

    /**
     * @notice Enum representing the teleport type of a Telecoin
     * @dev Values 0-1 are legacy types for backward compatibility with deployed contracts
     *      Values 2-3 are new types that support teleporting by calling methods on a Telecoin
     *      - LegacyMain (0): Legacy Main Telecoin (bool false, uses lock/unlock)
     *      - LegacyTeleporting (1): Legacy Teleporting Telecoin (bool true, uses mint/burn)
     *      - Main (2): New Main Telecoin with Telecoin.teleport() support
     *      - Teleporting (3): New Teleporting Telecoin with Telecoin.teleport() support
     */
    enum TeleportType {
        LegacyMain, // 0 - backward compatible with bool false
        LegacyTeleporting, // 1 - backward compatible with bool true
        Main, // 2 - new Main Telecoin type
        Teleporting // 3 - new Teleporting Telecoin type
    }

    /**
     * @notice Error thrown when attempting to interact with an incomplete curve
     * @dev This error is typically thrown when trying to add liquidity before curve completion
     */
    error CurveIsNotComplete();

    /**
     * @notice Returns the teleport type of this Telecoin
     * @dev Values 0-1 are legacy (backward compatible), values 2-3 support transmitLzSend
     * @return teleportType The TeleportType enum value:
     *         - LegacyMain (0): Legacy Main Telecoin (lock/unlock)
     *         - LegacyTeleporting (1): Legacy Teleporting Telecoin (mint/burn)
     *         - Main (2): New Main Telecoin with transmitLzSend support
     *         - Teleporting (3): New Teleporting Telecoin with transmitLzSend support
     */
    function isTeleporting() external view returns (TeleportType teleportType);

    /**
     * @notice Sets the restricted pool address during curve initialization
     * @dev Can only be called by Printr before completion
     * @param poolAddress The pool address to restrict transfers to
     * @custom:throws UnauthorizedAccount if caller is not Printr or curve is already complete
     * @custom:throws ZeroAddress if pool address is zero
     */
    function setRestrictedPool(
        address poolAddress
    ) external;

    /**
     * @notice Marks the curve as completed, removing transfer restrictions
     * @dev Can only be called by Printr when curve is not already completed
     * @custom:throws UnauthorizedAccount if caller is not Printr or curve is already completed
     */
    function markCurveComplete() external;

}

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

import { ITelecoin } from "./ITelecoin.sol";

/**
 * @title ITeleportingTelecoin
 * @notice Interface for the TeleportingTelecoin contract with teleporting capabilities
 * @dev Extends ITelecoin for teleport functionality with ITS integration
 */
interface ITeleportingTelecoin is ITelecoin {

    /**
     * @notice Error thrown when invalid function selector is provided
     */
    error InvalidFunctionSelector();

    /**
     * @notice Teleports tokens into existence for cross-chain transfers
     * @param to Address to receive the tokens
     * @param value Amount of tokens to teleport in
     */
    function teleportIn(
        address to,
        uint256 value
    ) external;

    /**
     * @notice Teleports tokens out of existence for cross-chain transfers
     * @param from Address whose tokens are being teleported out
     * @param value Amount of tokens to teleport out
     */
    function teleportOut(
        address from,
        uint256 value
    ) external;

}

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

import { ILayerZeroEndpointV2 } from "./ILayerZeroEndpointV2.sol";

/**
 * @dev Struct representing token parameters for the OFT send() operation.
 */
struct SendParam {
    uint32 dstEid; // Destination endpoint ID
    bytes32 to; // Recipient address
    uint256 amountLD; // Amount to send in local decimals
    uint256 minAmountLD; // Minimum amount to send in local decimals
    bytes extraOptions; // Additional options for LayerZero message
    bytes composeMsg; // Composed message for send() operation
    bytes oftCmd; // OFT command (unused in default implementations)
}

/**
 * @dev Struct representing OFT limit information.
 */
struct OFTLimit {
    uint256 minAmountLD; // Minimum amount in local decimals
    uint256 maxAmountLD; // Maximum amount in local decimals
}

/**
 * @dev Struct representing OFT receipt information.
 */
struct OFTReceipt {
    uint256 amountSentLD; // Amount debited from sender
    uint256 amountReceivedLD; // Amount to be received on remote side
}

/**
 * @dev Struct representing OFT fee details.
 */
struct OFTFeeDetail {
    int256 feeAmountLD; // Amount of fee in local decimals
    string description; // Description of the fee
}

/**
 * @title IOFT
 * @dev Interface for the Omnichain Fungible Token (OFT) standard.
 * @dev Interface ID: 0x02e49c2c
 */
interface IOFT {

    // Custom errors
    error InvalidLocalDecimals();
    error SlippageExceeded(uint256 amountLD, uint256 minAmountLD);

    // Events
    event OFTSent(
        bytes32 indexed guid, uint32 dstEid, address indexed fromAddress, uint256 amountSentLD, uint256 amountReceivedLD
    );

    event OFTReceived(bytes32 indexed guid, uint32 srcEid, address indexed toAddress, uint256 amountReceivedLD);

    /**
     * @notice Retrieves interfaceID and version of the OFT.
     * @return interfaceId The interface ID (0x02e49c2c).
     * @return version The version.
     */
    function oftVersion() external view returns (bytes4 interfaceId, uint64 version);

    /**
     * @notice Retrieves the address of the token associated with the OFT.
     * @return token The address of the ERC20 token implementation.
     */
    function token() external view returns (address);

    /**
     * @notice Indicates whether the OFT contract requires approval of the 'token()' to send.
     * @return requiresApproval Whether approval is required.
     */
    function approvalRequired() external view returns (bool);

    /**
     * @notice Retrieves the shared decimals of the OFT.
     * @return sharedDecimals The shared decimals (typically 6).
     */
    function sharedDecimals() external view returns (uint8);

    /**
     * @notice Provides a quote for OFT-related operations.
     * @param _sendParam The parameters for the send operation.
     * @return limit The OFT limit information.
     * @return oftFeeDetails The details of OFT fees.
     * @return receipt The OFT receipt information.
     */
    function quoteOFT(
        SendParam calldata _sendParam
    ) external returns (OFTLimit memory limit, OFTFeeDetail[] memory oftFeeDetails, OFTReceipt memory receipt);

    /**
     * @notice Provides a quote for the send() operation.
     * @param _sendParam The parameters for the send() operation.
     * @param _payInLzToken Flag indicating whether the caller is paying in the LZ token.
     * @return fee The calculated LayerZero messaging fee.
     */
    function quoteSend(
        SendParam calldata _sendParam,
        bool _payInLzToken
    ) external returns (ILayerZeroEndpointV2.MessagingFee memory fee);

    /**
     * @notice Executes the send() operation.
     * @param _sendParam The parameters for the send operation.
     * @param _fee The fee information supplied by the caller.
     * @param _refundAddress The address to receive any excess funds.
     * @return receipt The LayerZero messaging receipt.
     * @return oftReceipt The OFT receipt information.
     */
    function send(
        SendParam calldata _sendParam,
        ILayerZeroEndpointV2.MessagingFee calldata _fee,
        address _refundAddress
    ) external payable returns (ILayerZeroEndpointV2.MessagingReceipt memory receipt, OFTReceipt memory oftReceipt);

}

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

import { IPrintrStorage } from "../printr/IPrintrStorage.sol";

/**
 * @title IPrintrTeleport
 * @notice Interface for PrintrTeleport contract that handles cross-chain messaging with Solana
 * @dev This interface defines the functionality for sending and receiving messages between EVM and Solana
 *      using the Teleport message protocol for token transfers
 */
interface IPrintrTeleport is IPrintrStorage {

    /**
     * @notice Error thrown when payload has invalid length
     */
    error InvalidPayloadLength();

    /**
     * @notice Error thrown when message kind is unexpected
     */
    error UnexpectedMessageKind();

    /**
     * @notice Error thrown when endpoint address is invalid
     */
    error InvalidEndpoint();

    /**
     * @notice Error thrown when solana program address is invalid
     */
    error InvalidSolanaProgram();

    /**
     * @notice Error thrown when sender is not authorized
     */
    error UnauthorizedSender();

    /**
     * @notice Error thrown when contract has insufficient fee balance
     */
    error InsufficientFee();

    /**
     * @notice Error thrown when fee transfer fails
     */
    error FeeTransferFailed();

    /**
     * @notice Error thrown when an invalid protocol is specified
     */
    error InvalidProtocol();

    /**
     * @notice Error thrown when the chain ID length is invalid
     */
    error InvalidChainIdLength();

    /**
     * @notice Error thrown when a non-numeric character is found in chain ID
     */
    error InvalidNumericCharacter();

    /**
     * @notice Error thrown when the endpoint ID is invalid
     */
    error InvalidEndpointId();

    /**
     * @notice Error thrown when calldata is invalid
     */
    error InvalidCalldata();

    /**
     * @notice Error thrown when security level is invalid
     */
    error InvalidSecurityLevel();

    /**
     * @notice Error thrown when protocol is not in global whitelist
     */
    error ProtocolNotWhitelisted();

    /**
     * @notice Error thrown when protocol is in token blacklist
     */
    error ProtocolBlacklisted();

    /**
     * @notice Error thrown when invalid protocol bit position used
     */
    error InvalidProtocolBitPosition();

    /**
     * @notice Error thrown when teleport amount exceeds maximum supported value
     * @dev Maximum supported amount is type(uint64).max * 1e9 (in 18 decimals)
     * @param requestedAmount The amount that was requested to teleport
     * @param maxAmount The maximum amount that can be teleported
     */
    error TeleportAmountOverflow(uint256 requestedAmount, uint256 maxAmount);

    /**
     * @notice Protocol enum for teleportation methods
     * @dev Must match the Protocol enum in ITelecoin for compatibility
     */
    enum TeleportProtocol {
        UNSPECIFIED, // 0 - Used to catch malformed transactions
        ITS, // 1 - Interchain Token Service
        LZ_FAST, // 2 - Fast LayerZero channel with minimal confirmations
        LZ_SECURE, // 3 - Secure LayerZero channel with balanced confirmations
        LZ_SLOW // 4 - Slow LayerZero channel with maximum confirmations
    }

    /**
     * @notice Struct containing constructor parameters for LayerZero deployment
     * @param itsFlatFee Flat fee for ITS transfers
     * @param itsBipsFee Basis points fee for ITS transfers
     * @param lzFlatFee Flat fee for LayerZero transfers
     * @param lzBipsFee Basis points fee for LayerZero transfers
     * @param lzEndpoint LayerZero endpoint address for this chain
     * @param fastSolanaLzPeer Solana LayerZero peer address for fast/low security channel
     * @param secureSolanaLzPeer Solana LayerZero peer address for secure/medium security channel
     * @param slowSolanaLzPeer Solana LayerZero peer address for slow/high security channel
     * @param lzReceiveGasLimit Gas limit for LayerZero receive operations
     * @param lzReceiveNativeDrop Native drop amount for LayerZero receive operations
     */
    struct TeleportDeployParams {
        uint256 itsFlatFee;
        uint16 itsBipsFee;
        uint256 lzFlatFee;
        uint16 lzBipsFee;
        address lzEndpoint;
        bytes32 fastSolanaLzPeer;
        bytes32 secureSolanaLzPeer;
        bytes32 slowSolanaLzPeer;
        uint128 lzReceiveGasLimit;
        uint128 lzReceiveNativeDrop;
    }

    /**
     * @notice Struct containing teleport parameters
     * @param destChain The destination chain identifier
     * @param destAddress The bytes representation of the address of the recipient
     * @param amount The amount of tokens to be transferred
     * @param metadata Additional data for the cross-chain transfer
     * @param protocol The teleportation protocol to use (ITS, LZ_FAST, LZ_SECURE, LZ_SLOW)
     */
    struct TeleportParams {
        string destChain;
        bytes destAddress;
        uint256 amount;
        bytes metadata;
        TeleportProtocol protocol;
    }

    /**
     * @notice Event emitted when a new LayerZero delegate is set for a channel
     * @param protocol The teleport protocol (LZ_FAST, LZ_SECURE, LZ_SLOW)
     * @param delegate Address of the new LayerZero delegate
     */
    event LzDelegateUpdated(TeleportProtocol indexed protocol, address indexed delegate);

    /**
     * @notice Event emitted when global protocol whitelist is updated
     * @param newWhitelist The new whitelist bitmap
     */
    event TeleportProtocolWhitelistUpdated(bytes32 indexed newWhitelist);

    /**
     * @notice Event emitted when a token's protocol blacklist is updated
     * @param telecoinId The telecoin ID
     * @param newBlacklist The new blacklist bitmap
     */
    event TelecoinProtocolBlacklistUpdated(bytes32 indexed telecoinId, bytes32 indexed newBlacklist);

    /**
     * @notice Event emitted when tokens are teleported to another chain
     * @param telecoinId The ID of the token being transferred
     * @param tokenAddress The address of the token contract
     * @param sourceAddress The address initiating the transfer
     * @param destChain The target chain for the transfer
     * @param destAddress The address on the destination chain
     * @param amount The amount of tokens being transferred
     */
    event Teleporting(
        bytes32 indexed telecoinId,
        address indexed tokenAddress,
        address indexed sourceAddress,
        string destChain,
        bytes destAddress,
        uint256 amount
    );

    // ============================================
    // EXTERNAL FUNCTIONS
    // ============================================

    /**
     * @notice Calculates the universal token ID based on token parameters
     * @dev Returns the telecoinId as a universal token ID that's vendor-agnostic
     * @param tokenParams Parameters for the token deployment
     * @return telecoinId The universal token ID
     */
    function getTelecoinId(
        TelecoinParams calldata tokenParams
    ) external pure returns (bytes32 telecoinId);

    /**
     * @notice Sets the LayerZero delegate address for a specific channel
     * @dev Only callable by the contract owner or authorized account
     * @param protocol The teleport protocol to configure (LZ_FAST, LZ_SECURE, LZ_SLOW)
     * @param delegate Address of the new LayerZero delegate
     */
    function setLzDelegate(
        TeleportProtocol protocol,
        address delegate
    ) external;

    /**
     * @notice Gets the LZChannel address for a specific protocol
     * @param protocol The teleport protocol (LZ_FAST, LZ_SECURE, LZ_SLOW)
     * @return channel Address of the LZChannel contract for this protocol
     */
    function getLzChannel(
        TeleportProtocol protocol
    ) external view returns (address channel);

    /**
     * @notice Updates the global protocol whitelist by enabling and/or disabling protocols
     * @dev Only callable by the contract owner. Processes disable array first, then enable array.
     * @param enable Array of protocols to enable globally
     * @param disable Array of protocols to disable globally
     */
    function updateGlobalProtocols(
        TeleportProtocol[] calldata enable,
        TeleportProtocol[] calldata disable
    ) external;

    /**
     * @notice Gets the global protocol whitelist bitmap
     * @return whitelist Bitmap of enabled protocols
     */
    function getTeleportProtocolWhitelist() external view returns (bytes32 whitelist);

    /**
     * @notice Updates the protocol blacklist for a specific telecoin
     * @dev Only callable by the contract owner. Processes enable array first (removes from blacklist), then disable
     * array (adds to blacklist).
     * @param telecoinId The ID of the telecoin
     * @param enable Array of protocols to enable for this telecoin (remove from blacklist)
     * @param disable Array of protocols to disable for this telecoin (add to blacklist)
     */
    function updateTelecoinProtocols(
        bytes32 telecoinId,
        TeleportProtocol[] calldata enable,
        TeleportProtocol[] calldata disable
    ) external;

    /**
     * @notice Gets a token's protocol blacklist bitmap
     * @param telecoinId The telecoin ID
     * @return blacklist Bitmap of disabled protocols for this token
     */
    function getTelecoinProtocolBlacklist(
        bytes32 telecoinId
    ) external view returns (bytes32 blacklist);

    /**
     * @notice Checks if a protocol is allowed for a specific token
     * @dev Returns true if protocol is in global whitelist AND NOT in token blacklist
     * @param telecoinId The telecoin ID
     * @param protocol The protocol to check
     * @return allowed True if protocol is allowed for this token
     */
    function isProtocolAllowed(
        bytes32 telecoinId,
        TeleportProtocol protocol
    ) external view returns (bool allowed);

    /**
     * @notice Quotes the total teleport fee for any protocol
     * @dev Consolidates fee calculation for ITS, LayerZero, and future protocols
     * @param token Token address to transfer
     * @param params The teleport parameters struct (includes protocol)
     * @return totalNativeFee The total fee in native currency (protocol fee + bridge fee)
     * @return basePairFee The fee in base pair tokens (0 if base pair is native)
     * @return basePair The base pair address (address(0) if native)
     * @return bridgeFee The bridge-specific gas fee (ITS gas fee or LZ messaging fee)
     */
    function quoteTeleportFee(
        address token,
        TeleportParams calldata params
    ) external returns (uint256 totalNativeFee, uint256 basePairFee, address basePair, uint256 bridgeFee);

    /**
     * @notice Universal teleport function that routes to ITS or LayerZero
     * @dev Routes tokens to the appropriate cross-chain protocol based on params.protocol
     * @param token The token address to teleport
     * @param params The teleport parameters struct (includes protocol)
     */
    function teleport(
        address token,
        TeleportParams calldata params
    ) external payable;

    /**
     * @notice Universal teleport function with witness signature support
     * @dev Called via permitWitnessCall to enable signature-based teleportation
     * @dev Signer must be the first param to protect from permitWitnessCall attacks
     * @param signer Address initiating the teleport, must be first parameter
     * @param telecoinId The telecoin ID instead of token address
     * @param params The teleport parameters struct (includes protocol)
     */
    function witnessTeleport(
        address signer,
        bytes32 telecoinId,
        TeleportParams calldata params
    ) external payable;

    /**
     * @notice Transmits a LayerZero cross-chain token transfer
     * @dev Called by token contracts to initiate LayerZero transfers
     * @dev telecoinId first param, to protect from permitWitnessCall calls
     * @param telecoinId Universal token ID (32 bytes)
     * @param sender Address initiating the transfer
     * @param params The teleport parameters struct
     */
    function transmitLzSend(
        bytes32 telecoinId,
        address sender,
        TeleportParams calldata params
    ) external payable returns (bytes memory receipt);

    /**
     * @notice Broadcasts an interchain transfer event
     * @dev Emits an InterchainTransfer event with the provided parameters
     * @dev telecoinId first param, to protect from permitWitnessCall calls
     * @param telecoinId The ID of the token being transferred
     * @param sourceAddress The address initiating the transfer
     * @param destChain The target chain for the transfer
     * @param destAddress The address on the destination chain
     * @param amount The amount of tokens being transferred
     */
    function broadcastInterchainTransfer(
        bytes32 telecoinId,
        address sourceAddress,
        string calldata destChain,
        bytes calldata destAddress,
        uint256 amount
    ) external;

    /**
     * @notice Processes an incoming interchain transfer from Solana
     * @dev Processes tokens for the specified sender based on the received message
     * @param telecoinId The ID of the token being transferred
     * @param sender The address receiving the tokens
     * @param amount The amount of tokens to process
     */
    function processInterchainTransfer(
        bytes32 telecoinId,
        address sender,
        uint256 amount
    ) external;

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

    /**
     * @notice Gets the LayerZero endpoint address
     * @return The LayerZero endpoint address
     */
    function lzEndpoint() external view returns (address);

    /**
     * @notice Gets the current gas limit for LayerZero receive operations
     * @return gasLimit Current gas limit
     */
    function lzReceiveGasLimit() external view returns (uint128 gasLimit);

    /**
     * @notice Gets the current native drop amount for LayerZero receive operations
     * @return nativeDrop Current native drop amount
     */
    function lzReceiveNativeDrop() external view returns (uint128 nativeDrop);

    /**
     * @notice Receives teleport messages from authorized channels
     * @dev Called by LZChannel contracts to forward processed messages
     * @param protocol The teleport protocol of the calling channel (LZ_FAST, LZ_SECURE, LZ_SLOW)
     * @param originSender The LayerZero origin sender address (Solana peer or self)
     * @param message The processed teleport payload
     */
    function receiveFromChannel(
        TeleportProtocol protocol,
        bytes32 originSender,
        bytes calldata message
    ) external payable;

}

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

import { IERC20Errors } from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
import { IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

import { IERC20Permit } from "./IERC20Permit.sol";

/// @title IERC20Witness Interface
/// @notice Interface for ERC20 tokens with witness-based permit functionality
/// @dev Extends standard ERC20 with permitWitness functions that include additional validation data
interface IERC20Witness is IERC20Permit, IERC20Metadata, IERC20Errors {

    /*//////////////////////////////////////////////////////////////
                          WITNESS-SPECIFIC ERRORS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Thrown when an invalid witness value is provided
     * @dev The witness data failed validation checks
     */
    error ERC20InvalidWitness();

    /**
     * @notice Thrown when attempting to call the contract itself
     * @dev Self-calls are not permitted for security reasons
     */
    error ERC20InvalidCallTarget();

    /**
     * @notice Thrown when invalid call data is provided
     * @dev The call data format or content is malformed
     */
    error ERC20InvalidCallData();

    /**
     * @notice Thrown when an external call fails during witness operations
     * @dev The target contract call returned failure or reverted
     */
    error ERC20CallFailed();

    struct ContractCall {
        address target;
        string method;
        uint256 nativeValue;
        bytes params;
    }

    /*//////////////////////////////////////////////////////////////
                      PERMIT WITNESS LOGIC
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Returns the permit witness typehash for EIP-712 signature encoding
     * @dev Used for permitWitness function signature validation
     * @return The keccak256 hash of the permit witness type string
     */
    function PERMIT_WITNESS_TYPEHASH() external view returns (bytes32);

    /**
     * @notice Returns the base permit witness type string for EIP-712 structured data
     * @dev Used as a prefix to construct full type strings with witness data
     * @return The base permit witness type string stub
     */
    function PERMIT_WITNESS_TYPESTRING_STAB() external view returns (string memory);

    /**
     * @notice Returns the contract call witness type string completion
     * @dev Appended to PERMIT_WITNESS_TYPESTRING_STAB to form complete EIP-712 type string
     * @return The contract call type string completion
     */
    function WITNESS_CALL_TYPESTRING() external view returns (string memory);

    /**
     * @notice Returns the contract call typehash for EIP-712 signature encoding
     * @dev Used for ContractCall struct hashing in witness validation
     * @return The keccak256 hash of the ContractCall type string
     */
    function WITNESS_CALL_TYPEHASH() external view returns (bytes32);

    /**
     * @notice Returns the witness type identifier for transfer operations
     * @dev Used to validate witness data in transfer contexts
     * @return The bytes32 identifier for transfer witness operations
     */
    function TRANSFER_WITNESS() external view returns (bytes32);

    /**
     * @notice Sets allowance using signature with additional witness validation
     * @dev Extends standard permit functionality with witness data for enhanced security
     * @param owner The owner of the tokens
     * @param spender The address authorized to spend the tokens
     * @param value The amount of tokens to authorize for spending
     * @param deadline The timestamp at which the permit expires
     * @param witness Additional validation data to prevent certain attack vectors
     * @param v The recovery parameter of the signature (27 or 28)
     * @param r The first 32 bytes of the signature
     * @param s The second 32 bytes of the signature
     */
    function permitWitness(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        bytes32 witness,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @notice Sets allowance using signature with custom witness type string for free-form witness data
     * @dev Extends permitWitness with witnessTypeString parameter to support various witness structures
     * @param owner The owner of the tokens
     * @param spender The address authorized to spend the tokens
     * @param value The amount of tokens to authorize for spending
     * @param deadline The timestamp at which the permit expires
     * @param witness Additional validation data hash to prevent certain attack vectors
     * @param witnessTypeString The EIP-712 type string for the witness data structure
     * @param v The recovery parameter of the signature (27 or 28)
     * @param r The first 32 bytes of the signature
     * @param s The second 32 bytes of the signature
     */
    function permitWitness(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        bytes32 witness,
        string calldata witnessTypeString,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @notice Permits and executes a direct transfer using signature authorization
     * @dev Combines permit and transferFrom into a single operation with witness validation
     * @param owner The owner of the tokens to transfer
     * @param spender The recipient address for the transfer
     * @param value The amount of tokens to transfer
     * @param deadline The timestamp at which the permit expires
     * @param v The recovery parameter of the signature (27 or 28)
     * @param r The first 32 bytes of the signature
     * @param s The second 32 bytes of the signature
     * @return success Whether the transfer operation was successful
     */
    function permitTransferFrom(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external returns (bool success);

    /**
     * @notice Permits token allowance and executes an external call in one transaction
     * @dev Combines permit functionality with external contract interaction using witness validation
     * @param owner The owner of the tokens
     * @param spender The address authorized to spend the tokens
     * @param value The amount of tokens to authorize for spending
     * @param deadline The timestamp at which the permit expires
     * @param call ContractCall struct containing target address, method signature, and parameters
     * @param v The recovery parameter of the signature (27 or 28)
     * @param r The first 32 bytes of the signature
     * @param s The second 32 bytes of the signature
     * @return returnData The return data from the executed external call
     */
    function permitWitnessCall(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        ContractCall calldata call,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external payable returns (bytes memory returnData);

}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)

pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";

/**
 * @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 ContextUpgradeable is Initializable {
    function __Context_init() internal onlyInitializing {
    }

    function __Context_init_unchained() internal onlyInitializing {
    }
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

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

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.20;

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
 * reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
 * case an upgrade adds a module that needs to be initialized.
 *
 * For example:
 *
 * [.hljs-theme-light.nopadding]
 * ```solidity
 * contract MyToken is ERC20Upgradeable {
 *     function initialize() initializer public {
 *         __ERC20_init("MyToken", "MTK");
 *     }
 * }
 *
 * contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
 *     function initializeV2() reinitializer(2) public {
 *         __ERC20Permit_init("MyToken");
 *     }
 * }
 * ```
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
 * the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() {
 *     _disableInitializers();
 * }
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Storage of the initializable contract.
     *
     * It's implemented on a custom ERC-7201 namespace to reduce the risk of storage collisions
     * when using with upgradeable contracts.
     *
     * @custom:storage-location erc7201:openzeppelin.storage.Initializable
     */
    struct InitializableStorage {
        /**
         * @dev Indicates that the contract has been initialized.
         */
        uint64 _initialized;
        /**
         * @dev Indicates that the contract is in the process of being initialized.
         */
        bool _initializing;
    }

    // keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Initializable")) - 1)) & ~bytes32(uint256(0xff))
    bytes32 private constant INITIALIZABLE_STORAGE = 0xf0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00;

    /**
     * @dev The contract is already initialized.
     */
    error InvalidInitialization();

    /**
     * @dev The contract is not initializing.
     */
    error NotInitializing();

    /**
     * @dev Triggered when the contract has been initialized or reinitialized.
     */
    event Initialized(uint64 version);

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts.
     *
     * Similar to `reinitializer(1)`, except that in the context of a constructor an `initializer` may be invoked any
     * number of times. This behavior in the constructor can be useful during testing and is not expected to be used in
     * production.
     *
     * Emits an {Initialized} event.
     */
    modifier initializer() {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        // Cache values to avoid duplicated sloads
        bool isTopLevelCall = !$._initializing;
        uint64 initialized = $._initialized;

        // Allowed calls:
        // - initialSetup: the contract is not in the initializing state and no previous version was
        //                 initialized
        // - construction: the contract is initialized at version 1 (no reininitialization) and the
        //                 current contract is just being deployed
        bool initialSetup = initialized == 0 && isTopLevelCall;
        bool construction = initialized == 1 && address(this).code.length == 0;

        if (!initialSetup && !construction) {
            revert InvalidInitialization();
        }
        $._initialized = 1;
        if (isTopLevelCall) {
            $._initializing = true;
        }
        _;
        if (isTopLevelCall) {
            $._initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * A reinitializer may be used after the original initialization step. This is essential to configure modules that
     * are added through upgrades and that require initialization.
     *
     * When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
     * cannot be nested. If one is invoked in the context of another, execution will revert.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     *
     * WARNING: Setting the version to 2**64 - 1 will prevent any future reinitialization.
     *
     * Emits an {Initialized} event.
     */
    modifier reinitializer(uint64 version) {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing || $._initialized >= version) {
            revert InvalidInitialization();
        }
        $._initialized = version;
        $._initializing = true;
        _;
        $._initializing = false;
        emit Initialized(version);
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} and {reinitializer} modifiers, directly or indirectly.
     */
    modifier onlyInitializing() {
        _checkInitializing();
        _;
    }

    /**
     * @dev Reverts if the contract is not in an initializing state. See {onlyInitializing}.
     */
    function _checkInitializing() internal view virtual {
        if (!_isInitializing()) {
            revert NotInitializing();
        }
    }

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     *
     * Emits an {Initialized} event the first time it is successfully executed.
     */
    function _disableInitializers() internal virtual {
        // solhint-disable-next-line var-name-mixedcase
        InitializableStorage storage $ = _getInitializableStorage();

        if ($._initializing) {
            revert InvalidInitialization();
        }
        if ($._initialized != type(uint64).max) {
            $._initialized = type(uint64).max;
            emit Initialized(type(uint64).max);
        }
    }

    /**
     * @dev Returns the highest version that has been initialized. See {reinitializer}.
     */
    function _getInitializedVersion() internal view returns (uint64) {
        return _getInitializableStorage()._initialized;
    }

    /**
     * @dev Returns `true` if the contract is currently initializing. See {onlyInitializing}.
     */
    function _isInitializing() internal view returns (bool) {
        return _getInitializableStorage()._initializing;
    }

    /**
     * @dev Returns a pointer to the storage namespace.
     */
    // solhint-disable-next-line var-name-mixedcase
    function _getInitializableStorage() private pure returns (InitializableStorage storage $) {
        assembly {
            $.slot := INITIALIZABLE_STORAGE
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)

pragma solidity ^0.8.20;

/**
 * @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.
 *
 * ==== Security Considerations
 *
 * There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
 * expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
 * considered as an intention to spend the allowance in any specific way. The second is that because permits have
 * built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
 * take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
 * generally recommended is:
 *
 * ```solidity
 * function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
 *     try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
 *     doThing(..., value);
 * }
 *
 * function doThing(..., uint256 value) public {
 *     token.safeTransferFrom(msg.sender, address(this), value);
 *     ...
 * }
 * ```
 *
 * Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
 * `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
 * {SafeERC20-safeTransferFrom}).
 *
 * Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
 * contracts should have entry points that don't rely on permit.
 */
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].
     *
     * CAUTION: See Security Considerations above.
     */
    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);
}

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

pragma solidity ^0.8.20;

/**
 * @dev Collection of functions related to the address type
 */
library Address {
    /**
     * @dev The ETH balance of the account is not enough to perform the operation.
     */
    error AddressInsufficientBalance(address account);

    /**
     * @dev There's no code at `target` (it is not a contract).
     */
    error AddressEmptyCode(address target);

    /**
     * @dev A call to an address target failed. The target may have reverted.
     */
    error FailedInnerCall();

    /**
     * @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.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
     */
    function sendValue(address payable recipient, uint256 amount) internal {
        if (address(this).balance < amount) {
            revert AddressInsufficientBalance(address(this));
        }

        (bool success, ) = recipient.call{value: amount}("");
        if (!success) {
            revert FailedInnerCall();
        }
    }

    /**
     * @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 or custom error, it is bubbled
     * up by this function (like regular Solidity function calls). However, if
     * the call reverted with no returned reason, this function reverts with a
     * {FailedInnerCall} error.
     *
     * 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.
     */
    function functionCall(address target, bytes memory data) internal returns (bytes memory) {
        return functionCallWithValue(target, data, 0);
    }

    /**
     * @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`.
     */
    function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
        if (address(this).balance < value) {
            revert AddressInsufficientBalance(address(this));
        }
        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResultFromTarget(target, success, returndata);
    }

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

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

    /**
     * @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
     * was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
     * unsuccessful call.
     */
    function verifyCallResultFromTarget(
        address target,
        bool success,
        bytes memory returndata
    ) internal view returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            // only check if target is a contract if the call was successful and the return data is empty
            // otherwise we already know that it was a contract
            if (returndata.length == 0 && target.code.length == 0) {
                revert AddressEmptyCode(target);
            }
            return returndata;
        }
    }

    /**
     * @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
     * revert reason or with a default {FailedInnerCall} error.
     */
    function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
        if (!success) {
            _revert(returndata);
        } else {
            return returndata;
        }
    }

    /**
     * @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
     */
    function _revert(bytes memory returndata) 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 FailedInnerCall();
        }
    }
}

// SPDX-License-Identifier: MIT
// https://github.com/axelarnetwork/interchain-token-service/blob/main/contracts/interfaces/IInterchainTokenService.sol

pragma solidity ^0.8.0;

type ITokenManager is address;

/**
 * @title IInterchainGasEstimation Interface
 * @notice This is an interface for the InterchainGasEstimation contract
 * which allows for estimating gas fees for cross-chain communication on the Axelar network.
 */
interface IInterchainGasEstimation {

    /**
     * @notice Estimates the gas fee for a cross-chain contract call.
     * @param destinationChain Axelar registered name of the destination chain
     * @param destinationAddress Destination contract address being called
     * @param executionGasLimit The gas limit to be used for the destination contract execution,
     *        e.g. pass in 200k if your app consumes needs upto 200k for this contract call
     * @param params Additional parameters for the gas estimation
     * @return gasEstimate The cross-chain gas estimate, in terms of source chain's native gas token that should be
     * forwarded to the gas service.
     */
    function estimateGasFee(
        string calldata destinationChain,
        string calldata destinationAddress,
        bytes calldata payload,
        uint256 executionGasLimit,
        bytes calldata params
    ) external view returns (uint256 gasEstimate);

}

/**
 * @title IInterchainTokenService Interface
 * @notice Interface for the Interchain Token Service
 */
interface IInterchainTokenService {

    enum TokenManagerType {
        NATIVE_INTERCHAIN_TOKEN, // This type is reserved for interchain tokens deployed by ITS, and can't be used by
        // custom token managers.
        MINT_BURN_FROM, // The token will be minted/burned on transfers. The token needs to give mint permission to the
        // token manager, but burning happens via an approval.
        LOCK_UNLOCK, // The token will be locked/unlocked at the token manager.
        LOCK_UNLOCK_FEE, // The token will be locked/unlocked at the token manager, which will account for any
        // fee-on-transfer behaviour.
        MINT_BURN // The token will be minted/burned on transfers. The token needs to give mint and burn permission to
        // the token manager.
    }

    event InterchainTransfer(
        bytes32 indexed tokenId,
        address indexed sourceAddress,
        string destinationChain,
        bytes destinationAddress,
        uint256 amount,
        bytes32 indexed dataHash
    );
    event InterchainTransferReceived(
        bytes32 indexed commandId,
        bytes32 indexed tokenId,
        string sourceChain,
        bytes sourceAddress,
        address indexed destinationAddress,
        uint256 amount,
        bytes32 dataHash
    );
    event TokenMetadataRegistered(address indexed tokenAddress, uint8 decimals);
    event LinkTokenStarted(
        bytes32 indexed tokenId,
        string destinationChain,
        bytes sourceTokenAddress,
        bytes destinationTokenAddress,
        TokenManagerType indexed tokenManagerType,
        bytes params
    );
    event InterchainTokenDeploymentStarted(
        bytes32 indexed tokenId,
        string tokenName,
        string tokenSymbol,
        uint8 tokenDecimals,
        bytes minter,
        string destinationChain
    );
    event TokenManagerDeployed(
        bytes32 indexed tokenId, address tokenManager, TokenManagerType indexed tokenManagerType, bytes params
    );
    event InterchainTokenDeployed(
        bytes32 indexed tokenId,
        address tokenAddress,
        address indexed minter,
        string name,
        string symbol,
        uint8 decimals
    );
    event InterchainTokenIdClaimed(bytes32 indexed tokenId, address indexed deployer, bytes32 indexed salt);

    /**
     * @notice Returns the address of the interchain gas service contract.
     * @return gasService The instance of the IInterchainGasEstimation contract.
     */
    function gasService() external view returns (IInterchainGasEstimation);

    /**
     * @notice Returns the address of the ITS Hub contract.
     * @return hubAddress The address of the ITS Hub contract.
     */
    function itsHubAddress() external view returns (string memory hubAddress);

    /**
     * @notice Returns the address of the token manager deployer contract.
     * @return tokenManagerDeployerAddress The address of the token manager deployer contract.
     */
    function tokenManagerDeployer() external view returns (address tokenManagerDeployerAddress);

    /**
     * @notice Returns the address of the interchain token deployer contract.
     * @return interchainTokenDeployerAddress The address of the interchain token deployer contract.
     */
    function interchainTokenDeployer() external view returns (address interchainTokenDeployerAddress);

    /**
     * @notice Returns the address of TokenManager implementation.
     * @return tokenManagerAddress_ The address of the token manager contract.
     */
    function tokenManager() external view returns (address tokenManagerAddress_);

    /**
     * @notice Returns the address of TokenHandler implementation.
     * @return tokenHandlerAddress The address of the token handler contract.
     */
    function tokenHandler() external view returns (address tokenHandlerAddress);

    /**
     * @notice Returns the address of the interchain token factory.
     * @return address The address of the interchain token factory.
     */
    function interchainTokenFactory() external view returns (address);

    /**
     * @notice Returns the hash of the chain name.
     * @return bytes32 The hash of the chain name.
     */
    function chainNameHash() external view returns (bytes32);

    /**
     * @notice Returns the address of the token manager associated with the given tokenId.
     * @param tokenId The tokenId of the token manager.
     * @return tokenManagerAddress_ The address of the token manager.
     */
    function tokenManagerAddress(
        bytes32 tokenId
    ) external view returns (address tokenManagerAddress_);

    /**
     * @notice Returns the instance of ITokenManager from a specific tokenId.
     * @param tokenId The tokenId of the deployed token manager.
     * @return tokenManager_ The instance of ITokenManager associated with the specified tokenId.
     */
    function deployedTokenManager(
        bytes32 tokenId
    ) external view returns (ITokenManager tokenManager_);

    /**
     * @notice Returns the address of the token that an existing tokenManager points to.
     * @param tokenId The tokenId of the registered token.
     * @return tokenAddress The address of the token.
     */
    function registeredTokenAddress(
        bytes32 tokenId
    ) external view returns (address tokenAddress);

    /**
     * @notice Returns the address of the interchain token associated with the given tokenId.
     * @param tokenId The tokenId of the interchain token.
     * @return tokenAddress The address of the interchain token.
     */
    function interchainTokenAddress(
        bytes32 tokenId
    ) external view returns (address tokenAddress);

    /**
     * @notice Returns the custom tokenId associated with the given operator and salt.
     * @param operator_ The operator address.
     * @param salt The salt used for token id calculation.
     * @return tokenId The custom tokenId associated with the operator and salt.
     */
    function interchainTokenId(
        address operator_,
        bytes32 salt
    ) external view returns (bytes32 tokenId);

    /**
     * @notice Registers metadata for a token on the ITS Hub. This metadata is used for scaling linked tokens.
     * The token metadata must be registered before linkToken can be called for the corresponding token.
     * @param tokenAddress The address of the token.
     * @param gasValue The cross-chain gas value for sending the registration message to ITS Hub.
     */
    function registerTokenMetadata(
        address tokenAddress,
        uint256 gasValue
    ) external payable;

    /**
     * @notice Only to be used by the InterchainTokenFactory to register custom tokens to this chain. Then link token
     * can be used to register those tokens to other chains.
     * @param salt A unique salt to derive tokenId from.
     * @param tokenManagerType The type of the token manager to use for the token registration.
     * @param linkParams The operator for the token.
     */
    function registerCustomToken(
        bytes32 salt,
        address tokenAddress,
        TokenManagerType tokenManagerType,
        bytes calldata linkParams
    ) external payable returns (bytes32 tokenId);

    /**
     * @notice If `destinationChain` is an empty string, this function will register the token address on the current
     * chain.
     * Otherwise, it will link the token address on the destination chain with the token corresponding to the tokenId on
     * the current chain.
     * A token manager is deployed on EVM chains that's responsible for managing the linked token.
     * @dev This function replaces the prior `deployTokenManager` function.
     * @param salt A unique identifier to allow for multiple tokens registered per deployer.
     * @param destinationChain The chain to link the token to. Pass an empty string for this chain.
     * @param destinationTokenAddress The token address to link, as bytes.
     * @param tokenManagerType The type of the token manager to use to send and receive tokens.
     * @param linkParams Additional parameteres to use to link the token. Fow not it is just the address of the
     * operator.
     * @param gasValue Pass a non-zero value only for remote linking, which should be the gas to use to pay for the
     * contract call.
     * @return tokenId The tokenId associated with the token manager.
     */
    function linkToken(
        bytes32 salt,
        string calldata destinationChain,
        bytes memory destinationTokenAddress,
        TokenManagerType tokenManagerType,
        bytes memory linkParams,
        uint256 gasValue
    ) external payable returns (bytes32 tokenId);

    /**
     * @notice Deploys and registers an interchain token on a remote chain.
     * @param salt The salt used for token deployment.
     * @param destinationChain The name of the destination chain. Use '' for this chain.
     * @param name The name of the interchain tokens.
     * @param symbol The symbol of the interchain tokens.
     * @param decimals The number of decimals for the interchain tokens.
     * @param minter The minter data for mint/burn operations.
     * @param gasValue The gas value for deployment.
     * @return tokenId The tokenId corresponding to the deployed InterchainToken.
     */
    function deployInterchainToken(
        bytes32 salt,
        string calldata destinationChain,
        string memory name,
        string memory symbol,
        uint8 decimals,
        bytes memory minter,
        uint256 gasValue
    ) external payable returns (bytes32 tokenId);

    /**
     * @notice Initiates an interchain transfer of a specified token to a destination chain.
     * @param tokenId The unique identifier of the token to be transferred.
     * @param destinationChain The destination chain to send the tokens to.
     * @param destinationAddress The address on the destination chain to send the tokens to.
     * @param amount The amount of tokens to be transferred.
     * @param metadata Optional metadata for the call for additional effects (such as calling a destination contract).
     */
    function interchainTransfer(
        bytes32 tokenId,
        string calldata destinationChain,
        bytes calldata destinationAddress,
        uint256 amount,
        bytes calldata metadata,
        uint256 gasValue
    ) external payable;

    /**
     * @notice Sets the flow limits for multiple tokens.
     * @param tokenIds An array of tokenIds.
     * @param flowLimits An array of flow limits corresponding to the tokenIds.
     */
    function setFlowLimits(
        bytes32[] calldata tokenIds,
        uint256[] calldata flowLimits
    ) external;

    /**
     * @notice Allows the owner to pause/unpause the token service.
     * @param paused whether to pause or unpause.
     */
    function setPauseStatus(
        bool paused
    ) external;

    /**
     * @notice Allows the owner to migrate legacy tokens that cannot be migrated automatically.
     * @param tokenId the tokenId of the registered token.
     */
    function migrateInterchainToken(
        bytes32 tokenId
    ) external;

    /**
     * @notice Transmit an interchain transfer for the given tokenId.
     * @dev Only callable by a token registered under a tokenId.
     * @param tokenId The tokenId of the token (which must be the msg.sender).
     * @param sourceAddress The address where the token is coming from.
     * @param destinationChain The name of the chain to send tokens to.
     * @param destinationAddress The destinationAddress for the interchainTransfer.
     * @param amount The amount of token to give.
     * @param metadata Optional metadata for the call for additional effects (such as calling a destination contract).
     */
    function transmitInterchainTransfer(
        bytes32 tokenId,
        address sourceAddress,
        string calldata destinationChain,
        bytes memory destinationAddress,
        uint256 amount,
        bytes calldata metadata
    ) external payable;

}

File 38 of 47 : IModule.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import { IPrintrFeeDistribution } from "./printr/IPrintrFeeDistribution.sol";
import { IPrintrGetters } from "./printr/IPrintrGetters.sol";
import { IPrintrOwner } from "./printr/IPrintrOwner.sol";
import { IPrintrStorage } from "./printr/IPrintrStorage.sol";

/**
 * @title Module Interface
 * @notice Interface for getter, fee distribution, and owner functionality
 * @dev Combines IPrintrGetters, IPrintrFeeDistribution, and IPrintrOwner for the Module implementation contract
 */
interface IModule is IPrintrStorage, IPrintrGetters, IPrintrFeeDistribution, IPrintrOwner { }

File 39 of 47 : ITeleport.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.27;

import { IPrintrInterchain } from "./printr/IPrintrInterchain.sol";
import { IPrintrStorage } from "./printr/IPrintrStorage.sol";
import { IPrintrTeleport } from "./printr/IPrintrTeleport.sol";

/**
 * @title Teleport Interface
 * @notice Interface for cross-chain operations
 * @dev Aggregates specialized interfaces for interchain and teleport functionality
 *
 * Components:
 * - IPrintrStorage: Core storage and state management
 * - IPrintrInterchain: Cross-chain token deployment and linking
 * - IPrintrTeleport: LayerZero teleport functionality for cross-chain operations
 *
 * Note: Owner functions, trading, and fee distribution are handled by Module via fallback delegation
 */
interface ITeleport is IPrintrStorage, IPrintrInterchain, IPrintrTeleport { }

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)

pragma solidity ^0.8.20;

/**
 * @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);
}

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

/**
 * @title ILayerZeroEndpointV2
 * @notice Interface for LayerZero V2 endpoint contract
 * @dev Defines the core messaging functions for cross-chain communication
 */
interface ILayerZeroEndpointV2 {

    /**
     * @notice Struct containing messaging fee information
     * @param nativeFee Native fee amount to send
     * @param lzTokenFee LayerZero token fee amount
     */
    struct MessagingFee {
        uint256 nativeFee;
        uint256 lzTokenFee;
    }

    /**
     * @notice Struct containing messaging parameters
     * @param dstEid Destination endpoint ID
     * @param receiver Receiver address on destination chain (as bytes32)
     * @param message Message payload to send
     * @param options Execution options for the message
     * @param payInLzToken Whether to pay fees in LZ token
     */
    struct MessagingParams {
        uint32 dstEid;
        bytes32 receiver;
        bytes message;
        bytes options;
        bool payInLzToken;
    }

    /**
     * @notice Struct containing messaging receipt information
     * @param guid Globally unique identifier for the message
     * @param nonce Message nonce
     * @param fee Messaging fee paid
     */
    struct MessagingReceipt {
        bytes32 guid;
        uint64 nonce;
        MessagingFee fee;
    }

    /**
     * @notice Send a message to another chain
     * @param params Messaging parameters
     * @param refundAddress Address to refund excess fees
     * @return receipt Message receipt containing guid and fee info
     */
    function send(
        MessagingParams calldata params,
        address refundAddress
    ) external payable returns (MessagingReceipt memory receipt);

    /**
     * @notice Quote the fee for sending a message
     * @param params Messaging parameters
     * @param sender The sender address
     * @return fee The messaging fee quote
     */
    function quote(
        MessagingParams calldata params,
        address sender
    ) external view returns (MessagingFee memory fee);

    /**
     * @notice Struct for setting configuration parameters
     * @param eid Endpoint ID
     * @param configType Configuration type (1=Executor, 2=ULN)
     * @param config Encoded configuration data
     */
    struct SetConfigParam {
        uint32 eid;
        uint32 configType;
        bytes config;
    }

    /**
     * @notice Set configuration for an OApp
     * @param oapp The OApp address to configure
     * @param lib The library address (SendLib or ReceiveLib)
     * @param params Array of configuration parameters
     */
    function setConfig(
        address oapp,
        address lib,
        SetConfigParam[] calldata params
    ) external;

    /**
     * @notice Set send library for an OApp
     * @param _oapp The OApp address
     * @param _eid Destination endpoint ID
     * @param _newLib Send library address
     */
    function setSendLibrary(
        address _oapp,
        uint32 _eid,
        address _newLib
    ) external;

    /**
     * @notice Set receive library for an OApp
     * @param _oapp The OApp address
     * @param _eid Source endpoint ID
     * @param _newLib Receive library address
     * @param _gracePeriod Grace period for library switch
     */
    function setReceiveLibrary(
        address _oapp,
        uint32 _eid,
        address _newLib,
        uint256 _gracePeriod
    ) external;

    /**
     * @notice Set delegate for message handling
     * @param delegate Address of the delegate
     */
    function setDelegate(
        address delegate
    ) external;

    /**
     * @notice Get the send library for an OApp and destination
     * @param _sender The sender address
     * @param _eid Destination endpoint ID
     * @return lib The send library address
     */
    function getSendLibrary(
        address _sender,
        uint32 _eid
    ) external view returns (address lib);

    /**
     * @notice Get the receive library for an OApp and source
     * @param _receiver The receiver address
     * @param _eid Source endpoint ID
     * @return lib The receive library address
     */
    function getReceiveLibrary(
        address _receiver,
        uint32 _eid
    ) external view returns (address lib, bool);

    /**
     * @notice Get the default send library for an endpoint
     * @param _eid Destination endpoint ID
     * @return The default send library address
     */
    function defaultSendLibrary(
        uint32 _eid
    ) external view returns (address);

    /**
     * @notice Get the default receive library for an endpoint
     * @param _eid Source endpoint ID
     * @return The default receive library address
     */
    function defaultReceiveLibrary(
        uint32 _eid
    ) external view returns (address);

}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;

/**
 * @dev Standard ERC20 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
 */
interface IERC20Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC20InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC20InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     * @param allowance Amount of tokens a `spender` is allowed to operate with.
     * @param needed Minimum amount required to perform a transfer.
     */
    error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC20InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `spender` to be approved. Used in approvals.
     * @param spender Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC20InvalidSpender(address spender);
}

/**
 * @dev Standard ERC721 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
 */
interface IERC721Errors {
    /**
     * @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
     * Used in balance queries.
     * @param owner Address of the current owner of a token.
     */
    error ERC721InvalidOwner(address owner);

    /**
     * @dev Indicates a `tokenId` whose `owner` is the zero address.
     * @param tokenId Identifier number of a token.
     */
    error ERC721NonexistentToken(uint256 tokenId);

    /**
     * @dev Indicates an error related to the ownership over a particular token. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param tokenId Identifier number of a token.
     * @param owner Address of the current owner of a token.
     */
    error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC721InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC721InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param tokenId Identifier number of a token.
     */
    error ERC721InsufficientApproval(address operator, uint256 tokenId);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC721InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC721InvalidOperator(address operator);
}

/**
 * @dev Standard ERC1155 Errors
 * Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
 */
interface IERC1155Errors {
    /**
     * @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     * @param balance Current balance for the interacting account.
     * @param needed Minimum amount required to perform a transfer.
     * @param tokenId Identifier number of a token.
     */
    error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);

    /**
     * @dev Indicates a failure with the token `sender`. Used in transfers.
     * @param sender Address whose tokens are being transferred.
     */
    error ERC1155InvalidSender(address sender);

    /**
     * @dev Indicates a failure with the token `receiver`. Used in transfers.
     * @param receiver Address to which tokens are being transferred.
     */
    error ERC1155InvalidReceiver(address receiver);

    /**
     * @dev Indicates a failure with the `operator`’s approval. Used in transfers.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     * @param owner Address of the current owner of a token.
     */
    error ERC1155MissingApprovalForAll(address operator, address owner);

    /**
     * @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
     * @param approver Address initiating an approval operation.
     */
    error ERC1155InvalidApprover(address approver);

    /**
     * @dev Indicates a failure with the `operator` to be approved. Used in approvals.
     * @param operator Address that may be allowed to operate on tokens without being their owner.
     */
    error ERC1155InvalidOperator(address operator);

    /**
     * @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
     * Used in batch transfers.
     * @param idsLength Length of the array of token identifiers
     * @param valuesLength Length of the array of token amounts
     */
    error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}

// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;

/// @title IERC20Permit Interface with Errors
/// @notice Interface for ERC20 Permit extension with proper error definitions
/// @dev Extends the standard EIP-2612 permit functionality with error definitions
interface IERC20Permit {

    /*//////////////////////////////////////////////////////////////
                                ERRORS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Thrown when a permit signature has expired
     * @param deadline The timestamp at which the signature expired
     */
    error ERC2612ExpiredSignature(uint256 deadline);

    /**
     * @notice Thrown when the recovered signer does not match the expected owner
     * @param signer The address recovered from the signature
     * @param owner The expected owner address
     */
    error ERC2612InvalidSigner(address signer, address owner);

    /*//////////////////////////////////////////////////////////////
                                EVENTS
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Emitted when an owner manually invalidates their nonce
     * @param owner The address that invalidated their nonce
     * @param newNonce The new nonce value after invalidation
     */
    event NonceInvalidated(address indexed owner, uint256 newNonce);

    /*//////////////////////////////////////////////////////////////
                          PERMIT LOGIC
    //////////////////////////////////////////////////////////////*/

    /**
     * @notice Returns the current nonce for the given owner
     * @dev This value must be included whenever a signature is generated for permit
     * @param owner The address to get the nonce for
     * @return The current nonce value for the owner
     */
    function nonces(
        address owner
    ) external view returns (uint256);

    /**
     * @notice Returns the domain separator used in the encoding of signatures
     * @dev Used to prevent signature replay attacks across different domains
     * @return The EIP-712 domain separator hash
     */
    function DOMAIN_SEPARATOR() external view returns (bytes32);

    /**
     * @notice Returns the permit typehash used in EIP-712 signature encoding
     * @dev Constant value defining the structure of permit messages
     * @return The keccak256 hash of the permit type string
     */
    function PERMIT_TYPEHASH() external view returns (bytes32);

    /**
     * @notice Sets allowance using EIP-2612 signature-based authorization
     * @dev Allows setting allowance without requiring a separate transaction from the token owner
     * @param owner The owner of the tokens
     * @param spender The address authorized to spend the tokens
     * @param value The amount of tokens to authorize for spending
     * @param deadline The timestamp at which the permit expires
     * @param v The recovery parameter of the signature (27 or 28)
     * @param r The first 32 bytes of the signature
     * @param s The second 32 bytes of the signature
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @notice Invalidates the current nonce for msg.sender
     * @dev Increments the nonce, making all outstanding permit signatures invalid
     * This provides users with an emergency mechanism to cancel pending permits in case of:
     * - Key compromise or suspected phishing
     * - Changed mind about a signed but not executed permit
     * - Need to invalidate multiple permits at once
     * Emits a NonceInvalidated event
     */
    function invalidateNonce() external;

}

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

import { IPrintrStorage } from "./IPrintrStorage.sol";

/**
 * @title IPrintrFeeDistribution
 * @notice Interface for fee distribution functionality in the Printr protocol
 */
interface IPrintrFeeDistribution is IPrintrStorage {

    /**
     * @notice Structure containing fee distribution information
     * @param totalAmount Total amount being distributed
     * @param growthAmount Amount allocated to growth fund
     * @param buybackAmount Amount allocated to buyback fund
     * @param teamAmount Amount allocated to team treasury
     * @param creatorAmount Amount allocated to creator NFT holder
     * @param creatorAddress Address of the creator NFT holder (or zero if none)
     */
    struct FeeDistribution {
        uint256 totalAmount;
        uint256 growthAmount;
        uint256 buybackAmount;
        uint256 teamAmount;
        uint256 creatorAmount;
        address creatorAddress;
    }

    /**
     * @notice Emitted when base pair fees are distributed
     * @param token The PRINTR token address for which fees were collected
     * @param feeToken The base pair token being distributed
     * @param totalAmount Total amount of fees distributed
     * @param isLiquidityFee Whether these are LP fees (true) or protocol fees (false)
     */
    event FeesDistributed(address indexed token, address indexed feeToken, uint256 totalAmount, bool isLiquidityFee);

    /**
     * @notice Emitted when token LP fees are distributed to staking wallet
     * @param token The token address
     * @param amount Amount sent to staking wallet
     */
    event TokenFeesDistributed(address indexed token, uint256 amount);

    /**
     * @notice Returns accumulated protocol fees and their distribution
     * @param token The PRINTR token address to query
     * @return distribution The fee distribution breakdown for the accumulated fees
     */
    function getAccumulatedProtocolFees(
        address token
    ) external returns (FeeDistribution memory distribution);

    /**
     * @notice Collects and distributes accumulated protocol fees for a token
     * @dev Permissionless function that distributes fees to platform wallets and Creator NFT holder
     * @param token Address of the PRINTR token to collect fees from
     */
    function collectProtocolFees(
        address token
    ) external returns (FeeDistribution memory distribution);

    /**
     * @notice Collects and distributes accumulated liquidity fees for a token
     * @dev Permissionless function that distributes LP fees to platform wallets and Creator NFT holder
     * @param token Address of the PRINTR token to collect fees from
     */
    function collectLiquidityFees(
        address token
    ) external returns (FeeDistribution memory basePairDistribution, uint256 tokenStakingAmount);

}

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

import { IPrintrStorage } from "./IPrintrStorage.sol";

/**
 * @title Printr Getters Interface
 * @notice Interface for the PrintrGetters contract
 * @dev All price calculations use PRECISION_SCALAR (1e18) for accurate floating point math
 *      Implements cross-chain token deployment through ITelecoinFactory integration
 *      This interface defines the core storage and view functions for the Printr system
 */
interface IPrintrGetters is IPrintrStorage {

    /**
     * @notice Retrieves the address of a token based on its universal ID
     * @param telecoinId The universal token ID (deploySalt)
     * @return Address of the token associated with the given ID
     */
    function getTokenAddress(
        bytes32 telecoinId
    ) external view returns (address);

    /**
     * @notice Calculates the deterministic address for a token deployment
     * @dev Uses creator address and deployment parameters to compute the deployment address
     * @param tokenParams Parameters for the token deployment
     * @return tokenAddress The calculated token deployment address
     */
    function getTokenAddress(
        TelecoinParams calldata tokenParams
    ) external view returns (address tokenAddress);

    /**
     * @notice Retrieves information about a specific token
     * @param token Address of the token
     * @return CurveInfo struct containing the token's configuration and state
     */
    function getCurve(
        address token
    ) external view returns (CurveInfo memory);

    /**
     * @notice Gets the lock status for a token
     * @param token Address of the token to check
     * @return Array containing lock information
     */
    function getLiquidityLocks(
        address token
    ) external view returns (uint256[2] memory);

}

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

import { IPrintrStorage } from "./IPrintrStorage.sol";

/**
 * @title Printr Owner Interface
 * @notice Interface for administrative functions related to fee collection
 * @dev Extends IPrintrStorage to provide fee management capabilities
 */
interface IPrintrOwner is IPrintrStorage {

    /**
     * @notice Pauses the contract, preventing all trading and liquidity operations
     */
    function pause() external;

    /**
     * @notice Unpauses the contract, allowing trading and liquidity operations to resume
     */
    function unpause() external;

}

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

import { IPrintrStorage } from "./IPrintrStorage.sol";

/**
 * @title Printr Interchain Interface
 * @notice Interface for managing cross-chain token registration and linking
 * @dev Extends IPrintrStorage to handle interchain token functionality
 */
interface IPrintrInterchain is IPrintrStorage {

    error TokenIdMismatch();

    /**
     * @notice Calculates the interchain token ID for a token deployment
     * @dev Uses creator address and deployment parameters to generate a deterministic ID
     * @param tokenParams Parameters for the token deployment
     * @return interchainTokenId The calculated interchain token ID
     */
    function getInterchainTokenId(
        TelecoinParams calldata tokenParams
    ) external view returns (bytes32 interchainTokenId);

    /**
     * @notice Registers a token across multiple chains through the Interchain Token Service
     * @dev Deploys token and registers with ITS on initial chain
     * @param tokenParams Parameters for cross-chain token deployment
     */
    function registerInterchainToken(
        TelecoinParams calldata tokenParams
    ) external payable;

    /**
     * @notice Links an EVM token to its interchain counterpart on another chain
     * @dev Establishes cross-chain connection for an existing token
     * @param tokenParams Parameters of the token to link
     * @param destinationChain Name of the chain to link with
     */
    function linkEvmInterchainToken(
        TelecoinParams calldata tokenParams,
        string calldata destinationChain
    ) external payable;

}

Settings
{
  "remappings": [
    "forge-std/=lib/forge-std/src/",
    "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
    "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/",
    "ds-test/=lib/openzeppelin-contracts-upgradeable/lib/forge-std/lib/ds-test/src/",
    "erc4626-tests/=lib/openzeppelin-contracts-upgradeable/lib/erc4626-tests/",
    "halmos-cheatcodes/=lib/openzeppelin-contracts-upgradeable/lib/halmos-cheatcodes/src/",
    "openzeppelin-contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/",
    "openzeppelin-contracts/=lib/openzeppelin-contracts/"
  ],
  "optimizer": {
    "enabled": true,
    "runs": 2000
  },
  "metadata": {
    "useLiteralContent": false,
    "bytecodeHash": "ipfs",
    "appendCBOR": true
  },
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "evmVersion": "prague",
  "viaIR": false
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"components":[{"internalType":"string","name":"chainName","type":"string"},{"internalType":"address","name":"treasury","type":"address"},{"internalType":"address","name":"legacyTreasury","type":"address"},{"internalType":"address","name":"mainTelecoinFactory","type":"address"},{"internalType":"address","name":"teleportingTelecoinFactory","type":"address"},{"internalType":"address","name":"its","type":"address"},{"internalType":"address","name":"itsFactory","type":"address"},{"internalType":"address","name":"wrappedNativeToken","type":"address"},{"internalType":"address","name":"locker","type":"address"},{"internalType":"address","name":"liquidityModule","type":"address"},{"internalType":"address","name":"create3Deployer","type":"address"},{"internalType":"address","name":"growthFund","type":"address"},{"internalType":"address","name":"buybackFund","type":"address"},{"internalType":"address","name":"teamTreasuryFund","type":"address"},{"internalType":"address","name":"stakingFund","type":"address"},{"internalType":"address","name":"printrDev","type":"address"},{"internalType":"address","name":"legacyPrintrDev","type":"address"},{"internalType":"address","name":"legacyPrintrDev2","type":"address"},{"internalType":"uint256","name":"feePercentGrowth","type":"uint256"},{"internalType":"uint256","name":"feePercentBuyback","type":"uint256"},{"internalType":"uint256","name":"feePercentTeam","type":"uint256"},{"internalType":"uint256","name":"feePercentCreator","type":"uint256"},{"internalType":"uint16","name":"tradingFee","type":"uint16"}],"internalType":"struct IPrintrStorage.DeploymentParams","name":"storageParams","type":"tuple"},{"internalType":"address","name":"teleport_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"Create3AddressMismatch","type":"error"},{"inputs":[],"name":"EnforcedPause","type":"error"},{"inputs":[],"name":"ExpectedPause","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[{"internalType":"uint256","name":"fee","type":"uint256"}],"name":"FeeIsTooHigh","type":"error"},{"inputs":[],"name":"FeePercentagesMustSum","type":"error"},{"inputs":[],"name":"InsufficientInitialBuy","type":"error"},{"inputs":[],"name":"InsufficientPayment","type":"error"},{"inputs":[],"name":"InvalidBasePairDecimals","type":"error"},{"inputs":[],"name":"InvalidBasePairs","type":"error"},{"inputs":[],"name":"InvalidBasePrices","type":"error"},{"inputs":[],"name":"InvalidCreatorAddress","type":"error"},{"inputs":[],"name":"InvalidImplementation","type":"error"},{"inputs":[],"name":"InvalidInitialPrice","type":"error"},{"inputs":[],"name":"InvalidInitialization","type":"error"},{"inputs":[],"name":"InvalidLength","type":"error"},{"inputs":[],"name":"InvalidQuoteResult","type":"error"},{"inputs":[],"name":"LiquidityAlreadyDeployed","type":"error"},{"inputs":[],"name":"LiquidityDeploymentFailed","type":"error"},{"inputs":[],"name":"MathOverflowedMulDiv","type":"error"},{"inputs":[],"name":"NotInitializing","type":"error"},{"inputs":[],"name":"PoolCreationFailed","type":"error"},{"inputs":[],"name":"PriceExceedsLimit","type":"error"},{"inputs":[],"name":"RefundFailed","type":"error"},{"inputs":[],"name":"RenounceOwnershipDisabled","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"inputs":[],"name":"SwapFailed","type":"error"},{"inputs":[],"name":"TokenNotFound","type":"error"},{"inputs":[],"name":"TooHighThreshold","type":"error"},{"inputs":[],"name":"UnauthorizedCaller","type":"error"},{"inputs":[],"name":"WrongChainName","type":"error"},{"inputs":[],"name":"ZeroAmount","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"creator","type":"address"},{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"bytes32","name":"telecoinId","type":"bytes32"}],"name":"CurveCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint64","name":"version","type":"uint64"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenAmount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"baseAmount","type":"uint256"}],"name":"LiquidityDeployed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"positionManager","type":"address"},{"indexed":false,"internalType":"uint256","name":"positionId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"lockId","type":"uint256"}],"name":"LiquidityLocked","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"bytes32","name":"telecoinId","type":"bytes32"}],"name":"TelecoinPrinted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint256","name":"totalSupply","type":"uint256"}],"name":"TokenGraduated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":false,"internalType":"uint8","name":"stage","type":"uint8"},{"indexed":false,"internalType":"uint256","name":"gasLeft","type":"uint256"}],"name":"TokenGraduationPartialFailure","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"trader","type":"address"},{"indexed":false,"internalType":"bool","name":"isBuy","type":"bool"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"cost","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"priceAfter","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"issuedSupply","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"reserve","type":"uint256"}],"name":"TokenTrade","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"BIPS_SCALAR","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EVM_ADDRESS_PREFIX","outputs":[{"internalType":"bytes1","name":"","type":"bytes1"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PRECISION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"maxPrice","type":"uint256"}],"name":"buy","outputs":[{"components":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"priceLimit","type":"uint256"},{"internalType":"uint16","name":"tradingFee","type":"uint16"}],"internalType":"struct IPrintrTrading.TradeParams","name":"","type":"tuple"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"buybackFund","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"create3Deployer","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"currentChainHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenAmount","type":"uint256"}],"name":"estimateTokenCost","outputs":[{"internalType":"uint256","name":"availableAmount","type":"uint256"},{"internalType":"uint256","name":"cost","type":"uint256"},{"internalType":"uint256","name":"fee","type":"uint256"},{"internalType":"uint256","name":"priceAfter","type":"uint256"},{"internalType":"uint256","name":"issuedSupply","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"tokenAmount","type":"uint256"}],"name":"estimateTokenRefund","outputs":[{"internalType":"uint256","name":"tokenAmountIn","type":"uint256"},{"internalType":"uint256","name":"refund","type":"uint256"},{"internalType":"uint256","name":"fee","type":"uint256"},{"internalType":"uint256","name":"priceAfter","type":"uint256"},{"internalType":"uint256","name":"issuedSupply","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"feePercentBuyback","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feePercentCreator","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feePercentGrowth","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feePercentTeam","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"growthFund","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"interchainTokenService","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"itsFactory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"legacyPrintrDev","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"legacyPrintrDev2","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"legacyTreasury","outputs":[{"internalType":"contract ITreasury","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"liquidityModule","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"locker","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mainTelecoinFactory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"initialSpending","type":"uint256"},{"components":[{"internalType":"bytes32","name":"salt","type":"bytes32"},{"internalType":"bytes","name":"creatorAddresses","type":"bytes"},{"internalType":"bytes32","name":"name","type":"bytes32"},{"internalType":"bytes32","name":"symbol","type":"bytes32"},{"internalType":"bytes32","name":"packedParams","type":"bytes32"},{"internalType":"bytes32[]","name":"chains","type":"bytes32[]"},{"internalType":"bytes32[]","name":"basePairs","type":"bytes32[]"},{"internalType":"bytes","name":"basePrices","type":"bytes"}],"internalType":"struct IPrintrStorage.TelecoinParams","name":"telecoinParams","type":"tuple"}],"name":"print","outputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"bytes32","name":"telecoinId","type":"bytes32"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"printrDev","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"baseAmount","type":"uint256"}],"name":"quoteTokenAmount","outputs":[{"internalType":"uint256","name":"tokenAmount","type":"uint256"},{"internalType":"uint256","name":"cost","type":"uint256"},{"internalType":"uint256","name":"fee","type":"uint256"},{"internalType":"uint256","name":"priceAfter","type":"uint256"},{"internalType":"uint256","name":"issuedSupply","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"minPrice","type":"uint256"}],"name":"sell","outputs":[{"components":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"priceLimit","type":"uint256"},{"internalType":"uint16","name":"tradingFee","type":"uint16"}],"internalType":"struct IPrintrTrading.TradeParams","name":"","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"baseAmount","type":"uint256"},{"internalType":"uint256","name":"maxPrice","type":"uint256"}],"name":"spend","outputs":[{"components":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"priceLimit","type":"uint256"},{"internalType":"uint16","name":"tradingFee","type":"uint16"}],"internalType":"struct IPrintrTrading.TradeParams","name":"","type":"tuple"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"stakingFund","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"teamTreasuryFund","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"teleport","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"teleportingTelecoinFactory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tradingFee","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"treasury","outputs":[{"internalType":"contract ITreasury","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"signer","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"minPrice","type":"uint256"}],"name":"witnessSell","outputs":[{"components":[{"internalType":"address","name":"account","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"priceLimit","type":"uint256"},{"internalType":"uint16","name":"tradingFee","type":"uint16"}],"internalType":"struct IPrintrTrading.TradeParams","name":"","type":"tuple"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"wrappedNativeToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]

610380604052348015610010575f5ffd5b5060405161676738038061676783398101604081905261002f91610390565b816100386101ec565b805180516020918201206080908152908201516001600160a01b0390811660c09081526040840151821660e09081526060850151831661010090815293850151831661012090815260a08087015185166101409081529387015185166101609081529287015185166101809081529587015185166102209081529187015185166102009081529387015185166102405291860151841661026052938501518316610280526101a08086015184166102a0526101c08087015185166102c09081526101e08089015187169093529387015185169052938501519092169092529082015161ffff16905261012d6064612710610581565b816102c0015161ffff161115610167576102c0810151604051635ef1a3d160e01b815261ffff909116600482015260240160405180910390fd5b6102408101516102e08190526102608201516103008190526102808301516103208190526102a08401516103408190526127109390926101a6916105a0565b6101b091906105a0565b6101ba91906105a0565b146101d85760405163feaa733160e01b815260040160405180910390fd5b506001600160a01b031661036052506105c5565b7ff0c57e16840df040f15088dc2f81fe391c3923bec73e23a9662efc9c229c6a00805468010000000000000000900460ff161561023c5760405163f92ee8a960e01b815260040160405180910390fd5b80546001600160401b039081161461029b5780546001600160401b0319166001600160401b0390811782556040519081527fc7f505b2f371ae2175ee4913f4499e1f2633a7b5936321eed1cdaeb6115181d29060200160405180910390a15b50565b634e487b7160e01b5f52604160045260245ffd5b6040516102e081016001600160401b03811182821017156102d5576102d561029e565b60405290565b5f82601f8301126102ea575f5ffd5b81516001600160401b038111156103035761030361029e565b604051601f8201601f19908116603f011681016001600160401b03811182821017156103315761033161029e565b604052818152838201602001851015610348575f5ffd5b8160208501602083015e5f918101602001919091529392505050565b80516001600160a01b038116811461037a575f5ffd5b919050565b805161ffff8116811461037a575f5ffd5b5f5f604083850312156103a1575f5ffd5b82516001600160401b038111156103b6575f5ffd5b83016102e081860312156103c8575f5ffd5b6103d06102b2565b81516001600160401b038111156103e5575f5ffd5b6103f1878285016102db565b82525061040060208301610364565b602082015261041160408301610364565b604082015261042260608301610364565b606082015261043360808301610364565b608082015261044460a08301610364565b60a082015261045560c08301610364565b60c082015261046660e08301610364565b60e08201526104786101008301610364565b61010082015261048b6101208301610364565b61012082015261049e6101408301610364565b6101408201526104b16101608301610364565b6101608201526104c46101808301610364565b6101808201526104d76101a08301610364565b6101a08201526104ea6101c08301610364565b6101c08201526104fd6101e08301610364565b6101e08201526105106102008301610364565b6102008201526105236102208301610364565b6102208201526102408281015190820152610260808301519082015261028080830151908201526102a080830151908201526105626102c0830161037f565b6102c08201529250610578905060208401610364565b90509250929050565b5f8261059b57634e487b7160e01b5f52601260045260245ffd5b500490565b808201808211156105bf57634e487b7160e01b5f52601160045260245ffd5b92915050565b60805160a05160c05160e05161010051610120516101405161016051610180516101a0516101c0516101e05161020051610220516102405161026051610280516102a0516102c0516102e05161030051610320516103405161036051615f506108175f395f8181610276015261097f01525f6106e801525f61046901525f61068201525f61052f01525f61088101525f61084e01525f61059501525f61060e01525f61071b01525f61094c01525f81816104fc0152818161216201528181612e4c0152818161374401528181613c1c015281816141710152614cdd01525f61056201525f6108b401525f81816103ad0152611fc401525f8181610347015281816126190152818161299601528181612a5901528181612c6b01528181612cf101528181612ff6015281816130340152818161346e015281816134f401528181614bf301528181614d940152614dea01525f61031401525f81816102c4015261487201525f818161037a01526149ab01525f81816109d101526149d101525f818161091901528181614f5d01528181614fa3015261506d01525f81816106b5015281816118d601528181611a9e0152818161282b015281816129ec01528181612af40152818161344b015281816135f201528181613799015281816139b7015261503e01525f81816105c801528181610bcf01528181610de101528181610fbf015281816110ba0152818161123c015281816112aa0152818161144b015281816115f001528181612331015281816123a501528181612566015261259301525f81816104c901526145f60152615f505ff3fe60806040526004361061026d575f3560e01c80636ea2fa5611610152578063ca0454aa116100ca578063d7b96d4e1161007e578063f359b59d11610063578063f359b59d146109a1578063f98adc0b146109c0578063ffa1ad74146109f357610274565b8063d7b96d4e1461093b578063d86b75c71461096e57610274565b8063cd5373c0116100af578063cd5373c0146108a3578063d00cad0d146108d6578063d1bb06e41461090857610274565b8063ca0454aa1461083d578063cd0e315f1461087057610274565b80638b8e06711161012157806393b70d631161010657806393b70d63146107f0578063a9d424e21461080f578063aaf5eb681461082257610274565b80638b8e0671146107965780638c509739146107a957610274565b80636ea2fa56146106d75780637023b1511461070a57806384b2a44c1461073d57806384ed4ace1461075257610274565b8063400b6cdc116101e557806356f43352116101b45780635c975abb116101995780635c975abb146106305780635fb22c451461067157806361d027b3146106a457610274565b806356f43352146105b75780635c3640ec146105fd57610274565b8063400b6cdc146104eb5780634776b4591461051e5780634ee56d6814610551578063526a9a931461058457610274565b80632aa629751161023c57806335d1c5ad1161022157806335d1c5ad146104585780633c83e14c146104995780633efb10ea146104b857610274565b80632aa629751461039c57806331de7d15146103cf57610274565b806309c6bed9146102b35780631508da301461030357806317fcb39b14610336578063260e4b221461036957610274565b3661027457005b7f0000000000000000000000000000000000000000000000000000000000000000365f80375f5f365f845af43d5f5f3e8080156102af573d5ff35b3d5ffd5b3480156102be575f5ffd5b506102e67f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561030e575f5ffd5b506102e67f000000000000000000000000000000000000000000000000000000000000000081565b348015610341575f5ffd5b506102e67f000000000000000000000000000000000000000000000000000000000000000081565b348015610374575f5ffd5b506102e67f000000000000000000000000000000000000000000000000000000000000000081565b3480156103a7575f5ffd5b506102e67f000000000000000000000000000000000000000000000000000000000000000081565b3480156103da575f5ffd5b506103ee6103e9366004615639565b610a48565b6040516102fa91905f60c0820190506001600160a01b0383511682526001600160a01b0360208401511660208301526001600160a01b036040840151166040830152606083015160608301526080830151608083015261ffff60a08401511660a083015292915050565b348015610463575f5ffd5b5061048b7f000000000000000000000000000000000000000000000000000000000000000081565b6040519081526020016102fa565b3480156104a4575f5ffd5b506103ee6104b336600461567c565b610c14565b3480156104c3575f5ffd5b5061048b7f000000000000000000000000000000000000000000000000000000000000000081565b3480156104f6575f5ffd5b506102e67f000000000000000000000000000000000000000000000000000000000000000081565b348015610529575f5ffd5b5061048b7f000000000000000000000000000000000000000000000000000000000000000081565b34801561055c575f5ffd5b506102e67f000000000000000000000000000000000000000000000000000000000000000081565b34801561058f575f5ffd5b506102e67f000000000000000000000000000000000000000000000000000000000000000081565b3480156105c2575f5ffd5b506105ea7f000000000000000000000000000000000000000000000000000000000000000081565b60405161ffff90911681526020016102fa565b348015610608575f5ffd5b506102e67f000000000000000000000000000000000000000000000000000000000000000081565b34801561063b575f5ffd5b507fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f033005460ff1660405190151581526020016102fa565b34801561067c575f5ffd5b5061048b7f000000000000000000000000000000000000000000000000000000000000000081565b3480156106af575f5ffd5b506102e67f000000000000000000000000000000000000000000000000000000000000000081565b3480156106e2575f5ffd5b5061048b7f000000000000000000000000000000000000000000000000000000000000000081565b348015610715575f5ffd5b506102e67f000000000000000000000000000000000000000000000000000000000000000081565b348015610748575f5ffd5b5061048b61271081565b34801561075d575f5ffd5b506107655f81565b6040517fff0000000000000000000000000000000000000000000000000000000000000090911681526020016102fa565b6103ee6107a4366004615639565b610e28565b3480156107b4575f5ffd5b506107c86107c33660046156d3565b6110ed565b604080519586526020860194909452928401919091526060830152608082015260a0016102fa565b3480156107fb575f5ffd5b506107c861080a3660046156d3565b6112e6565b6103ee61081d366004615639565b611470565b34801561082d575f5ffd5b5061048b670de0b6b3a764000081565b348015610848575f5ffd5b506102e67f000000000000000000000000000000000000000000000000000000000000000081565b34801561087b575f5ffd5b506102e67f000000000000000000000000000000000000000000000000000000000000000081565b3480156108ae575f5ffd5b506102e67f000000000000000000000000000000000000000000000000000000000000000081565b6108e96108e43660046156fd565b61168e565b604080516001600160a01b0390931683526020830191909152016102fa565b348015610913575f5ffd5b506102e67f000000000000000000000000000000000000000000000000000000000000000081565b348015610946575f5ffd5b506102e67f000000000000000000000000000000000000000000000000000000000000000081565b348015610979575f5ffd5b506102e67f000000000000000000000000000000000000000000000000000000000000000081565b3480156109ac575f5ffd5b506107c86109bb3660046156d3565b6123eb565b3480156109cb575f5ffd5b506102e67f000000000000000000000000000000000000000000000000000000000000000081565b3480156109fe575f5ffd5b50610a3b6040518060400160405280600681526020017f76312e302e30000000000000000000000000000000000000000000000000000081525081565b6040516102fa9190615776565b6040805160c0810182525f80825260208201819052918101829052606081018290526080810182905260a0810191909152610a816125b8565b6001600160a01b038581165f9081527f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb0060208181526040808420815160e0810183528154968716815261ffff600160a01b8804169381019390935260ff600160b01b8704811692840192909252600160b81b8604909116606083015267ffffffffffffffff600160c01b95869004811660808401526001909101546001600160c01b03811660a08401529490940490931660c08401529190859003610b5957604051631f2a200560e01b815260040160405180910390fd5b80516001600160a01b0316610b8157604051630cbdb7b360e41b815260040160405180910390fd5b5f198503610b9657610b938733612616565b94505b6040805160c0810182523381526001600160a01b038881166020830152891691810191909152606081018690526080810185905261ffff7f00000000000000000000000000000000000000000000000000000000000000001660a0820152610bfe8282612776565b610c0787612b58565b925050505b949350505050565b6040805160c0810182525f80825260208201819052918101829052606081018290526080810182905260a0810191909152610c4d6125b8565b336001600160a01b03861614610c8f576040517f5c427cd900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b038581165f9081527f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb006020818152604092839020835160e081018552815495861680825261ffff600160a01b8804169382019390935260ff600160b01b8704811695820195909552600160b81b8604909416606085015267ffffffffffffffff600160c01b95869004811660808601526001909101546001600160c01b03811660a08601529490940490931660c083015291610d6557604051630cbdb7b360e41b815260040160405180910390fd5b5f198503610d7a57610d778789612616565b94505b845f03610d9a57604051631f2a200560e01b815260040160405180910390fd5b5f6040518060c001604052808a6001600160a01b03168152602001886001600160a01b03168152602001896001600160a01b031681526020018781526020018681526020017f000000000000000000000000000000000000000000000000000000000000000061ffff168152509050610e138282612776565b610e1c87612b58565b98975050505050505050565b6040805160c0810182525f80825260208201819052918101829052606081018290526080810182905260a0810191909152610e616125b8565b6001600160a01b038581165f9081527f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb0060208181526040808420815160e0810183528154968716815261ffff600160a01b8804169381019390935260ff600160b01b8704811692840192909252600160b81b8604909116606083015267ffffffffffffffff600160c01b95869004811660808401526001909101546001600160c01b03811660a08401529490940490931660c08401529190859003610f3957604051631f2a200560e01b815260040160405180910390fd5b80516001600160a01b0316610f6157604051630cbdb7b360e41b815260040160405180910390fd5b5f198503610f78578051610f759033612616565b94505b5f6040518060c00160405280336001600160a01b03168152602001886001600160a01b03168152602001896001600160a01b031681526020018781526020018681526020017f000000000000000000000000000000000000000000000000000000000000000061ffff1681525090508160c0015167ffffffffffffffff165f0361101f5761100782825f612bec565b606082015261101587612b58565b9250610c0c915050565b6001600160a01b038089165f9081526020858152604091829020825160e0810184528154948516815261ffff600160a01b8604169281019290925260ff600160b01b8504811693830193909352600160b81b8404909216606082015267ffffffffffffffff600160c01b93849004811660808301526001909201546001600160c01b03811660a0830152929092041660c08201526110de90877f00000000000000000000000000000000000000000000000000000000000000006130bf565b6060820152610bfe8282613216565b5f80808080807f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb006001600160a01b03808a165f9081526020838152604091829020825160e081018452815494851680825261ffff600160a01b8704169382019390935260ff600160b01b8604811694820194909452600160b81b8504909316606084015267ffffffffffffffff600160c01b94859004811660808501526001909101546001600160c01b03811660a08501529390930490921660c08201529192506111cb57604051630cbdb7b360e41b815260040160405180910390fd5b60c081015167ffffffffffffffff161580156111ee57505f816020015161ffff16115b15611279576112686040518060c001604052805f6001600160a01b031681526020015f6001600160a01b031681526020018b6001600160a01b031681526020018a81526020015f81526020017f000000000000000000000000000000000000000000000000000000000000000061ffff1681525082613b17565b9650965096509650965050506112dc565b60208101516112949061ffff16670de0b6b3a76400006157b0565b67ffffffffffffffff1660c08201526112cf81897f00000000000000000000000000000000000000000000000000000000000000005f613e2c565b9650965096509650965050505b9295509295909350565b5f80808080807f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb006001600160a01b03808a165f9081526020838152604091829020825160e081018452815494851680825261ffff600160a01b8704169382019390935260ff600160b01b8604811694820194909452600160b81b8504909316606084015267ffffffffffffffff600160c01b94859004811660808501526001909101546001600160c01b03811660a08501529390930490921660c08201529192506113c457604051630cbdb7b360e41b815260040160405180910390fd5b60c081015167ffffffffffffffff161580156113e757505f816020015161ffff16115b15611444576040805160c0810182526001600160a01b03808c16825283511660208201529081018990525f606082018190526080820181905260a082015261142f90826140cb565b929a50929850965090945092506112dc915050565b6112cf81897f00000000000000000000000000000000000000000000000000000000000000005f614358565b6040805160c0810182525f80825260208201819052918101829052606081018290526080810182905260a08101919091526114a96125b8565b6001600160a01b038581165f9081527f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb0060208181526040808420815160e0810183528154968716815261ffff600160a01b8804169381019390935260ff600160b01b8704811692840192909252600160b81b8604909116606083015267ffffffffffffffff600160c01b95869004811660808401526001909101546001600160c01b03811660a08401529490940490931660c0840152919085900361158157604051631f2a200560e01b815260040160405180910390fd5b80516001600160a01b03166115a957604051630cbdb7b360e41b815260040160405180910390fd5b5f6040518060c00160405280336001600160a01b03168152602001886001600160a01b03168152602001896001600160a01b031681526020018781526020018681526020017f000000000000000000000000000000000000000000000000000000000000000061ffff1681525090508160c0015167ffffffffffffffff165f03611684575f6116388284613b17565b5050509150505f61164c845f015133612616565b905080821061165b578061165d565b815b606084015261166d84845f612bec565b5061167789612b58565b8295505050505050610c0c565b610bfe8282613216565b5f5f6116986125b8565b604080516080810182525f8082526020820181905291810182905260608101919091527f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb00906116e685614598565b815261172e6116f860a08701876157cf565b808060200260200160405190810160405280939291908181526020018383602002808284375f920191909152506145c792505050565b60208083019190915261174c9061174790870187615815565b614670565b6001600160a01b031660408201525f6117686080870135614723565b905061177760c08701876157cf565b905061178660a08801886157cf565b9050146117a65760405163251f56a160e21b815260040160405180910390fd5b60106117b560e0880188615815565b6117c09291506157b0565b6117cd60a08801886157cf565b9050146117ed5760405163251f56a160e21b815260040160405180910390fd5b5f5b6117fc60a08801886157cf565b90508110156118bc575f61181c61181660e08a018a615815565b84614789565b9050805f03611857576040517f6d2a011900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f61186560c08a018a6157cf565b8481811061187557611875615858565b90506020020135036118b3576040517fdcad892800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b506001016117ef565b508151602082015190945061192e9087908690156118fa577f0000000000000000000000000000000000000000000000000000000000000000611900565b84604001515b61190d60a08b018b6157cf565b9050600114801561192057506020860151155b5f19876020015114156147d1565b94505f198260200151148061194557506020810151155b1561195f576119578260400151612b58565b5050506123e4565b6040805160e0810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c08101919091525f6119c66119a860c08a018a6157cf565b86602001518181106119bc576119bc615858565b9050602002013590565b90505f6119e36119d960e08b018b615815565b8760200151614789565b90505f670de0b6b3a7640000855f0151600a6119ff919061594f565b611a09919061595d565b905084604001515f03611a48576040517fb3cd5c6600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612710856020015110611a87576040517f10e4931a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040516370a0823160e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660048301528416906370a0823190602401602060405180830381865afa925050508015611b09575060408051601f3d908101601f19168201909252611b0691810190615974565b60015b611b3f576040517fdcad892800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50611b4d60a08b018b6157cf565b9050612710670de0b6b3a76400008760200151611b6a919061595d565b611b7491906157b0565b611b7e91906157b0565b602086015260408501518290611b9c90670de0b6b3a764000061595d565b611ba691906157b0565b60408601525f611bb960a08c018c6157cf565b9050838760600151670de0b6b3a7640000611bd4919061595d565b611bde91906157b0565b611be891906157b0565b9050836001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611c44575060408051601f3d908101601f19168201909252611c419181019061598b565b60015b611c7a576040517fafd210b500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60128160ff161015611cd357611c918160126159ab565b611c9c90600a61594f565b611ca690836157b0565b9150611cb38160126159ab565b611cbe90600a61594f565b8760400151611ccd91906157b0565b60408801525b60128160ff161115611d2c57611cea6012826159ab565b611cf590600a61594f565b611cff908361595d565b9150611d0c6012826159ab565b611d1790600a61594f565b8760400151611d26919061595d565b60408801525b505f198c03611d4257611d3f8433612616565b9b505b808c1015611d7c576040517ffe5cae1e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505f611d8b60a08c018c6157cf565b611d969150836157b0565b90505f670de0b6b3a7640000876040015183611db2919061595d565b611dbc91906157b0565b90505f670de0b6b3a7640000886020015185611dd8919061595d565b611de291906157b0565b611dec90846159c4565b9050808381611e0385670de0b6b3a764000061595d565b611e0d91906157b0565b611e17919061595d565b611e2191906157b0565b8960600181815250506040518060e00160405280876001600160a01b031681526020018e8060a00190611e5491906157cf565b61ffff16825250895160ff166020808301919091525f604083018190526060830181905260808301528a015167ffffffffffffffff1660a09091015296505b67ffffffffffffffff821115611ecb57611eae600a836157b0565b606088018051919350611ec0826159d7565b60ff16905250611e93565b5067ffffffffffffffff16608086015250604080870151905189916001600160a01b03808d16929116907fba476d0e4131eb62758cae024f971a6a6e5a5e21dfde233750bba5427901d2b3905f90a4604080870151868201518251600181525f6020820181905293810184905260608101919091526080810183905260a08101929092526001600160a01b0390811691908b16907f5c89aca078c4c42425a601eb984e720e9ea11723c9c40088f37fa5c68b0dddf69060c00160405180910390a350505060408084015190517f293c6a3a0000000000000000000000000000000000000000000000000000000081526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169163293c6a3a9161200b9189916004019182526001600160a01b0316602082015260400190565b6020604051808303815f875af1158015612027573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061204b9190615974565b506001600160a01b038681165f908152602086815260408083208551815493870151878401516060808a015160808b0151948a167fffffffffffffffffffff0000000000000000000000000000000000000000000090981697909717600160a01b61ffff90941693909302929092177fffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffffffff16600160b01b60ff928316027fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff1617600160b81b9190961602949094176001600160c01b03908116600160c01b67ffffffffffffffff938416810291909117845560a089015160c08a015192169190921690910217600190910155908601519051919283927f0000000000000000000000000000000000000000000000000000000000000000909116917f88e06b7000000000000000000000000000000000000000000000000000000000916121b89187918d9190602401615a74565b60408051601f198184030181529181526020820180516001600160e01b03167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161220e9190615a9e565b5f60405180830381855af49150503d805f8114612246576040519150601f19603f3d011682016040523d82523d5f602084013e61224b565b606091505b5091509150816122975780515f0361228f576040517fdc802c7400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805160208201fd5b5f818060200190518101906122ac9190615ab4565b6040517ff1e3d5c40000000000000000000000000000000000000000000000000000000081526001600160a01b038083166004830152919250908a169063f1e3d5c4906024015f604051808303815f87803b158015612309575f5ffd5b505af115801561231b573d5f5f3e3d5ffd5b50505050505050875f146123d2575f612355828a7f00000000000000000000000000000000000000000000000000000000000000006130bf565b90506123d0826040518060c00160405280336001600160a01b0316815260200187604001516001600160a01b031681526020018a6001600160a01b031681526020018481526020015f81526020017f000000000000000000000000000000000000000000000000000000000000000061ffff16815250613216565b505b6123df8360400151612b58565b505050505b9250929050565b5f80808080807f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb006001600160a01b03808a165f9081526020838152604091829020825160e081018452815494851680825261ffff600160a01b8704169382019390935260ff600160b01b8604811694820194909452600160b81b8504909316606084015267ffffffffffffffff600160c01b94859004811660808501526001909101546001600160c01b03811660a08501529390930490921660c08201529192506124c957604051630cbdb7b360e41b815260040160405180910390fd5b60c081015167ffffffffffffffff161580156124ec57505f816020015161ffff16115b15612535576040805160c08101825282516001600160a01b0390811682528b1660208201529081018990525f606082018190526080820181905260a082015261126890826140cb565b60208101516125509061ffff16670de0b6b3a76400006157b0565b67ffffffffffffffff1660c082015261258a81897f00000000000000000000000000000000000000000000000000000000000000006130bf565b96506112cf81887f00000000000000000000000000000000000000000000000000000000000000005f613e2c565b7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f033005460ff1615612614576040517fd93c066500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b031614801561265757505f34115b15612663575047612770565b6040516370a0823160e01b81526001600160a01b0383811660048301525f91908516906370a0823190602401602060405180830381865afa1580156126aa573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906126ce9190615974565b6040517fdd62ed3e0000000000000000000000000000000000000000000000000000000081526001600160a01b0385811660048301523060248301529192505f9186169063dd62ed3e90604401602060405180830381865afa158015612736573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061275a9190615974565b9050818110612769578161276b565b805b925050505b92915050565b60c08201517f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb009067ffffffffffffffff165f036127bc576127b78383614b65565b505050565b5f5f5f5f5f6127d98888606001518960a001518a60800151614358565b9450945094509450945084876060018181525050835f0361280d57604051631f2a200560e01b815260040160405180910390fd5b865160608801516040890151612850926001600160a01b03909116917f000000000000000000000000000000000000000000000000000000000000000090614eee565b61285a8385615acf565b8860a00181815161286b9190615ae2565b6001600160c01b0390811690915260a08a015160408a810180516001600160a01b039081165f90815260208d8152848220600190810180547fffffffffffffffff0000000000000000000000000000000000000000000000001697909816969096179096559151168152918a0190925290812080548693509091906128f1908490615acf565b90915550506020808801516040808a01516060808c015160a0808f015185515f8152978801929092529386018a9052908501879052608085018690526001600160c01b0316918401919091526001600160a01b03918216929116907f5c89aca078c4c42425a601eb984e720e9ea11723c9c40088f37fa5c68b0dddf69060c00160405180910390a38315612b4e57875161298a90614f5b565b87516001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116911603612abf578751604051636ce5768960e11b81526001600160a01b039182166004820152306024820152604481018690527f00000000000000000000000000000000000000000000000000000000000000009091169063d9caed12906064015f604051808303815f87803b158015612a2f575f5ffd5b505af1158015612a41573d5f5f3e3d5ffd5b5050604051632e1a7d4d60e01b8152600481018790527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03169250632e1a7d4d91506024015f604051808303815f87803b158015612aa4575f5ffd5b505af1158015612ab6573d5f5f3e3d5ffd5b50505050612b4e565b87516020880151604051636ce5768960e11b81526001600160a01b0392831660048201529082166024820152604481018690527f00000000000000000000000000000000000000000000000000000000000000009091169063d9caed12906064015f604051808303815f87803b158015612b37575f5ffd5b505af1158015612b49573d5f5f3e3d5ffd5b505050505b5050505050505050565b478015612be8575f826001600160a01b0316826040515f6040518083038185875af1925050503d805f8114612ba8576040519150601f19603f3d011682016040523d82523d5f602084013e612bad565b606091505b50509050806127b7576040517ff0c49d4400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5050565b5f5f612c04855f0151856040015186608001516150c8565b90505f6040518060c00160405280875f01516001600160a01b0316815260200186604001516001600160a01b03168152602001866060015181526020015f8152602001836001600160a01b0316815260200186602001516001600160a01b031681525090507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316865f01516001600160a01b0316148015612cac57505f34115b15612d68578460600151341015612cef576040517fcd1c886700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d0e30db086606001516040518263ffffffff1660e01b81526004015f604051808303818588803b158015612d4c575f5ffd5b505af1158015612d5e573d5f5f3e3d5ffd5b5050505050612d88565b845160608601518751612d88926001600160a01b03909116913090614eee565b5f5a90505f606061f618831115612ec0575f87612da55783612db1565b612db161f618856159c4565b6040805187516001600160a01b0390811660248301526020808a01518216604484015289840151606484015260608a0151608484015260808a0151821660a484015260a08a0151821660c4808501919091528451808503909101815260e49093018452820180516001600160e01b03167f475c70c00000000000000000000000000000000000000000000000000000000017905291519293507f0000000000000000000000000000000000000000000000000000000000000000909116918391612e7a91615a9e565b5f604051808303818686f4925050503d805f8114612eb3576040519150601f19603f3d011682016040523d82523d5f602084013e612eb8565b606091505b509093509150505b8115612ee15780806020019051810190612eda9190615974565b9550612f74565b8615612f3a5787604001516001600160a01b03167ffb1be82b5654061cfdabd96e32c72732fa8e00f8b23d8039b9cb71b449502c8f60015a6040805160ff909316835260208301919091520160405180910390a2612f74565b80515f0361228f576040517f81ceff3000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505086516040516370a0823160e01b81523060048201525f92506001600160a01b03909116906370a0823190602401602060405180830381865afa158015612fbe573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612fe29190615974565b905080156130b55786516001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811691160361309857604051632e1a7d4d60e01b8152600481018290527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690632e1a7d4d906024015f604051808303815f87803b15801561307d575f5ffd5b505af115801561308f573d5f5f3e3d5ffd5b505050506130b5565b602086015187516130b5916001600160a01b03909116908361512b565b5050509392505050565b5f5f670de0b6b3a76400008560400151600a6130db919061594f565b6130e5919061595d565b90505f856020015161ffff16826130fc91906157b0565b90505f8660600151600a613110919061594f565b876080015167ffffffffffffffff16613129919061595d565b90505f613136838361595d565b90505f8860a001516001600160c01b0316836131529190615acf565b61315c90836157b0565b90505f61316f61ffff8916612710615acf565b61317b8a61271061595d565b61318591906157b0565b9050808a60a001516001600160c01b0316856131a19190615acf565b6131ab9190615acf565b6131b590846157b0565b6131bf90836159c4565b9650826131cc88846159c4565b6131d689856159c4565b6131e090866157b0565b6131ea919061595d565b1080156131f657508615155b15613209576132066001886159c4565b96505b5050505050509392505050565b5f7f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb0090506132676040518060a001604052805f81526020015f81526020015f81526020015f81526020015f81525090565b8360c0015167ffffffffffffffff168460400151600a613287919061594f565b613291919061595d565b8152606083015160a084015160808501515f926132af928892613e2c565b6060870152608086015260408501919091526020840182905291501580156132db575081516060830151105b156132f957604051631f2a200560e01b815260040160405180910390fd5b80826040015161330991906159c4565b8560a00181815161331a9190615b01565b6001600160c01b0390811690915260a0870151604087810180516001600160a01b039081165f90815260208a8152848220600190810180547fffffffffffffffff000000000000000000000000000000000000000000000000169790981696909617909655915116815291870190925290812080548493509091906133a0908490615acf565b9250508190555083602001516001600160a01b031684604001516001600160a01b03167f5c89aca078c4c42425a601eb984e720e9ea11723c9c40088f37fa5c68b0dddf6600185602001518660400151876080015188606001518c60a0015160405161343f96959493929190951515865260208601949094526040850192909252606084015260808301526001600160c01b031660a082015260c00190565b60405180910390a3505f7f000000000000000000000000000000000000000000000000000000000000000090507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316855f01516001600160a01b03161480156134af57505f34115b156135875781604001514710156134f2576040517fcd1c886700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663d0e30db083604001516040518263ffffffff1660e01b81526004015f604051808303818588803b15801561354f575f5ffd5b505af1158015613561573d5f5f3e3d5ffd5b5050506040840151875161358293506001600160a01b03169150839061512b565b6135a7565b8351604083015186516135a7926001600160a01b03909116918490614eee565b6135b48460400151614f5b565b604080850151602080870151908501519251636ce5768960e11b81526001600160a01b039283166004820152908216602482015260448101929092527f0000000000000000000000000000000000000000000000000000000000000000169063d9caed12906064015f604051808303815f87803b158015613633575f5ffd5b505af1158015613645573d5f5f3e3d5ffd5b50505050815f0151826060015110613b10576040808501516001600160a01b03165f90815260208590522060010180546001600160c01b03169055845161368b90614f5b565b5f5a905061f6188110156137385760c0860151604080870180516001600160a01b039081165f9081526020898152938120600101805467ffffffffffffffff909616600160c01b026001600160c01b03909616959095179094559186015160608901525116907ffb1be82b5654061cfdabd96e32c72732fa8e00f8b23d8039b9cb71b449502c8f905a6040805160ff909316835260208301919091520160405180910390a2505050505050565b5f806001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001661377061f618856159c4565b604080516080810182528b81528a8201516001600160a01b0390811660208301528951828401527f000000000000000000000000000000000000000000000000000000000000000016606082015290516137cd9190602401615b20565b60408051601f198184030181529181526020820180516001600160e01b03167f9875302d000000000000000000000000000000000000000000000000000000001790525161381b9190615a9e565b5f604051808303818686f4925050503d805f8114613854576040519150601f19603f3d011682016040523d82523d5f602084013e613859565b606091505b5091509150816139045760c0880151604080890180516001600160a01b039081165f90815260208b8152938120600101805467ffffffffffffffff909616600160c01b026001600160c01b03909616959095179094559188015160608b01525116907ffb1be82b5654061cfdabd96e32c72732fa8e00f8b23d8039b9cb71b449502c8f905a6040805160ff909316835260208301919091520160405180910390a25050505050505050565b5f5f5f8380602001905181019061391b9190615b6b565b925092509250604051806040016040528084815260200183815250896002015f8c604001516001600160a01b03166001600160a01b031681526020019081526020015f2090600261396d9291906155d3565b5080156139dc576040808b01516001600160a01b03165f90815260018b0160205290812080548392906139a1908490615acf565b90915550508a516139dc906001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000008361512b565b5f88602001518b606001516139f191906159c4565b90508015613ab35760608b0181905260208c0151613a1b9061ffff16670de0b6b3a76400006157b0565b67ffffffffffffffff1660c08d015260a08b015160808c01515f91613a43918f918591613e2c565b5050509150505f613a5a8e5f01518e5f0151612616565b90505f818310613a6a5781613a6c565b825b90508015613aa05760608e01819052613a878f8f6001612bec565b8c60200151613a969190615acf565b60608f0152613aab565b60208c015160608f01525b505050613abe565b602089015160608c01525b8a604001516001600160a01b03167f3774b363547126f466f72012377433ce54030696ae07ad35617ad9317eaf776b8a5f0151604051613b0091815260200190565b60405180910390a2505050505050505b5050505050565b5f5f5f5f5f5f6040518060c00160405280885f01516001600160a01b0316815260200189604001516001600160a01b031681526020015f815260200189606001518152602001613b73895f01518b604001518c608001516150c8565b6001600160a01b0390811682525f6020928301819052604080518551841660248201528585015184166044820152858201516064820152606086015160848201526080860151841660a482015260a0860151841660c4808301919091528251808303909101815260e4909101825293840180516001600160e01b03167f72deb87d00000000000000000000000000000000000000000000000000000000179052519394509283927f000000000000000000000000000000000000000000000000000000000000000090921691613c4891615a9e565b5f60405180830381855af49150503d805f8114613c80576040519150601f19603f3d011682016040523d82523d5f602084013e613c85565b606091505b5091509150818015613c9757505f8151115b15613dfa575f81806020019051810190613cb19190615b6b565b91995096509050871580613cc557505f1988145b15613cfc576040517f4bb8f45800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b895f01516001600160a01b03168b604001516001600160a01b03161115613d3d5785613d30670de0b6b3a76400008061595d565b613d3a91906157b0565b95505b895184516001600160a01b03918216911603613d7157612710613d60828a61595d565b613d6a91906157b0565b9650613dac565b5f612710613d7f838b61595d565b613d8991906157b0565b9050670de0b6b3a7640000613d9e888361595d565b613da891906157b0565b9750505b836060015198505f670de0b6b3a76400008b60400151600a613dce919061594f565b613dd8919061595d565b90508a6020015161ffff1681613dee91906157b0565b955050505050506112dc565b6040517f4bb8f45800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f5f5f5f5f5f670de0b6b3a76400008b60400151600a613e4d919061594f565b613e57919061595d565b90508a6020015161ffff1681613e6d91906157b0565b9150505f8a60600151600a613e82919061594f565b8b6080015167ffffffffffffffff16613e9b919061595d565b90505f613ea8838361595d565b90505f8c60a001516001600160c01b031683613ec49190615acf565b613ece90836157b0565b9050613eda81856159c4565b94508915613f42575f613f13858c613efa87670de0b6b3a764000061595d565b613f0491906157b0565b613f0e919061595d565b61515c565b905081811015613f3c575f613f2882846159c4565b90508d811015613f3657809d505b50613f40565b5f9c505b505b5f8d60c0015167ffffffffffffffff168e60400151600a613f63919061594f565b613f6d919061595d565b905080613f7a8e88615acf565b1115613fa157858111613f8d575f613f97565b613f9786826159c4565b9950899c50613fa5565b8c99505b5060a08d01515f906001600160c01b031684613fc18f856159c4565b613fcb90866157b0565b613fd591906159c4565b613fdf91906159c4565b905082613fec8e846159c4565b613ff68f856159c4565b61400090866157b0565b61400a919061595d565b101561401e5761401b600182615acf565b90505b6140288a836159c4565b61403290866159c4565b955061271061404561ffff8e168361595d565b61404f91906157b0565b97508715801561406257505f8c61ffff16115b1561406c57600197505b6140768882615acf565b98505f6140838e846159c4565b905080868161409a88670de0b6b3a764000061595d565b6140a491906157b0565b6140ae919061595d565b6140b891906157b0565b9750505050505050945094509450945094565b6040805183516001600160a01b03908116602483015260208086015182166044840152858401516064840152606086015160848401526080860151821660a484015260a0860151821660c4808501919091528451808503909101815260e49093018452820180516001600160e01b03167f1cc6415d0000000000000000000000000000000000000000000000000000000017905291515f928392839283928392839283927f0000000000000000000000000000000000000000000000000000000000000000169161419b91615a9e565b5f60405180830381855af49150503d805f81146141d3576040519150601f19603f3d011682016040523d82523d5f602084013e6141d8565b606091505b50915091508180156141ea57505f8151115b15613dfa575f818060200190518101906142049190615b6b565b60408d01518c518e51949c50909a5091975092506001600160a01b0390811691160361427057895f01516001600160a01b03168a602001516001600160a01b0316111561426b578461425e670de0b6b3a76400008061595d565b61426891906157b0565b94505b6142b1565b89602001516001600160a01b03168a5f01516001600160a01b031611156142b157846142a4670de0b6b3a76400008061595d565b6142ae91906157b0565b94505b885f01516001600160a01b03168a602001516001600160a01b0316036142f8576142dd816127106159c4565b6142e7828a61595d565b6142f191906157b0565b9550614312565b612710614305828961595d565b61430f91906157b0565b95505b5f670de0b6b3a76400008a60400151600a61432d919061594f565b614337919061595d565b9050896020015161ffff168161434d91906157b0565b9450505050506112dc565b5f5f5f5f5f5f5f670de0b6b3a76400008b60400151600a614379919061594f565b614383919061595d565b90508a6020015161ffff168161439991906157b0565b9150505f8a60600151600a6143ae919061594f565b8b6080015167ffffffffffffffff166143c7919061595d565b90505f6143d4838361595d565b90505f8c60a001516001600160c01b0316836143f09190615acf565b6143fa90836157b0565b90505f61440782866159c4565b90508a15614456575f614427868d613efa88670de0b6b3a764000061595d565b905082811115614450575f61443c84836159c4565b90508e81101561444a57809e505b50614454565b5f9d505b505b808d111561446257809c505b8c995061446f8a826159c4565b95505f61447c8e84615acf565b61448690856157b0565b8f60a001516001600160c01b03168661449f9190615acf565b6144a991906159c4565b9050836144b68f85615acf565b8f856144c29190615acf565b6144cc90876157b0565b6144d6919061595d565b1080156144e257508015155b156144f5576144f26001826159c4565b90505b61271061450661ffff8f168361595d565b61451091906157b0565b985088158015614523575061ffff8d1615155b801561452e57508015155b1561453857600198505b61454289826159c4565b99505f61454f8f85615acf565b905080878161456689670de0b6b3a764000061595d565b61457091906157b0565b61457a919061595d565b61458491906157b0565b985050505050505050945094509450945094565b5f816040516020016145aa9190615c8f565b604051602081830303815290604052805190602001209050919050565b5f81515f036145e95760405163251f56a160e21b815260040160405180910390fd5b5f5b8251811015614667577f000000000000000000000000000000000000000000000000000000000000000061464d84838151811061462a5761462a615858565b60200260200101516040805180820190915260ff82168152602081019190915290565b805190602001200361465f5792915050565b6001016145eb565b505f1992915050565b5f601482900361468e576146848284615d5d565b60601c9050612770565b6014821180156146d5575082825f8181106146ab576146ab615858565b909101357fff00000000000000000000000000000000000000000000000000000000000000161590505b156146f1576146e8601560018486615d9d565b61468491615d5d565b6040517f52a7e56200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61474d60405180608001604052805f60ff1681526020015f81526020015f81526020015f81525090565b60ff8216815261ffff600883901c1660208201526dffffffffffffffffffffffffffff601883901c16604082015260889190911c606082015290565b5f838361479784601061595d565b906147a3856001615acf565b6147ae90601061595d565b926147bb93929190615d9d565b6147c491615dc4565b60801c90505b9392505050565b5f5f6147e08760800135614723565b90505f6040518061010001604052808881526020016148188a604001356040805180820190915260ff82168152602081019190915290565b81526020016148408a606001356040805180820190915260ff82168152602081019190915290565b8152602001670de0b6b3a7640000845f0151600a61485e919061594f565b614868919061595d565b81523060208201527f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031660408201525f606082015260800161499f896148ee6148bc60a08e018e6157cf565b5f8181106148cc576148cc615858565b905060200201356040805180820190915260ff82168152602081019190915290565b8051602091820120604080517f5498bf1696259c19f826e687122ca9404f85fe476a9e186af31eb6a53bf123dc81850152808201929092523060608301526080808301949094528051808303909401845260a0820181528351938301939093207f980c3be34c7ee75cc250c76223092614e21653cdf2faece10ac24fcef821df1060c08301525f60e08301526101008083019190915283518083039091018152610120909101909252815191012090565b905290505f856149cf577f00000000000000000000000000000000000000000000000000000000000000006149f1565b7f00000000000000000000000000000000000000000000000000000000000000005b90505f856149ff575f614a3a565b614a0c60a08b018b6157cf565b8551909150670de0b6b3a764000090614a2690600a61594f565b614a30919061595d565b614a3a91906157b0565b90505f5f836001600160a01b0316633dfa5c7d60e01b868c86604051602401614a6593929190615e29565b60408051601f198184030181529181526020820180516001600160e01b03167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051614abb9190615a9e565b5f60405180830381855af49150503d805f8114614af3576040519150601f19603f3d011682016040523d82523d5f602084013e614af8565b606091505b509150915081614b09578060208201fd5b80806020019051810190614b1d9190615ab4565b96508a876001600160a01b03167f50cabf552e2709fecaf92964e5189fdafb7e5cbab162274eb0f541dbb48588a160405160405180910390a350505050505095945050505050565b805160608201516040830151614b88926001600160a01b03909116913090614eee565b5f614b9f835f0151836040015184608001516150c8565b90505f6040518060c0016040528084604001516001600160a01b03168152602001855f01516001600160a01b03168152602001846060015181526020015f8152602001836001600160a01b031681526020017f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316865f01516001600160a01b031614614c37578460200151614c39565b305b6001600160a01b039081169091526040805183518316602482015260208085015184166044830152848301516064830152606085015160848301526080850151841660a483015260a0850151841660c4808401919091528351808403909101815260e49092018352810180516001600160e01b03167f475c70c00000000000000000000000000000000000000000000000000000000017905290519293505f9283927f00000000000000000000000000000000000000000000000000000000000000001691614d0791615a9e565b5f60405180830381855af49150503d805f8114614d3f576040519150601f19603f3d011682016040523d82523d5f602084013e614d44565b606091505b509150915081614d885780515f0361228f576040517f81ceff3000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b85516001600160a01b037f00000000000000000000000000000000000000000000000000000000000000008116911603614e4b575f81806020019051810190614dd19190615974565b604051632e1a7d4d60e01b8152600481018290529091507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031690632e1a7d4d906024015f604051808303815f87803b158015614e33575f5ffd5b505af1158015614e45573d5f5f3e3d5ffd5b50505050505b60408086015190516370a0823160e01b81523060048201525f916001600160a01b0316906370a0823190602401602060405180830381865afa158015614e93573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190614eb79190615974565b90508015614ee557614ee586602001518288604001516001600160a01b031661512b9092919063ffffffff16565b50505050505050565b6040516001600160a01b038481166024830152838116604483015260648201839052614f559186918216906323b872dd906084015b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050615240565b50505050565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316614f8c5750565b6040516370a0823160e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000811660048301525f91908316906370a0823190602401602060405180830381865afa158015614ff3573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906150179190615974565b90508015612be857604051636ce5768960e11b81526001600160a01b0383811660048301527f000000000000000000000000000000000000000000000000000000000000000081166024830152604482018390527f0000000000000000000000000000000000000000000000000000000000000000169063d9caed12906064015f604051808303815f87803b1580156150ae575f5ffd5b505af11580156150c0573d5f5f3e3d5ffd5b505050505050565b5f815f036150d757505f6147ca565b5f846001600160a01b0316846001600160a01b031611156151165782615105670de0b6b3a76400008061595d565b61510f91906157b0565b9050615119565b50815b615122816152bf565b95945050505050565b6040516001600160a01b038381166024830152604482018390526127b791859182169063a9059cbb90606401614f23565b5f815f0361516b57505f919050565b5f6001615177846152da565b901c6001901b9050600181848161519057615190615788565b048201901c905060018184816151a8576151a8615788565b048201901c905060018184816151c0576151c0615788565b048201901c905060018184816151d8576151d8615788565b048201901c905060018184816151f0576151f0615788565b048201901c9050600181848161520857615208615788565b048201901c9050600181848161522057615220615788565b048201901c90506147ca8182858161523a5761523a615788565b0461536d565b5f6152546001600160a01b03841683615382565b905080515f141580156152785750808060200190518101906152769190615efb565b155b156127b7576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b03841660048201526024015b60405180910390fd5b5f612770613f0e83600160c01b670de0b6b3a764000061538f565b5f80608083901c156152ee57608092831c92015b604083901c1561530057604092831c92015b602083901c1561531257602092831c92015b601083901c1561532457601092831c92015b600883901c1561533657600892831c92015b600483901c1561534857600492831c92015b600283901c1561535a57600292831c92015b600183901c156127705760010192915050565b5f81831061537b57816147ca565b5090919050565b60606147ca83835f615467565b5f838302815f1985870982811083820303915050805f036153c3578382816153b9576153b9615788565b04925050506147ca565b8084116153fc576040517f227bc15300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f848688095f868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010186841190950394909402919094039290920491909117919091029150509392505050565b6060814710156154a5576040517fcd7860590000000000000000000000000000000000000000000000000000000081523060048201526024016152b6565b5f5f856001600160a01b031684866040516154c09190615a9e565b5f6040518083038185875af1925050503d805f81146154fa576040519150601f19603f3d011682016040523d82523d5f602084013e6154ff565b606091505b509150915061550f868383615519565b9695505050505050565b60608261552e576155298261558e565b6147ca565b815115801561554557506001600160a01b0384163b155b15615587576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b03851660048201526024016152b6565b50806147ca565b80511561559e5780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50565b8260028101928215615601579160200282015b828111156156015782518255916020019190600101906155e6565b5061560d929150615611565b5090565b5b8082111561560d575f8155600101615612565b6001600160a01b03811681146155d0575f5ffd5b5f5f5f5f6080858703121561564c575f5ffd5b843561565781615625565b9350602085013561566781615625565b93969395505050506040820135916060013590565b5f5f5f5f5f60a08688031215615690575f5ffd5b853561569b81615625565b945060208601356156ab81615625565b935060408601356156bb81615625565b94979396509394606081013594506080013592915050565b5f5f604083850312156156e4575f5ffd5b82356156ef81615625565b946020939093013593505050565b5f5f6040838503121561570e575f5ffd5b82359150602083013567ffffffffffffffff81111561572b575f5ffd5b8301610100818603121561573d575f5ffd5b809150509250929050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f6147ca6020830184615748565b634e487b7160e01b5f52601260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b5f826157ca57634e487b7160e01b5f52601260045260245ffd5b500490565b5f5f8335601e198436030181126157e4575f5ffd5b83018035915067ffffffffffffffff8211156157fe575f5ffd5b6020019150600581901b36038213156123e4575f5ffd5b5f5f8335601e1984360301811261582a575f5ffd5b83018035915067ffffffffffffffff821115615844575f5ffd5b6020019150368190038213156123e4575f5ffd5b634e487b7160e01b5f52603260045260245ffd5b6001815b60018411156158a75780850481111561588b5761588b61579c565b600184161561589957908102905b60019390931c928002615870565b935093915050565b5f826158bd57506001612770565b816158c957505f612770565b81600181146158df57600281146158e957615905565b6001915050612770565b60ff8411156158fa576158fa61579c565b50506001821b612770565b5060208310610133831016604e8410600b8410161715615928575081810a612770565b6159345f19848461586c565b805f19048211156159475761594761579c565b029392505050565b5f6147ca60ff8416836158af565b80820281158282048414176127705761277061579c565b5f60208284031215615984575f5ffd5b5051919050565b5f6020828403121561599b575f5ffd5b815160ff811681146147ca575f5ffd5b60ff82811682821603908111156127705761277061579c565b818103818111156127705761277061579c565b5f60ff821660ff81036159ec576159ec61579c565b60010192915050565b6001600160a01b03815116825261ffff602082015116602083015260ff604082015116604083015260ff606082015116606083015267ffffffffffffffff608082015116608083015260a0810151615a5860a08401826001600160c01b03169052565b5060c08101516127b760c084018267ffffffffffffffff169052565b6101208101615a8382866159f5565b6001600160a01b039390931660e08201526101000152919050565b5f82518060208501845e5f920191825250919050565b5f60208284031215615ac4575f5ffd5b81516147ca81615625565b808201808211156127705761277061579c565b6001600160c01b0382811682821603908111156127705761277061579c565b6001600160c01b0381811683821601908111156127705761277061579c565b5f61014082019050615b338284516159f5565b6001600160a01b0360208401511660e083015260408301516101008301526001600160a01b0360608401511661012083015292915050565b5f5f5f60608486031215615b7d575f5ffd5b5050815160208301516040909301519094929350919050565b5f5f8335601e19843603018112615bab575f5ffd5b830160208101925035905067ffffffffffffffff811115615bca575f5ffd5b8036038213156123e4575f5ffd5b81835281816020850137505f602082840101525f6020601f19601f840116840101905092915050565b5f5f8335601e19843603018112615c16575f5ffd5b830160208101925035905067ffffffffffffffff811115615c35575f5ffd5b8060051b36038213156123e4575f5ffd5b8183525f7f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff831115615c76575f5ffd5b8260051b80836020870137939093016020019392505050565b60208082528235828201525f90615ca890840184615b96565b6101006040850152615cbf61012085018284615bd8565b604086013560608681019190915286013560808087019190915286013560a080870191909152909250615cf59150850185615c01565b601f198584030160c0860152615d0c838284615c46565b92505050615d1d60c0850185615c01565b601f198584030160e0860152615d34838284615c46565b92505050615d4560e0850185615b96565b601f198584030161010086015261550f838284615bd8565b80356bffffffffffffffffffffffff198116906014841015615d96576bffffffffffffffffffffffff19808560140360031b1b82161691505b5092915050565b5f5f85851115615dab575f5ffd5b83861115615db7575f5ffd5b5050820193919092039150565b80357fffffffffffffffffffffffffffffffff000000000000000000000000000000008116906010841015615d96577fffffffffffffffffffffffffffffffff00000000000000000000000000000000808560100360031b1b82161691505092915050565b60608152835160608201525f60208501516101006080840152615e50610160840182615748565b905060408601517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa08483030160a0850152615e8b8282615748565b915050606086015160c08401526001600160a01b0360808701511660e084015260a0860151615ec66101008501826001600160a01b03169052565b5060c08601516001600160a01b0390811661012085015260e09096015161014084015293909416602082015260400152919050565b5f60208284031215615f0b575f5ffd5b815180151581146147ca575f5ffdfea264697066735822122069a5090a068a6bca2779519b3274a245f71cb97166351c014b8642854450e8fd64736f6c634300081b003300000000000000000000000000000000000000000000000000000000000000400000000000000000000000001860dc5f4b93bca6da1f547ad475be0f0ab8b56000000000000000000000000000000000000000000000000000000000000002e00000000000000000000000008c843012b347664caf84b907053754208a933fee000000000000000000000000de420c835240216198bf4fd1eda28d7ead2a154900000000000000000000000013c5501bbac8ca3bce3c130502dc1e2033369dca00000000000000000000000013bed504264cb4e00d46c8efbfb95e83672be360000000000000000000000000b5fb4be02232b1bba4dc8f81dc24c26980de9e3c00000000000000000000000083a93500d23fbc3e82b410ad07a6a9f7a0670d6600000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab100000000000000000000000025c9c4b56e820e0dea438b145284f02d9ca9bd52000000000000000000000000d556a71d5bf70417f051732ddb0aaf95c719c3790000000000000000000000001f279e2fd9c4febd6f2b009b627441aa7639ce880000000000000000000000000d856684de729e48f267e6264fd969c2c749a3f40000000000000000000000008a67dcee6d279005e0f8ddab574078091277cc4e0000000000000000000000003069f27064ffb11a960413fbe7d0742185f53519000000000000000000000000c2e4fa661fc810b4805df91427fdda2f69dedd570000000000000000000000001e1577ba4dc74007ed6e8fb3d3ce2ce5477f531d00000000000000000000000077d3a7110bc847b1942d115f3eb9d1fa15032787000000000000000000000000c209ae58076e311b51f10b80fc070f4d2425c0d300000000000000000000000000000000000000000000000000000000000009c40000000000000000000000000000000000000000000000000000000000000fa000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000009c400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000008617262697472756d000000000000000000000000000000000000000000000000

Deployed Bytecode

0x60806040526004361061026d575f3560e01c80636ea2fa5611610152578063ca0454aa116100ca578063d7b96d4e1161007e578063f359b59d11610063578063f359b59d146109a1578063f98adc0b146109c0578063ffa1ad74146109f357610274565b8063d7b96d4e1461093b578063d86b75c71461096e57610274565b8063cd5373c0116100af578063cd5373c0146108a3578063d00cad0d146108d6578063d1bb06e41461090857610274565b8063ca0454aa1461083d578063cd0e315f1461087057610274565b80638b8e06711161012157806393b70d631161010657806393b70d63146107f0578063a9d424e21461080f578063aaf5eb681461082257610274565b80638b8e0671146107965780638c509739146107a957610274565b80636ea2fa56146106d75780637023b1511461070a57806384b2a44c1461073d57806384ed4ace1461075257610274565b8063400b6cdc116101e557806356f43352116101b45780635c975abb116101995780635c975abb146106305780635fb22c451461067157806361d027b3146106a457610274565b806356f43352146105b75780635c3640ec146105fd57610274565b8063400b6cdc146104eb5780634776b4591461051e5780634ee56d6814610551578063526a9a931461058457610274565b80632aa629751161023c57806335d1c5ad1161022157806335d1c5ad146104585780633c83e14c146104995780633efb10ea146104b857610274565b80632aa629751461039c57806331de7d15146103cf57610274565b806309c6bed9146102b35780631508da301461030357806317fcb39b14610336578063260e4b221461036957610274565b3661027457005b7f0000000000000000000000001860dc5f4b93bca6da1f547ad475be0f0ab8b560365f80375f5f365f845af43d5f5f3e8080156102af573d5ff35b3d5ffd5b3480156102be575f5ffd5b506102e67f000000000000000000000000b5fb4be02232b1bba4dc8f81dc24c26980de9e3c81565b6040516001600160a01b0390911681526020015b60405180910390f35b34801561030e575f5ffd5b506102e67f00000000000000000000000083a93500d23fbc3e82b410ad07a6a9f7a0670d6681565b348015610341575f5ffd5b506102e67f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab181565b348015610374575f5ffd5b506102e67f00000000000000000000000013bed504264cb4e00d46c8efbfb95e83672be36081565b3480156103a7575f5ffd5b506102e67f0000000000000000000000001e1577ba4dc74007ed6e8fb3d3ce2ce5477f531d81565b3480156103da575f5ffd5b506103ee6103e9366004615639565b610a48565b6040516102fa91905f60c0820190506001600160a01b0383511682526001600160a01b0360208401511660208301526001600160a01b036040840151166040830152606083015160608301526080830151608083015261ffff60a08401511660a083015292915050565b348015610463575f5ffd5b5061048b7f00000000000000000000000000000000000000000000000000000000000003e881565b6040519081526020016102fa565b3480156104a4575f5ffd5b506103ee6104b336600461567c565b610c14565b3480156104c3575f5ffd5b5061048b7f9186606d55c571b43a756333453d90ab5653c483deb4980cda697bfa36fba5de81565b3480156104f6575f5ffd5b506102e67f000000000000000000000000d556a71d5bf70417f051732ddb0aaf95c719c37981565b348015610529575f5ffd5b5061048b7f00000000000000000000000000000000000000000000000000000000000009c481565b34801561055c575f5ffd5b506102e67f000000000000000000000000c209ae58076e311b51f10b80fc070f4d2425c0d381565b34801561058f575f5ffd5b506102e67f0000000000000000000000008a67dcee6d279005e0f8ddab574078091277cc4e81565b3480156105c2575f5ffd5b506105ea7f000000000000000000000000000000000000000000000000000000000000006481565b60405161ffff90911681526020016102fa565b348015610608575f5ffd5b506102e67f0000000000000000000000000d856684de729e48f267e6264fd969c2c749a3f481565b34801561063b575f5ffd5b507fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f033005460ff1660405190151581526020016102fa565b34801561067c575f5ffd5b5061048b7f0000000000000000000000000000000000000000000000000000000000000fa081565b3480156106af575f5ffd5b506102e67f0000000000000000000000008c843012b347664caf84b907053754208a933fee81565b3480156106e2575f5ffd5b5061048b7f00000000000000000000000000000000000000000000000000000000000009c481565b348015610715575f5ffd5b506102e67f0000000000000000000000001f279e2fd9c4febd6f2b009b627441aa7639ce8881565b348015610748575f5ffd5b5061048b61271081565b34801561075d575f5ffd5b506107655f81565b6040517fff0000000000000000000000000000000000000000000000000000000000000090911681526020016102fa565b6103ee6107a4366004615639565b610e28565b3480156107b4575f5ffd5b506107c86107c33660046156d3565b6110ed565b604080519586526020860194909452928401919091526060830152608082015260a0016102fa565b3480156107fb575f5ffd5b506107c861080a3660046156d3565b6112e6565b6103ee61081d366004615639565b611470565b34801561082d575f5ffd5b5061048b670de0b6b3a764000081565b348015610848575f5ffd5b506102e67f0000000000000000000000003069f27064ffb11a960413fbe7d0742185f5351981565b34801561087b575f5ffd5b506102e67f000000000000000000000000c2e4fa661fc810b4805df91427fdda2f69dedd5781565b3480156108ae575f5ffd5b506102e67f00000000000000000000000077d3a7110bc847b1942d115f3eb9d1fa1503278781565b6108e96108e43660046156fd565b61168e565b604080516001600160a01b0390931683526020830191909152016102fa565b348015610913575f5ffd5b506102e67f000000000000000000000000de420c835240216198bf4fd1eda28d7ead2a154981565b348015610946575f5ffd5b506102e67f00000000000000000000000025c9c4b56e820e0dea438b145284f02d9ca9bd5281565b348015610979575f5ffd5b506102e67f0000000000000000000000001860dc5f4b93bca6da1f547ad475be0f0ab8b56081565b3480156109ac575f5ffd5b506107c86109bb3660046156d3565b6123eb565b3480156109cb575f5ffd5b506102e67f00000000000000000000000013c5501bbac8ca3bce3c130502dc1e2033369dca81565b3480156109fe575f5ffd5b50610a3b6040518060400160405280600681526020017f76312e302e30000000000000000000000000000000000000000000000000000081525081565b6040516102fa9190615776565b6040805160c0810182525f80825260208201819052918101829052606081018290526080810182905260a0810191909152610a816125b8565b6001600160a01b038581165f9081527f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb0060208181526040808420815160e0810183528154968716815261ffff600160a01b8804169381019390935260ff600160b01b8704811692840192909252600160b81b8604909116606083015267ffffffffffffffff600160c01b95869004811660808401526001909101546001600160c01b03811660a08401529490940490931660c08401529190859003610b5957604051631f2a200560e01b815260040160405180910390fd5b80516001600160a01b0316610b8157604051630cbdb7b360e41b815260040160405180910390fd5b5f198503610b9657610b938733612616565b94505b6040805160c0810182523381526001600160a01b038881166020830152891691810191909152606081018690526080810185905261ffff7f00000000000000000000000000000000000000000000000000000000000000641660a0820152610bfe8282612776565b610c0787612b58565b925050505b949350505050565b6040805160c0810182525f80825260208201819052918101829052606081018290526080810182905260a0810191909152610c4d6125b8565b336001600160a01b03861614610c8f576040517f5c427cd900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6001600160a01b038581165f9081527f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb006020818152604092839020835160e081018552815495861680825261ffff600160a01b8804169382019390935260ff600160b01b8704811695820195909552600160b81b8604909416606085015267ffffffffffffffff600160c01b95869004811660808601526001909101546001600160c01b03811660a08601529490940490931660c083015291610d6557604051630cbdb7b360e41b815260040160405180910390fd5b5f198503610d7a57610d778789612616565b94505b845f03610d9a57604051631f2a200560e01b815260040160405180910390fd5b5f6040518060c001604052808a6001600160a01b03168152602001886001600160a01b03168152602001896001600160a01b031681526020018781526020018681526020017f000000000000000000000000000000000000000000000000000000000000006461ffff168152509050610e138282612776565b610e1c87612b58565b98975050505050505050565b6040805160c0810182525f80825260208201819052918101829052606081018290526080810182905260a0810191909152610e616125b8565b6001600160a01b038581165f9081527f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb0060208181526040808420815160e0810183528154968716815261ffff600160a01b8804169381019390935260ff600160b01b8704811692840192909252600160b81b8604909116606083015267ffffffffffffffff600160c01b95869004811660808401526001909101546001600160c01b03811660a08401529490940490931660c08401529190859003610f3957604051631f2a200560e01b815260040160405180910390fd5b80516001600160a01b0316610f6157604051630cbdb7b360e41b815260040160405180910390fd5b5f198503610f78578051610f759033612616565b94505b5f6040518060c00160405280336001600160a01b03168152602001886001600160a01b03168152602001896001600160a01b031681526020018781526020018681526020017f000000000000000000000000000000000000000000000000000000000000006461ffff1681525090508160c0015167ffffffffffffffff165f0361101f5761100782825f612bec565b606082015261101587612b58565b9250610c0c915050565b6001600160a01b038089165f9081526020858152604091829020825160e0810184528154948516815261ffff600160a01b8604169281019290925260ff600160b01b8504811693830193909352600160b81b8404909216606082015267ffffffffffffffff600160c01b93849004811660808301526001909201546001600160c01b03811660a0830152929092041660c08201526110de90877f00000000000000000000000000000000000000000000000000000000000000646130bf565b6060820152610bfe8282613216565b5f80808080807f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb006001600160a01b03808a165f9081526020838152604091829020825160e081018452815494851680825261ffff600160a01b8704169382019390935260ff600160b01b8604811694820194909452600160b81b8504909316606084015267ffffffffffffffff600160c01b94859004811660808501526001909101546001600160c01b03811660a08501529390930490921660c08201529192506111cb57604051630cbdb7b360e41b815260040160405180910390fd5b60c081015167ffffffffffffffff161580156111ee57505f816020015161ffff16115b15611279576112686040518060c001604052805f6001600160a01b031681526020015f6001600160a01b031681526020018b6001600160a01b031681526020018a81526020015f81526020017f000000000000000000000000000000000000000000000000000000000000006461ffff1681525082613b17565b9650965096509650965050506112dc565b60208101516112949061ffff16670de0b6b3a76400006157b0565b67ffffffffffffffff1660c08201526112cf81897f00000000000000000000000000000000000000000000000000000000000000645f613e2c565b9650965096509650965050505b9295509295909350565b5f80808080807f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb006001600160a01b03808a165f9081526020838152604091829020825160e081018452815494851680825261ffff600160a01b8704169382019390935260ff600160b01b8604811694820194909452600160b81b8504909316606084015267ffffffffffffffff600160c01b94859004811660808501526001909101546001600160c01b03811660a08501529390930490921660c08201529192506113c457604051630cbdb7b360e41b815260040160405180910390fd5b60c081015167ffffffffffffffff161580156113e757505f816020015161ffff16115b15611444576040805160c0810182526001600160a01b03808c16825283511660208201529081018990525f606082018190526080820181905260a082015261142f90826140cb565b929a50929850965090945092506112dc915050565b6112cf81897f00000000000000000000000000000000000000000000000000000000000000645f614358565b6040805160c0810182525f80825260208201819052918101829052606081018290526080810182905260a08101919091526114a96125b8565b6001600160a01b038581165f9081527f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb0060208181526040808420815160e0810183528154968716815261ffff600160a01b8804169381019390935260ff600160b01b8704811692840192909252600160b81b8604909116606083015267ffffffffffffffff600160c01b95869004811660808401526001909101546001600160c01b03811660a08401529490940490931660c0840152919085900361158157604051631f2a200560e01b815260040160405180910390fd5b80516001600160a01b03166115a957604051630cbdb7b360e41b815260040160405180910390fd5b5f6040518060c00160405280336001600160a01b03168152602001886001600160a01b03168152602001896001600160a01b031681526020018781526020018681526020017f000000000000000000000000000000000000000000000000000000000000006461ffff1681525090508160c0015167ffffffffffffffff165f03611684575f6116388284613b17565b5050509150505f61164c845f015133612616565b905080821061165b578061165d565b815b606084015261166d84845f612bec565b5061167789612b58565b8295505050505050610c0c565b610bfe8282613216565b5f5f6116986125b8565b604080516080810182525f8082526020820181905291810182905260608101919091527f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb00906116e685614598565b815261172e6116f860a08701876157cf565b808060200260200160405190810160405280939291908181526020018383602002808284375f920191909152506145c792505050565b60208083019190915261174c9061174790870187615815565b614670565b6001600160a01b031660408201525f6117686080870135614723565b905061177760c08701876157cf565b905061178660a08801886157cf565b9050146117a65760405163251f56a160e21b815260040160405180910390fd5b60106117b560e0880188615815565b6117c09291506157b0565b6117cd60a08801886157cf565b9050146117ed5760405163251f56a160e21b815260040160405180910390fd5b5f5b6117fc60a08801886157cf565b90508110156118bc575f61181c61181660e08a018a615815565b84614789565b9050805f03611857576040517f6d2a011900000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f61186560c08a018a6157cf565b8481811061187557611875615858565b90506020020135036118b3576040517fdcad892800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b506001016117ef565b508151602082015190945061192e9087908690156118fa577f0000000000000000000000008c843012b347664caf84b907053754208a933fee611900565b84604001515b61190d60a08b018b6157cf565b9050600114801561192057506020860151155b5f19876020015114156147d1565b94505f198260200151148061194557506020810151155b1561195f576119578260400151612b58565b5050506123e4565b6040805160e0810182525f80825260208201819052918101829052606081018290526080810182905260a0810182905260c08101919091525f6119c66119a860c08a018a6157cf565b86602001518181106119bc576119bc615858565b9050602002013590565b90505f6119e36119d960e08b018b615815565b8760200151614789565b90505f670de0b6b3a7640000855f0151600a6119ff919061594f565b611a09919061595d565b905084604001515f03611a48576040517fb3cd5c6600000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b612710856020015110611a87576040517f10e4931a00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b6040516370a0823160e01b81526001600160a01b037f0000000000000000000000008c843012b347664caf84b907053754208a933fee811660048301528416906370a0823190602401602060405180830381865afa925050508015611b09575060408051601f3d908101601f19168201909252611b0691810190615974565b60015b611b3f576040517fdcad892800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50611b4d60a08b018b6157cf565b9050612710670de0b6b3a76400008760200151611b6a919061595d565b611b7491906157b0565b611b7e91906157b0565b602086015260408501518290611b9c90670de0b6b3a764000061595d565b611ba691906157b0565b60408601525f611bb960a08c018c6157cf565b9050838760600151670de0b6b3a7640000611bd4919061595d565b611bde91906157b0565b611be891906157b0565b9050836001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa925050508015611c44575060408051601f3d908101601f19168201909252611c419181019061598b565b60015b611c7a576040517fafd210b500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b60128160ff161015611cd357611c918160126159ab565b611c9c90600a61594f565b611ca690836157b0565b9150611cb38160126159ab565b611cbe90600a61594f565b8760400151611ccd91906157b0565b60408801525b60128160ff161115611d2c57611cea6012826159ab565b611cf590600a61594f565b611cff908361595d565b9150611d0c6012826159ab565b611d1790600a61594f565b8760400151611d26919061595d565b60408801525b505f198c03611d4257611d3f8433612616565b9b505b808c1015611d7c576040517ffe5cae1e00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505f611d8b60a08c018c6157cf565b611d969150836157b0565b90505f670de0b6b3a7640000876040015183611db2919061595d565b611dbc91906157b0565b90505f670de0b6b3a7640000886020015185611dd8919061595d565b611de291906157b0565b611dec90846159c4565b9050808381611e0385670de0b6b3a764000061595d565b611e0d91906157b0565b611e17919061595d565b611e2191906157b0565b8960600181815250506040518060e00160405280876001600160a01b031681526020018e8060a00190611e5491906157cf565b61ffff16825250895160ff166020808301919091525f604083018190526060830181905260808301528a015167ffffffffffffffff1660a09091015296505b67ffffffffffffffff821115611ecb57611eae600a836157b0565b606088018051919350611ec0826159d7565b60ff16905250611e93565b5067ffffffffffffffff16608086015250604080870151905189916001600160a01b03808d16929116907fba476d0e4131eb62758cae024f971a6a6e5a5e21dfde233750bba5427901d2b3905f90a4604080870151868201518251600181525f6020820181905293810184905260608101919091526080810183905260a08101929092526001600160a01b0390811691908b16907f5c89aca078c4c42425a601eb984e720e9ea11723c9c40088f37fa5c68b0dddf69060c00160405180910390a350505060408084015190517f293c6a3a0000000000000000000000000000000000000000000000000000000081526001600160a01b037f0000000000000000000000001e1577ba4dc74007ed6e8fb3d3ce2ce5477f531d169163293c6a3a9161200b9189916004019182526001600160a01b0316602082015260400190565b6020604051808303815f875af1158015612027573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061204b9190615974565b506001600160a01b038681165f908152602086815260408083208551815493870151878401516060808a015160808b0151948a167fffffffffffffffffffff0000000000000000000000000000000000000000000090981697909717600160a01b61ffff90941693909302929092177fffffffffffffffff0000ffffffffffffffffffffffffffffffffffffffffffff16600160b01b60ff928316027fffffffffffffffff00ffffffffffffffffffffffffffffffffffffffffffffff1617600160b81b9190961602949094176001600160c01b03908116600160c01b67ffffffffffffffff938416810291909117845560a089015160c08a015192169190921690910217600190910155908601519051919283927f000000000000000000000000d556a71d5bf70417f051732ddb0aaf95c719c379909116917f88e06b7000000000000000000000000000000000000000000000000000000000916121b89187918d9190602401615a74565b60408051601f198184030181529181526020820180516001600160e01b03167fffffffff0000000000000000000000000000000000000000000000000000000090941693909317909252905161220e9190615a9e565b5f60405180830381855af49150503d805f8114612246576040519150601f19603f3d011682016040523d82523d5f602084013e61224b565b606091505b5091509150816122975780515f0361228f576040517fdc802c7400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b805160208201fd5b5f818060200190518101906122ac9190615ab4565b6040517ff1e3d5c40000000000000000000000000000000000000000000000000000000081526001600160a01b038083166004830152919250908a169063f1e3d5c4906024015f604051808303815f87803b158015612309575f5ffd5b505af115801561231b573d5f5f3e3d5ffd5b50505050505050875f146123d2575f612355828a7f00000000000000000000000000000000000000000000000000000000000000646130bf565b90506123d0826040518060c00160405280336001600160a01b0316815260200187604001516001600160a01b031681526020018a6001600160a01b031681526020018481526020015f81526020017f000000000000000000000000000000000000000000000000000000000000006461ffff16815250613216565b505b6123df8360400151612b58565b505050505b9250929050565b5f80808080807f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb006001600160a01b03808a165f9081526020838152604091829020825160e081018452815494851680825261ffff600160a01b8704169382019390935260ff600160b01b8604811694820194909452600160b81b8504909316606084015267ffffffffffffffff600160c01b94859004811660808501526001909101546001600160c01b03811660a08501529390930490921660c08201529192506124c957604051630cbdb7b360e41b815260040160405180910390fd5b60c081015167ffffffffffffffff161580156124ec57505f816020015161ffff16115b15612535576040805160c08101825282516001600160a01b0390811682528b1660208201529081018990525f606082018190526080820181905260a082015261126890826140cb565b60208101516125509061ffff16670de0b6b3a76400006157b0565b67ffffffffffffffff1660c082015261258a81897f00000000000000000000000000000000000000000000000000000000000000646130bf565b96506112cf81887f00000000000000000000000000000000000000000000000000000000000000645f613e2c565b7fcd5ed15c6e187e77e9aee88184c21f4f2182ab5827cb3b7e07fbedcd63f033005460ff1615612614576040517fd93c066500000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b5f7f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab16001600160a01b0316836001600160a01b031614801561265757505f34115b15612663575047612770565b6040516370a0823160e01b81526001600160a01b0383811660048301525f91908516906370a0823190602401602060405180830381865afa1580156126aa573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906126ce9190615974565b6040517fdd62ed3e0000000000000000000000000000000000000000000000000000000081526001600160a01b0385811660048301523060248301529192505f9186169063dd62ed3e90604401602060405180830381865afa158015612736573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061275a9190615974565b9050818110612769578161276b565b805b925050505b92915050565b60c08201517f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb009067ffffffffffffffff165f036127bc576127b78383614b65565b505050565b5f5f5f5f5f6127d98888606001518960a001518a60800151614358565b9450945094509450945084876060018181525050835f0361280d57604051631f2a200560e01b815260040160405180910390fd5b865160608801516040890151612850926001600160a01b03909116917f0000000000000000000000008c843012b347664caf84b907053754208a933fee90614eee565b61285a8385615acf565b8860a00181815161286b9190615ae2565b6001600160c01b0390811690915260a08a015160408a810180516001600160a01b039081165f90815260208d8152848220600190810180547fffffffffffffffff0000000000000000000000000000000000000000000000001697909816969096179096559151168152918a0190925290812080548693509091906128f1908490615acf565b90915550506020808801516040808a01516060808c015160a0808f015185515f8152978801929092529386018a9052908501879052608085018690526001600160c01b0316918401919091526001600160a01b03918216929116907f5c89aca078c4c42425a601eb984e720e9ea11723c9c40088f37fa5c68b0dddf69060c00160405180910390a38315612b4e57875161298a90614f5b565b87516001600160a01b037f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab18116911603612abf578751604051636ce5768960e11b81526001600160a01b039182166004820152306024820152604481018690527f0000000000000000000000008c843012b347664caf84b907053754208a933fee9091169063d9caed12906064015f604051808303815f87803b158015612a2f575f5ffd5b505af1158015612a41573d5f5f3e3d5ffd5b5050604051632e1a7d4d60e01b8152600481018790527f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab16001600160a01b03169250632e1a7d4d91506024015f604051808303815f87803b158015612aa4575f5ffd5b505af1158015612ab6573d5f5f3e3d5ffd5b50505050612b4e565b87516020880151604051636ce5768960e11b81526001600160a01b0392831660048201529082166024820152604481018690527f0000000000000000000000008c843012b347664caf84b907053754208a933fee9091169063d9caed12906064015f604051808303815f87803b158015612b37575f5ffd5b505af1158015612b49573d5f5f3e3d5ffd5b505050505b5050505050505050565b478015612be8575f826001600160a01b0316826040515f6040518083038185875af1925050503d805f8114612ba8576040519150601f19603f3d011682016040523d82523d5f602084013e612bad565b606091505b50509050806127b7576040517ff0c49d4400000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5050565b5f5f612c04855f0151856040015186608001516150c8565b90505f6040518060c00160405280875f01516001600160a01b0316815260200186604001516001600160a01b03168152602001866060015181526020015f8152602001836001600160a01b0316815260200186602001516001600160a01b031681525090507f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab16001600160a01b0316865f01516001600160a01b0316148015612cac57505f34115b15612d68578460600151341015612cef576040517fcd1c886700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab16001600160a01b031663d0e30db086606001516040518263ffffffff1660e01b81526004015f604051808303818588803b158015612d4c575f5ffd5b505af1158015612d5e573d5f5f3e3d5ffd5b5050505050612d88565b845160608601518751612d88926001600160a01b03909116913090614eee565b5f5a90505f606061f618831115612ec0575f87612da55783612db1565b612db161f618856159c4565b6040805187516001600160a01b0390811660248301526020808a01518216604484015289840151606484015260608a0151608484015260808a0151821660a484015260a08a0151821660c4808501919091528451808503909101815260e49093018452820180516001600160e01b03167f475c70c00000000000000000000000000000000000000000000000000000000017905291519293507f000000000000000000000000d556a71d5bf70417f051732ddb0aaf95c719c379909116918391612e7a91615a9e565b5f604051808303818686f4925050503d805f8114612eb3576040519150601f19603f3d011682016040523d82523d5f602084013e612eb8565b606091505b509093509150505b8115612ee15780806020019051810190612eda9190615974565b9550612f74565b8615612f3a5787604001516001600160a01b03167ffb1be82b5654061cfdabd96e32c72732fa8e00f8b23d8039b9cb71b449502c8f60015a6040805160ff909316835260208301919091520160405180910390a2612f74565b80515f0361228f576040517f81ceff3000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b505086516040516370a0823160e01b81523060048201525f92506001600160a01b03909116906370a0823190602401602060405180830381865afa158015612fbe573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612fe29190615974565b905080156130b55786516001600160a01b037f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1811691160361309857604051632e1a7d4d60e01b8152600481018290527f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab16001600160a01b031690632e1a7d4d906024015f604051808303815f87803b15801561307d575f5ffd5b505af115801561308f573d5f5f3e3d5ffd5b505050506130b5565b602086015187516130b5916001600160a01b03909116908361512b565b5050509392505050565b5f5f670de0b6b3a76400008560400151600a6130db919061594f565b6130e5919061595d565b90505f856020015161ffff16826130fc91906157b0565b90505f8660600151600a613110919061594f565b876080015167ffffffffffffffff16613129919061595d565b90505f613136838361595d565b90505f8860a001516001600160c01b0316836131529190615acf565b61315c90836157b0565b90505f61316f61ffff8916612710615acf565b61317b8a61271061595d565b61318591906157b0565b9050808a60a001516001600160c01b0316856131a19190615acf565b6131ab9190615acf565b6131b590846157b0565b6131bf90836159c4565b9650826131cc88846159c4565b6131d689856159c4565b6131e090866157b0565b6131ea919061595d565b1080156131f657508615155b15613209576132066001886159c4565b96505b5050505050509392505050565b5f7f4115e7e53fb5d2198d5cadf1cb530b9c70f06c4e1b4565d7cc7245d4040dcb0090506132676040518060a001604052805f81526020015f81526020015f81526020015f81526020015f81525090565b8360c0015167ffffffffffffffff168460400151600a613287919061594f565b613291919061595d565b8152606083015160a084015160808501515f926132af928892613e2c565b6060870152608086015260408501919091526020840182905291501580156132db575081516060830151105b156132f957604051631f2a200560e01b815260040160405180910390fd5b80826040015161330991906159c4565b8560a00181815161331a9190615b01565b6001600160c01b0390811690915260a0870151604087810180516001600160a01b039081165f90815260208a8152848220600190810180547fffffffffffffffff000000000000000000000000000000000000000000000000169790981696909617909655915116815291870190925290812080548493509091906133a0908490615acf565b9250508190555083602001516001600160a01b031684604001516001600160a01b03167f5c89aca078c4c42425a601eb984e720e9ea11723c9c40088f37fa5c68b0dddf6600185602001518660400151876080015188606001518c60a0015160405161343f96959493929190951515865260208601949094526040850192909252606084015260808301526001600160c01b031660a082015260c00190565b60405180910390a3505f7f0000000000000000000000008c843012b347664caf84b907053754208a933fee90507f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab16001600160a01b0316855f01516001600160a01b03161480156134af57505f34115b156135875781604001514710156134f2576040517fcd1c886700000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b7f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab16001600160a01b031663d0e30db083604001516040518263ffffffff1660e01b81526004015f604051808303818588803b15801561354f575f5ffd5b505af1158015613561573d5f5f3e3d5ffd5b5050506040840151875161358293506001600160a01b03169150839061512b565b6135a7565b8351604083015186516135a7926001600160a01b03909116918490614eee565b6135b48460400151614f5b565b604080850151602080870151908501519251636ce5768960e11b81526001600160a01b039283166004820152908216602482015260448101929092527f0000000000000000000000008c843012b347664caf84b907053754208a933fee169063d9caed12906064015f604051808303815f87803b158015613633575f5ffd5b505af1158015613645573d5f5f3e3d5ffd5b50505050815f0151826060015110613b10576040808501516001600160a01b03165f90815260208590522060010180546001600160c01b03169055845161368b90614f5b565b5f5a905061f6188110156137385760c0860151604080870180516001600160a01b039081165f9081526020898152938120600101805467ffffffffffffffff909616600160c01b026001600160c01b03909616959095179094559186015160608901525116907ffb1be82b5654061cfdabd96e32c72732fa8e00f8b23d8039b9cb71b449502c8f905a6040805160ff909316835260208301919091520160405180910390a2505050505050565b5f806001600160a01b037f000000000000000000000000d556a71d5bf70417f051732ddb0aaf95c719c3791661377061f618856159c4565b604080516080810182528b81528a8201516001600160a01b0390811660208301528951828401527f0000000000000000000000008c843012b347664caf84b907053754208a933fee16606082015290516137cd9190602401615b20565b60408051601f198184030181529181526020820180516001600160e01b03167f9875302d000000000000000000000000000000000000000000000000000000001790525161381b9190615a9e565b5f604051808303818686f4925050503d805f8114613854576040519150601f19603f3d011682016040523d82523d5f602084013e613859565b606091505b5091509150816139045760c0880151604080890180516001600160a01b039081165f90815260208b8152938120600101805467ffffffffffffffff909616600160c01b026001600160c01b03909616959095179094559188015160608b01525116907ffb1be82b5654061cfdabd96e32c72732fa8e00f8b23d8039b9cb71b449502c8f905a6040805160ff909316835260208301919091520160405180910390a25050505050505050565b5f5f5f8380602001905181019061391b9190615b6b565b925092509250604051806040016040528084815260200183815250896002015f8c604001516001600160a01b03166001600160a01b031681526020019081526020015f2090600261396d9291906155d3565b5080156139dc576040808b01516001600160a01b03165f90815260018b0160205290812080548392906139a1908490615acf565b90915550508a516139dc906001600160a01b03167f0000000000000000000000008c843012b347664caf84b907053754208a933fee8361512b565b5f88602001518b606001516139f191906159c4565b90508015613ab35760608b0181905260208c0151613a1b9061ffff16670de0b6b3a76400006157b0565b67ffffffffffffffff1660c08d015260a08b015160808c01515f91613a43918f918591613e2c565b5050509150505f613a5a8e5f01518e5f0151612616565b90505f818310613a6a5781613a6c565b825b90508015613aa05760608e01819052613a878f8f6001612bec565b8c60200151613a969190615acf565b60608f0152613aab565b60208c015160608f01525b505050613abe565b602089015160608c01525b8a604001516001600160a01b03167f3774b363547126f466f72012377433ce54030696ae07ad35617ad9317eaf776b8a5f0151604051613b0091815260200190565b60405180910390a2505050505050505b5050505050565b5f5f5f5f5f5f6040518060c00160405280885f01516001600160a01b0316815260200189604001516001600160a01b031681526020015f815260200189606001518152602001613b73895f01518b604001518c608001516150c8565b6001600160a01b0390811682525f6020928301819052604080518551841660248201528585015184166044820152858201516064820152606086015160848201526080860151841660a482015260a0860151841660c4808301919091528251808303909101815260e4909101825293840180516001600160e01b03167f72deb87d00000000000000000000000000000000000000000000000000000000179052519394509283927f000000000000000000000000d556a71d5bf70417f051732ddb0aaf95c719c37990921691613c4891615a9e565b5f60405180830381855af49150503d805f8114613c80576040519150601f19603f3d011682016040523d82523d5f602084013e613c85565b606091505b5091509150818015613c9757505f8151115b15613dfa575f81806020019051810190613cb19190615b6b565b91995096509050871580613cc557505f1988145b15613cfc576040517f4bb8f45800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b895f01516001600160a01b03168b604001516001600160a01b03161115613d3d5785613d30670de0b6b3a76400008061595d565b613d3a91906157b0565b95505b895184516001600160a01b03918216911603613d7157612710613d60828a61595d565b613d6a91906157b0565b9650613dac565b5f612710613d7f838b61595d565b613d8991906157b0565b9050670de0b6b3a7640000613d9e888361595d565b613da891906157b0565b9750505b836060015198505f670de0b6b3a76400008b60400151600a613dce919061594f565b613dd8919061595d565b90508a6020015161ffff1681613dee91906157b0565b955050505050506112dc565b6040517f4bb8f45800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f5f5f5f5f5f5f670de0b6b3a76400008b60400151600a613e4d919061594f565b613e57919061595d565b90508a6020015161ffff1681613e6d91906157b0565b9150505f8a60600151600a613e82919061594f565b8b6080015167ffffffffffffffff16613e9b919061595d565b90505f613ea8838361595d565b90505f8c60a001516001600160c01b031683613ec49190615acf565b613ece90836157b0565b9050613eda81856159c4565b94508915613f42575f613f13858c613efa87670de0b6b3a764000061595d565b613f0491906157b0565b613f0e919061595d565b61515c565b905081811015613f3c575f613f2882846159c4565b90508d811015613f3657809d505b50613f40565b5f9c505b505b5f8d60c0015167ffffffffffffffff168e60400151600a613f63919061594f565b613f6d919061595d565b905080613f7a8e88615acf565b1115613fa157858111613f8d575f613f97565b613f9786826159c4565b9950899c50613fa5565b8c99505b5060a08d01515f906001600160c01b031684613fc18f856159c4565b613fcb90866157b0565b613fd591906159c4565b613fdf91906159c4565b905082613fec8e846159c4565b613ff68f856159c4565b61400090866157b0565b61400a919061595d565b101561401e5761401b600182615acf565b90505b6140288a836159c4565b61403290866159c4565b955061271061404561ffff8e168361595d565b61404f91906157b0565b97508715801561406257505f8c61ffff16115b1561406c57600197505b6140768882615acf565b98505f6140838e846159c4565b905080868161409a88670de0b6b3a764000061595d565b6140a491906157b0565b6140ae919061595d565b6140b891906157b0565b9750505050505050945094509450945094565b6040805183516001600160a01b03908116602483015260208086015182166044840152858401516064840152606086015160848401526080860151821660a484015260a0860151821660c4808501919091528451808503909101815260e49093018452820180516001600160e01b03167f1cc6415d0000000000000000000000000000000000000000000000000000000017905291515f928392839283928392839283927f000000000000000000000000d556a71d5bf70417f051732ddb0aaf95c719c379169161419b91615a9e565b5f60405180830381855af49150503d805f81146141d3576040519150601f19603f3d011682016040523d82523d5f602084013e6141d8565b606091505b50915091508180156141ea57505f8151115b15613dfa575f818060200190518101906142049190615b6b565b60408d01518c518e51949c50909a5091975092506001600160a01b0390811691160361427057895f01516001600160a01b03168a602001516001600160a01b0316111561426b578461425e670de0b6b3a76400008061595d565b61426891906157b0565b94505b6142b1565b89602001516001600160a01b03168a5f01516001600160a01b031611156142b157846142a4670de0b6b3a76400008061595d565b6142ae91906157b0565b94505b885f01516001600160a01b03168a602001516001600160a01b0316036142f8576142dd816127106159c4565b6142e7828a61595d565b6142f191906157b0565b9550614312565b612710614305828961595d565b61430f91906157b0565b95505b5f670de0b6b3a76400008a60400151600a61432d919061594f565b614337919061595d565b9050896020015161ffff168161434d91906157b0565b9450505050506112dc565b5f5f5f5f5f5f5f670de0b6b3a76400008b60400151600a614379919061594f565b614383919061595d565b90508a6020015161ffff168161439991906157b0565b9150505f8a60600151600a6143ae919061594f565b8b6080015167ffffffffffffffff166143c7919061595d565b90505f6143d4838361595d565b90505f8c60a001516001600160c01b0316836143f09190615acf565b6143fa90836157b0565b90505f61440782866159c4565b90508a15614456575f614427868d613efa88670de0b6b3a764000061595d565b905082811115614450575f61443c84836159c4565b90508e81101561444a57809e505b50614454565b5f9d505b505b808d111561446257809c505b8c995061446f8a826159c4565b95505f61447c8e84615acf565b61448690856157b0565b8f60a001516001600160c01b03168661449f9190615acf565b6144a991906159c4565b9050836144b68f85615acf565b8f856144c29190615acf565b6144cc90876157b0565b6144d6919061595d565b1080156144e257508015155b156144f5576144f26001826159c4565b90505b61271061450661ffff8f168361595d565b61451091906157b0565b985088158015614523575061ffff8d1615155b801561452e57508015155b1561453857600198505b61454289826159c4565b99505f61454f8f85615acf565b905080878161456689670de0b6b3a764000061595d565b61457091906157b0565b61457a919061595d565b61458491906157b0565b985050505050505050945094509450945094565b5f816040516020016145aa9190615c8f565b604051602081830303815290604052805190602001209050919050565b5f81515f036145e95760405163251f56a160e21b815260040160405180910390fd5b5f5b8251811015614667577f9186606d55c571b43a756333453d90ab5653c483deb4980cda697bfa36fba5de61464d84838151811061462a5761462a615858565b60200260200101516040805180820190915260ff82168152602081019190915290565b805190602001200361465f5792915050565b6001016145eb565b505f1992915050565b5f601482900361468e576146848284615d5d565b60601c9050612770565b6014821180156146d5575082825f8181106146ab576146ab615858565b909101357fff00000000000000000000000000000000000000000000000000000000000000161590505b156146f1576146e8601560018486615d9d565b61468491615d5d565b6040517f52a7e56200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b61474d60405180608001604052805f60ff1681526020015f81526020015f81526020015f81525090565b60ff8216815261ffff600883901c1660208201526dffffffffffffffffffffffffffff601883901c16604082015260889190911c606082015290565b5f838361479784601061595d565b906147a3856001615acf565b6147ae90601061595d565b926147bb93929190615d9d565b6147c491615dc4565b60801c90505b9392505050565b5f5f6147e08760800135614723565b90505f6040518061010001604052808881526020016148188a604001356040805180820190915260ff82168152602081019190915290565b81526020016148408a606001356040805180820190915260ff82168152602081019190915290565b8152602001670de0b6b3a7640000845f0151600a61485e919061594f565b614868919061595d565b81523060208201527f000000000000000000000000b5fb4be02232b1bba4dc8f81dc24c26980de9e3c6001600160a01b031660408201525f606082015260800161499f896148ee6148bc60a08e018e6157cf565b5f8181106148cc576148cc615858565b905060200201356040805180820190915260ff82168152602081019190915290565b8051602091820120604080517f5498bf1696259c19f826e687122ca9404f85fe476a9e186af31eb6a53bf123dc81850152808201929092523060608301526080808301949094528051808303909401845260a0820181528351938301939093207f980c3be34c7ee75cc250c76223092614e21653cdf2faece10ac24fcef821df1060c08301525f60e08301526101008083019190915283518083039091018152610120909101909252815191012090565b905290505f856149cf577f00000000000000000000000013bed504264cb4e00d46c8efbfb95e83672be3606149f1565b7f00000000000000000000000013c5501bbac8ca3bce3c130502dc1e2033369dca5b90505f856149ff575f614a3a565b614a0c60a08b018b6157cf565b8551909150670de0b6b3a764000090614a2690600a61594f565b614a30919061595d565b614a3a91906157b0565b90505f5f836001600160a01b0316633dfa5c7d60e01b868c86604051602401614a6593929190615e29565b60408051601f198184030181529181526020820180516001600160e01b03167fffffffff00000000000000000000000000000000000000000000000000000000909416939093179092529051614abb9190615a9e565b5f60405180830381855af49150503d805f8114614af3576040519150601f19603f3d011682016040523d82523d5f602084013e614af8565b606091505b509150915081614b09578060208201fd5b80806020019051810190614b1d9190615ab4565b96508a876001600160a01b03167f50cabf552e2709fecaf92964e5189fdafb7e5cbab162274eb0f541dbb48588a160405160405180910390a350505050505095945050505050565b805160608201516040830151614b88926001600160a01b03909116913090614eee565b5f614b9f835f0151836040015184608001516150c8565b90505f6040518060c0016040528084604001516001600160a01b03168152602001855f01516001600160a01b03168152602001846060015181526020015f8152602001836001600160a01b031681526020017f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab16001600160a01b0316865f01516001600160a01b031614614c37578460200151614c39565b305b6001600160a01b039081169091526040805183518316602482015260208085015184166044830152848301516064830152606085015160848301526080850151841660a483015260a0850151841660c4808401919091528351808403909101815260e49092018352810180516001600160e01b03167f475c70c00000000000000000000000000000000000000000000000000000000017905290519293505f9283927f000000000000000000000000d556a71d5bf70417f051732ddb0aaf95c719c3791691614d0791615a9e565b5f60405180830381855af49150503d805f8114614d3f576040519150601f19603f3d011682016040523d82523d5f602084013e614d44565b606091505b509150915081614d885780515f0361228f576040517f81ceff3000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b85516001600160a01b037f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab18116911603614e4b575f81806020019051810190614dd19190615974565b604051632e1a7d4d60e01b8152600481018290529091507f00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab16001600160a01b031690632e1a7d4d906024015f604051808303815f87803b158015614e33575f5ffd5b505af1158015614e45573d5f5f3e3d5ffd5b50505050505b60408086015190516370a0823160e01b81523060048201525f916001600160a01b0316906370a0823190602401602060405180830381865afa158015614e93573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190614eb79190615974565b90508015614ee557614ee586602001518288604001516001600160a01b031661512b9092919063ffffffff16565b50505050505050565b6040516001600160a01b038481166024830152838116604483015260648201839052614f559186918216906323b872dd906084015b604051602081830303815290604052915060e01b6020820180516001600160e01b038381831617835250505050615240565b50505050565b7f000000000000000000000000de420c835240216198bf4fd1eda28d7ead2a15496001600160a01b0316614f8c5750565b6040516370a0823160e01b81526001600160a01b037f000000000000000000000000de420c835240216198bf4fd1eda28d7ead2a1549811660048301525f91908316906370a0823190602401602060405180830381865afa158015614ff3573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906150179190615974565b90508015612be857604051636ce5768960e11b81526001600160a01b0383811660048301527f0000000000000000000000008c843012b347664caf84b907053754208a933fee81166024830152604482018390527f000000000000000000000000de420c835240216198bf4fd1eda28d7ead2a1549169063d9caed12906064015f604051808303815f87803b1580156150ae575f5ffd5b505af11580156150c0573d5f5f3e3d5ffd5b505050505050565b5f815f036150d757505f6147ca565b5f846001600160a01b0316846001600160a01b031611156151165782615105670de0b6b3a76400008061595d565b61510f91906157b0565b9050615119565b50815b615122816152bf565b95945050505050565b6040516001600160a01b038381166024830152604482018390526127b791859182169063a9059cbb90606401614f23565b5f815f0361516b57505f919050565b5f6001615177846152da565b901c6001901b9050600181848161519057615190615788565b048201901c905060018184816151a8576151a8615788565b048201901c905060018184816151c0576151c0615788565b048201901c905060018184816151d8576151d8615788565b048201901c905060018184816151f0576151f0615788565b048201901c9050600181848161520857615208615788565b048201901c9050600181848161522057615220615788565b048201901c90506147ca8182858161523a5761523a615788565b0461536d565b5f6152546001600160a01b03841683615382565b905080515f141580156152785750808060200190518101906152769190615efb565b155b156127b7576040517f5274afe70000000000000000000000000000000000000000000000000000000081526001600160a01b03841660048201526024015b60405180910390fd5b5f612770613f0e83600160c01b670de0b6b3a764000061538f565b5f80608083901c156152ee57608092831c92015b604083901c1561530057604092831c92015b602083901c1561531257602092831c92015b601083901c1561532457601092831c92015b600883901c1561533657600892831c92015b600483901c1561534857600492831c92015b600283901c1561535a57600292831c92015b600183901c156127705760010192915050565b5f81831061537b57816147ca565b5090919050565b60606147ca83835f615467565b5f838302815f1985870982811083820303915050805f036153c3578382816153b9576153b9615788565b04925050506147ca565b8084116153fc576040517f227bc15300000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f848688095f868103871696879004966002600389028118808a02820302808a02820302808a02820302808a02820302808a02820302808a02909103029181900381900460010186841190950394909402919094039290920491909117919091029150509392505050565b6060814710156154a5576040517fcd7860590000000000000000000000000000000000000000000000000000000081523060048201526024016152b6565b5f5f856001600160a01b031684866040516154c09190615a9e565b5f6040518083038185875af1925050503d805f81146154fa576040519150601f19603f3d011682016040523d82523d5f602084013e6154ff565b606091505b509150915061550f868383615519565b9695505050505050565b60608261552e576155298261558e565b6147ca565b815115801561554557506001600160a01b0384163b155b15615587576040517f9996b3150000000000000000000000000000000000000000000000000000000081526001600160a01b03851660048201526024016152b6565b50806147ca565b80511561559e5780518082602001fd5b6040517f1425ea4200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50565b8260028101928215615601579160200282015b828111156156015782518255916020019190600101906155e6565b5061560d929150615611565b5090565b5b8082111561560d575f8155600101615612565b6001600160a01b03811681146155d0575f5ffd5b5f5f5f5f6080858703121561564c575f5ffd5b843561565781615625565b9350602085013561566781615625565b93969395505050506040820135916060013590565b5f5f5f5f5f60a08688031215615690575f5ffd5b853561569b81615625565b945060208601356156ab81615625565b935060408601356156bb81615625565b94979396509394606081013594506080013592915050565b5f5f604083850312156156e4575f5ffd5b82356156ef81615625565b946020939093013593505050565b5f5f6040838503121561570e575f5ffd5b82359150602083013567ffffffffffffffff81111561572b575f5ffd5b8301610100818603121561573d575f5ffd5b809150509250929050565b5f81518084528060208401602086015e5f602082860101526020601f19601f83011685010191505092915050565b602081525f6147ca6020830184615748565b634e487b7160e01b5f52601260045260245ffd5b634e487b7160e01b5f52601160045260245ffd5b5f826157ca57634e487b7160e01b5f52601260045260245ffd5b500490565b5f5f8335601e198436030181126157e4575f5ffd5b83018035915067ffffffffffffffff8211156157fe575f5ffd5b6020019150600581901b36038213156123e4575f5ffd5b5f5f8335601e1984360301811261582a575f5ffd5b83018035915067ffffffffffffffff821115615844575f5ffd5b6020019150368190038213156123e4575f5ffd5b634e487b7160e01b5f52603260045260245ffd5b6001815b60018411156158a75780850481111561588b5761588b61579c565b600184161561589957908102905b60019390931c928002615870565b935093915050565b5f826158bd57506001612770565b816158c957505f612770565b81600181146158df57600281146158e957615905565b6001915050612770565b60ff8411156158fa576158fa61579c565b50506001821b612770565b5060208310610133831016604e8410600b8410161715615928575081810a612770565b6159345f19848461586c565b805f19048211156159475761594761579c565b029392505050565b5f6147ca60ff8416836158af565b80820281158282048414176127705761277061579c565b5f60208284031215615984575f5ffd5b5051919050565b5f6020828403121561599b575f5ffd5b815160ff811681146147ca575f5ffd5b60ff82811682821603908111156127705761277061579c565b818103818111156127705761277061579c565b5f60ff821660ff81036159ec576159ec61579c565b60010192915050565b6001600160a01b03815116825261ffff602082015116602083015260ff604082015116604083015260ff606082015116606083015267ffffffffffffffff608082015116608083015260a0810151615a5860a08401826001600160c01b03169052565b5060c08101516127b760c084018267ffffffffffffffff169052565b6101208101615a8382866159f5565b6001600160a01b039390931660e08201526101000152919050565b5f82518060208501845e5f920191825250919050565b5f60208284031215615ac4575f5ffd5b81516147ca81615625565b808201808211156127705761277061579c565b6001600160c01b0382811682821603908111156127705761277061579c565b6001600160c01b0381811683821601908111156127705761277061579c565b5f61014082019050615b338284516159f5565b6001600160a01b0360208401511660e083015260408301516101008301526001600160a01b0360608401511661012083015292915050565b5f5f5f60608486031215615b7d575f5ffd5b5050815160208301516040909301519094929350919050565b5f5f8335601e19843603018112615bab575f5ffd5b830160208101925035905067ffffffffffffffff811115615bca575f5ffd5b8036038213156123e4575f5ffd5b81835281816020850137505f602082840101525f6020601f19601f840116840101905092915050565b5f5f8335601e19843603018112615c16575f5ffd5b830160208101925035905067ffffffffffffffff811115615c35575f5ffd5b8060051b36038213156123e4575f5ffd5b8183525f7f07ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff831115615c76575f5ffd5b8260051b80836020870137939093016020019392505050565b60208082528235828201525f90615ca890840184615b96565b6101006040850152615cbf61012085018284615bd8565b604086013560608681019190915286013560808087019190915286013560a080870191909152909250615cf59150850185615c01565b601f198584030160c0860152615d0c838284615c46565b92505050615d1d60c0850185615c01565b601f198584030160e0860152615d34838284615c46565b92505050615d4560e0850185615b96565b601f198584030161010086015261550f838284615bd8565b80356bffffffffffffffffffffffff198116906014841015615d96576bffffffffffffffffffffffff19808560140360031b1b82161691505b5092915050565b5f5f85851115615dab575f5ffd5b83861115615db7575f5ffd5b5050820193919092039150565b80357fffffffffffffffffffffffffffffffff000000000000000000000000000000008116906010841015615d96577fffffffffffffffffffffffffffffffff00000000000000000000000000000000808560100360031b1b82161691505092915050565b60608152835160608201525f60208501516101006080840152615e50610160840182615748565b905060408601517fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa08483030160a0850152615e8b8282615748565b915050606086015160c08401526001600160a01b0360808701511660e084015260a0860151615ec66101008501826001600160a01b03169052565b5060c08601516001600160a01b0390811661012085015260e09096015161014084015293909416602082015260400152919050565b5f60208284031215615f0b575f5ffd5b815180151581146147ca575f5ffdfea264697066735822122069a5090a068a6bca2779519b3274a245f71cb97166351c014b8642854450e8fd64736f6c634300081b0033

Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)

00000000000000000000000000000000000000000000000000000000000000400000000000000000000000001860dc5f4b93bca6da1f547ad475be0f0ab8b56000000000000000000000000000000000000000000000000000000000000002e00000000000000000000000008c843012b347664caf84b907053754208a933fee000000000000000000000000de420c835240216198bf4fd1eda28d7ead2a154900000000000000000000000013c5501bbac8ca3bce3c130502dc1e2033369dca00000000000000000000000013bed504264cb4e00d46c8efbfb95e83672be360000000000000000000000000b5fb4be02232b1bba4dc8f81dc24c26980de9e3c00000000000000000000000083a93500d23fbc3e82b410ad07a6a9f7a0670d6600000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab100000000000000000000000025c9c4b56e820e0dea438b145284f02d9ca9bd52000000000000000000000000d556a71d5bf70417f051732ddb0aaf95c719c3790000000000000000000000001f279e2fd9c4febd6f2b009b627441aa7639ce880000000000000000000000000d856684de729e48f267e6264fd969c2c749a3f40000000000000000000000008a67dcee6d279005e0f8ddab574078091277cc4e0000000000000000000000003069f27064ffb11a960413fbe7d0742185f53519000000000000000000000000c2e4fa661fc810b4805df91427fdda2f69dedd570000000000000000000000001e1577ba4dc74007ed6e8fb3d3ce2ce5477f531d00000000000000000000000077d3a7110bc847b1942d115f3eb9d1fa15032787000000000000000000000000c209ae58076e311b51f10b80fc070f4d2425c0d300000000000000000000000000000000000000000000000000000000000009c40000000000000000000000000000000000000000000000000000000000000fa000000000000000000000000000000000000000000000000000000000000003e800000000000000000000000000000000000000000000000000000000000009c400000000000000000000000000000000000000000000000000000000000000640000000000000000000000000000000000000000000000000000000000000008617262697472756d000000000000000000000000000000000000000000000000

-----Decoded View---------------
Arg [0] : storageParams (tuple):
Arg [1] : chainName (string): arbitrum
Arg [2] : treasury (address): 0x8c843012B347664caf84B907053754208a933FeE
Arg [3] : legacyTreasury (address): 0xdE420c835240216198bf4fd1eDa28D7EAD2A1549
Arg [4] : mainTelecoinFactory (address): 0x13c5501BbAc8ca3bcE3C130502dc1e2033369dCa
Arg [5] : teleportingTelecoinFactory (address): 0x13bEd504264cB4E00d46c8EfBFB95e83672Be360
Arg [6] : its (address): 0xB5FB4BE02232B1bBA4dC8f81dc24C26980dE9e3C
Arg [7] : itsFactory (address): 0x83a93500d23Fbc3e82B410aD07A6a9F7A0670D66
Arg [8] : wrappedNativeToken (address): 0x82aF49447D8a07e3bd95BD0d56f35241523fBab1
Arg [9] : locker (address): 0x25c9C4B56E820e0DEA438b145284F02D9Ca9Bd52
Arg [10] : liquidityModule (address): 0xD556a71d5bF70417f051732ddb0AAF95c719c379
Arg [11] : create3Deployer (address): 0x1F279E2fd9c4FebD6F2B009b627441aa7639cE88
Arg [12] : growthFund (address): 0x0d856684dE729E48f267E6264Fd969c2c749a3F4
Arg [13] : buybackFund (address): 0x8a67dCeE6d279005e0F8DDaB574078091277Cc4e
Arg [14] : teamTreasuryFund (address): 0x3069F27064fFb11a960413fBE7d0742185F53519
Arg [15] : stakingFund (address): 0xc2e4FA661FC810b4805DF91427FdDA2F69DeDD57
Arg [16] : printrDev (address): 0x1E1577ba4dC74007ED6e8FB3d3CE2Ce5477f531D
Arg [17] : legacyPrintrDev (address): 0x77d3a7110bC847b1942d115f3EB9D1Fa15032787
Arg [18] : legacyPrintrDev2 (address): 0xC209Ae58076E311B51F10B80fc070f4d2425c0d3
Arg [19] : feePercentGrowth (uint256): 2500
Arg [20] : feePercentBuyback (uint256): 4000
Arg [21] : feePercentTeam (uint256): 1000
Arg [22] : feePercentCreator (uint256): 2500
Arg [23] : tradingFee (uint16): 100

Arg [1] : teleport_ (address): 0x1860DC5f4B93BCA6Da1F547Ad475be0f0ab8b560

-----Encoded View---------------
27 Constructor Arguments found :
Arg [0] : 0000000000000000000000000000000000000000000000000000000000000040
Arg [1] : 0000000000000000000000001860dc5f4b93bca6da1f547ad475be0f0ab8b560
Arg [2] : 00000000000000000000000000000000000000000000000000000000000002e0
Arg [3] : 0000000000000000000000008c843012b347664caf84b907053754208a933fee
Arg [4] : 000000000000000000000000de420c835240216198bf4fd1eda28d7ead2a1549
Arg [5] : 00000000000000000000000013c5501bbac8ca3bce3c130502dc1e2033369dca
Arg [6] : 00000000000000000000000013bed504264cb4e00d46c8efbfb95e83672be360
Arg [7] : 000000000000000000000000b5fb4be02232b1bba4dc8f81dc24c26980de9e3c
Arg [8] : 00000000000000000000000083a93500d23fbc3e82b410ad07a6a9f7a0670d66
Arg [9] : 00000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1
Arg [10] : 00000000000000000000000025c9c4b56e820e0dea438b145284f02d9ca9bd52
Arg [11] : 000000000000000000000000d556a71d5bf70417f051732ddb0aaf95c719c379
Arg [12] : 0000000000000000000000001f279e2fd9c4febd6f2b009b627441aa7639ce88
Arg [13] : 0000000000000000000000000d856684de729e48f267e6264fd969c2c749a3f4
Arg [14] : 0000000000000000000000008a67dcee6d279005e0f8ddab574078091277cc4e
Arg [15] : 0000000000000000000000003069f27064ffb11a960413fbe7d0742185f53519
Arg [16] : 000000000000000000000000c2e4fa661fc810b4805df91427fdda2f69dedd57
Arg [17] : 0000000000000000000000001e1577ba4dc74007ed6e8fb3d3ce2ce5477f531d
Arg [18] : 00000000000000000000000077d3a7110bc847b1942d115f3eb9d1fa15032787
Arg [19] : 000000000000000000000000c209ae58076e311b51f10b80fc070f4d2425c0d3
Arg [20] : 00000000000000000000000000000000000000000000000000000000000009c4
Arg [21] : 0000000000000000000000000000000000000000000000000000000000000fa0
Arg [22] : 00000000000000000000000000000000000000000000000000000000000003e8
Arg [23] : 00000000000000000000000000000000000000000000000000000000000009c4
Arg [24] : 0000000000000000000000000000000000000000000000000000000000000064
Arg [25] : 0000000000000000000000000000000000000000000000000000000000000008
Arg [26] : 617262697472756d000000000000000000000000000000000000000000000000


Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading

A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.