ETH Price: $4,004.24 (-2.78%)

Contract

0x00000000A78E278b2d2e2935FaeBe19ee9F1FF14
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

More Info

Private Name Tags

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
Configure Token ...236772382025-10-28 16:54:3517 hrs ago1761670475IN
0x00000000...ee9F1FF14
0 ETH0.000266722.30584311
Configure Token ...236633832025-10-26 18:20:352 days ago1761502835IN
0x00000000...ee9F1FF14
0 ETH0.000020660.2113356
Configure Token ...236617632025-10-26 12:54:232 days ago1761483263IN
0x00000000...ee9F1FF14
0 ETH0.000015560.25610739
Configure Token ...236558402025-10-25 17:03:113 days ago1761411791IN
0x00000000...ee9F1FF14
0 ETH0.00020322.07832255
Configure Token ...236506962025-10-24 23:47:474 days ago1761349667IN
0x00000000...ee9F1FF14
0 ETH0.000011820.19449939
Configure Token ...236415082025-10-23 16:52:235 days ago1761238343IN
0x00000000...ee9F1FF14
0 ETH0.000034270.35054994
Configure Token ...236404882025-10-23 13:25:595 days ago1761225959IN
0x00000000...ee9F1FF14
0 ETH0.000016730.27541583
Configure Token ...236334562025-10-22 13:46:596 days ago1761140819IN
0x00000000...ee9F1FF14
0 ETH0.000020540.21012493
Configure Token ...236303522025-10-22 3:20:237 days ago1761103223IN
0x00000000...ee9F1FF14
0 ETH0.000097661.60698771
Configure Token ...236213102025-10-20 20:53:118 days ago1760993591IN
0x00000000...ee9F1FF14
0 ETH0.000012020.20747932
Configure Token ...236212802025-10-20 20:47:118 days ago1760993231IN
0x00000000...ee9F1FF14
0 ETH0.000012360.20346625
Configure Token ...236143362025-10-19 21:23:119 days ago1760908991IN
0x00000000...ee9F1FF14
0 ETH0.000157671.61265583
Configure Token ...236005432025-10-17 23:03:5911 days ago1760742239IN
0x00000000...ee9F1FF14
0 ETH0.000097581.60575211
Configure Token ...235928632025-10-16 21:19:3512 days ago1760649575IN
0x00000000...ee9F1FF14
0 ETH0.00002740.43600626
Configure Token ...235862782025-10-15 23:10:2313 days ago1760569823IN
0x00000000...ee9F1FF14
0 ETH0.000174721.68127191
Configure Token ...235861762025-10-15 22:49:5913 days ago1760568599IN
0x00000000...ee9F1FF14
0 ETH0.000102051.6785841
Configure Token ...235852622025-10-15 19:45:5913 days ago1760557559IN
0x00000000...ee9F1FF14
0 ETH0.000107991.77693097
Configure Token ...235841862025-10-15 16:09:2313 days ago1760544563IN
0x00000000...ee9F1FF14
0 ETH0.000358783.45241307
Configure Token ...235838452025-10-15 15:00:2313 days ago1760540423IN
0x00000000...ee9F1FF14
0 ETH0.000225713.71390105
Configure Token ...235816542025-10-15 7:39:2314 days ago1760513963IN
0x00000000...ee9F1FF14
0 ETH0.000103691.7058692
Configure Token ...235816362025-10-15 7:35:4714 days ago1760513747IN
0x00000000...ee9F1FF14
0 ETH0.000102941.6935979
Configure Token ...235813312025-10-15 6:34:1114 days ago1760510051IN
0x00000000...ee9F1FF14
0 ETH0.000158251.61853827
Configure Token ...235808902025-10-15 5:04:3514 days ago1760504675IN
0x00000000...ee9F1FF14
0 ETH0.000098841.6261049
Configure Token ...235798422025-10-15 1:33:2314 days ago1760492003IN
0x00000000...ee9F1FF14
0 ETH0.000158891.62506507
Configure Token ...235798142025-10-15 1:27:4714 days ago1760491667IN
0x00000000...ee9F1FF14
0 ETH0.000158741.62357612
View all transactions

Latest 9 internal transactions

Advanced mode:
Parent Transaction Hash Method Block
From
To
0x600b5981235156422025-10-06 2:06:4723 days ago1759716407
0x00000000...ee9F1FF14
 Contract Creation0 ETH
0x600b5981233705092025-09-15 19:17:2343 days ago1757963843
0x00000000...ee9F1FF14
 Contract Creation0 ETH
0x600b5981231253732025-08-12 13:50:3577 days ago1755006635
0x00000000...ee9F1FF14
 Contract Creation0 ETH
0x600b5981227820642025-06-25 14:31:59125 days ago1750861919
0x00000000...ee9F1FF14
 Contract Creation0 ETH
0x600b5981227820642025-06-25 14:31:59125 days ago1750861919
0x00000000...ee9F1FF14
 Contract Creation0 ETH
0x600b5981227555862025-06-21 21:42:23129 days ago1750542143
0x00000000...ee9F1FF14
 Contract Creation0 ETH
0x600b5981227555862025-06-21 21:42:23129 days ago1750542143
0x00000000...ee9F1FF14
 Contract Creation0 ETH
0x600b5981227555862025-06-21 21:42:23129 days ago1750542143
0x00000000...ee9F1FF14
 Contract Creation0 ETH
0x60a06040224202732025-05-05 21:40:11176 days ago1746481211  Contract Creation0 ETH
Cross-Chain Transactions

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

Validator Index Block Amount
View All Withdrawals

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

Contract Source Code Verified (Exact Match)

Contract Name:
PMPV0

Compiler Version
v0.8.22+commit.4fc1097e

Optimization Enabled:
Yes with 10 runs

Other Settings:
paris EvmVersion
// SPDX-License-Identifier: LGPL-3.0-only
// Created By: Art Blocks Inc.

pragma solidity 0.8.22;

import {IWeb3Call} from "../interfaces/v0.8.x/IWeb3Call.sol";
import {IPMPV0} from "../interfaces/v0.8.x/IPMPV0.sol";
import {IPMPConfigureHook} from "../interfaces/v0.8.x/IPMPConfigureHook.sol";
import {IPMPAugmentHook} from "../interfaces/v0.8.x/IPMPAugmentHook.sol";
import {IGenArt721CoreContractV3_Base} from "../interfaces/v0.8.x/IGenArt721CoreContractV3_Base.sol";
import {IERC721} from "@openzeppelin-5.0/contracts/token/ERC721/IERC721.sol";
import {IDelegateRegistry} from "../interfaces/v0.8.x/IDelegateRegistry.sol";

import {ERC165Checker} from "@openzeppelin-5.0/contracts/utils/introspection/ERC165Checker.sol";
import {Strings} from "@openzeppelin-5.0/contracts/utils/Strings.sol";
import {ReentrancyGuard} from "@openzeppelin-5.0/contracts/utils/ReentrancyGuard.sol";

import {Web3Call} from "./Web3Call.sol";
import {ImmutableStringArray} from "../libs/v0.8.x/ImmutableStringArray.sol";
import {ABHelpers} from "../libs/v0.8.x/ABHelpers.sol";

/**
 * @title Project Metadata Parameters (PMP) contract, V0
 * @author Art Blocks Inc.
 * @notice This contract enables Artists to define and configure project parameters that token
 * owners can set within constraints. This provides a standardized way for projects to expose
 * configurable parameters that can be used by renderers and other contracts.
 * WARNING: This contract implements an open protocol for parameter configuration, and does not
 * restrict usage to Art Blocks or its affiliates. Use with caution. The contract does assume
 * that the core contract conforms to the IGenArt721CoreContractV3_Base interface for authentication,
 * and that the core contract is using the ERC721 standard for token management. These assumptions
 * may not hold for all core contracts, especially those outside of the Art Blocks ecosystem.
 * @notice Artists may configure arbitrary external hooks for post-token-configuration and
 * read-augmentation. These hooks are executed at the end of the token configuration process
 * and when reading token PMPs, respectively. These hooks are validated for ERC165 interface
 * compatibility, but have the ability to execute arbitrary code at the discretion of the artist.
 * The contract implements reentrancy guards to protect against reentrancy attacks during hook calls,
 * resulting in the subcall having a similar level as a minting contract sending funds to an artist's
 * additional payee address during a token sale. Use appropriate discretion.
 * WARNING: Hook calls are not validated, and may cause unexpected behavior. These include:
 * - Hooks may revert the transaction, resulting in denial of service
 * - Hooks may augment the token's returned parameters with unintended data, resulting in
 *   unexpected behavior
 * The artist is solely responsible for configuring hooks and validating their behavior.
 * @dev This contract implements the IWeb3Call and IPMPV0 interfaces, providing functionality
 * for parameter configuration and retrieval. It includes support for various parameter types,
 * authorization options, and hooks for extending functionality.
 */
contract PMPV0 is IPMPV0, Web3Call, ReentrancyGuard {
    using Strings for string;
    using Strings for uint256;
    using Strings for int256;
    using ImmutableStringArray for ImmutableStringArray.StringArray;

    IDelegateRegistry public immutable delegateRegistry;
    bytes32 public constant DELEGATION_REGISTRY_TOKEN_OWNER_RIGHTS =
        bytes32("postmintparameters");

    bytes32 private constant _TYPE = "PMPV0";

    bytes16 private constant _HEX_DIGITS = "0123456789abcdef";
    uint256 private constant _DECIMAL_PRECISION_DIGITS = 10;
    uint256 private constant _DECIMAL_PRECISION =
        10 ** _DECIMAL_PRECISION_DIGITS;

    // @dev min hex color assumed to be 0x000000
    // @dev intentionally not including alpha channel, can be separate PMP if desired
    uint256 private constant _HEX_COLOR_MAX = 0xFFFFFF;

    uint256 private constant _TIMESTAMP_MIN = 0; // @dev unix timestamp, 0 = 1970-01-01
    uint256 private constant _TIMESTAMP_MAX = type(uint64).max; // max guardrail, ~10 billion years

    /**
     * @notice Storage structure for parameter configuration.
     * @dev Includes additional field highestConfigNonce compared to PMPConfig.
     */
    struct PMPConfigStorage {
        // @dev highest config nonce for which this PMPConfig is valid (relative to projectConfig.configNonce)
        uint8 highestConfigNonce; // slot 0: 1 byte
        AuthOption authOption; // slot 0: 1 byte
        ParamType paramType; // slot 0: 1 byte
        uint48 pmpLockedAfterTimestamp; // slot 0: 6 bytes // @dev uint48 is sufficient to store ~2^48 seconds, ~8,900 years
        address authAddress; // slot 0: 20 bytes
        // @dev store array length as uint8 in slot 0 for SLOAD efficiency during token configuration
        uint8 selectOptionsLength; // slot 0: 1 byte
        // @dev use immutable string array for storage efficiency during project configuration
        ImmutableStringArray.StringArray selectOptions; // slot 1: 32 bytes
        bytes32 minRange; // slot 2: 32 bytes
        bytes32 maxRange; // slot 3: 32 bytes
    }

    // @dev core contract address and projectId are implicit based on mapping pointing to ProjectConfig struct
    struct ProjectConfig {
        // @dev array of pmpKeys for efficient enumeration, uses efficient SSTORE2 storage
        ImmutableStringArray.StringArray pmpKeys; // slot 0: 32 bytes
        // @dev mapping of pmpKeys to PMPConfigStorage for O(1) access, and cheap updates when no changes
        mapping(bytes32 pmpKeyHash => PMPConfigStorage pmpConfigStorage) pmpConfigsStorage; // slot 1: 32 bytes
        // config nonce that is incremented during each configureProject call
        uint8 configNonce; // slot 2: 1 byte
        // post-configuration hook to be called after a token's PMP is configured
        IPMPConfigureHook tokenPMPPostConfigHook; // slot 2: 20 bytes
        // token pmp read augmentation hook to be called when reading a token's PMPs
        IPMPAugmentHook tokenPMPReadAugmentationHook; // slot 3: 20 bytes
    }

    // mapping of ProjectConfig structs for each project
    mapping(address coreContract => mapping(uint256 projectId => ProjectConfig projectConfig)) projectConfigs;

    // mapping of PMP structs for each token
    mapping(address coreContract => mapping(uint256 tokenId => mapping(bytes32 pmpKeyHash => PMPStorage pmp))) tokenPMPs;

    /**
     * @notice Constructor for PMPV0 contract.
     * @param delegateRegistry_ The address of the delegate registry contract. Intended to be
     * the delegate.xyz v2 contract.
     */
    constructor(IDelegateRegistry delegateRegistry_) {
        delegateRegistry = delegateRegistry_;
        emit DelegationRegistryUpdated(address(delegateRegistry_));
    }

    /**
     * @notice Configure project hooks for post-configuration and read augmentation.
     * WARNING: Hook calls may revert the transaction, and may cause unexpected behavior.
     * The artist is solely responsible for configuring hooks and validating their behavior.
     * Improper configuration or hooks with unexpected behavior may result in denial of service,
     * unexpected behavior, or other issues.
     * @param coreContract The address of the core contract.
     * @param projectId The project ID to configure hooks for.
     * @param tokenPMPPostConfigHook The hook to call after a token's PMP is configured.
     * @param tokenPMPReadAugmentationHook The hook to call when reading a token's PMPs.
     * @dev Only the project artist can configure project hooks.
     * @dev Both hooks are validated for ERC165 interface compatibility.
     * @dev Uses nonReentrant modifier to prevent reentrancy attacks during hook calls or auth checks.
     */
    function configureProjectHooks(
        address coreContract,
        uint256 projectId,
        IPMPConfigureHook tokenPMPPostConfigHook,
        IPMPAugmentHook tokenPMPReadAugmentationHook
    ) external nonReentrant {
        // only artists may configure project hooks
        _onlyArtist({
            coreContract: coreContract,
            projectId: projectId,
            sender: msg.sender
        });
        // validation - check for ERC165 implementation for non-null hooks
        if (address(tokenPMPPostConfigHook) != address(0)) {
            // use ERC165 checker to validate implementation
            require(
                ERC165Checker.supportsInterface({
                    account: address(tokenPMPPostConfigHook),
                    interfaceId: type(IPMPConfigureHook).interfaceId
                }),
                "PMP: tokenPMPPostConfigHook does not implement IPMPConfigureHook"
            );
        }
        if (address(tokenPMPReadAugmentationHook) != address(0)) {
            require(
                ERC165Checker.supportsInterface({
                    account: address(tokenPMPReadAugmentationHook),
                    interfaceId: type(IPMPAugmentHook).interfaceId
                }),
                "PMP: tokenPMPReadAugmentationHook does not implement IPMPAugmentHook"
            );
        }
        // update projectConfig
        ProjectConfig storage projectConfig = projectConfigs[coreContract][
            projectId
        ];
        projectConfig.tokenPMPPostConfigHook = tokenPMPPostConfigHook;
        projectConfig
            .tokenPMPReadAugmentationHook = tokenPMPReadAugmentationHook;

        // emit event
        emit ProjectHooksConfigured({
            coreContract: coreContract,
            projectId: projectId,
            tokenPMPPostConfigHook: tokenPMPPostConfigHook,
            tokenPMPReadAugmentationHook: tokenPMPReadAugmentationHook
        });
    }

    /**
     * @notice Configure the available parameters for a project and their constraints.
     * @param coreContract The address of the core contract.
     * @param projectId The project ID to configure parameters for.
     * @param pmpInputConfigs Array of parameter configurations defining the available parameters.
     * @dev Only the project artist can configure project parameters.
     * @dev Each configuration is validated for proper parameter type and constraints.
     * @dev The project's configuration nonce is incremented with each call.
     * @dev Only <= 256 configs are supported.
     * @dev Only <= 255 bytes are supported for pmpKeys.
     * @dev nonReentrant due to auth check that requires interaction.
     */
    function configureProject(
        address coreContract,
        uint256 projectId,
        PMPInputConfig[] calldata pmpInputConfigs
    ) external nonReentrant {
        // only artists may configure projects
        _onlyArtist({
            coreContract: coreContract,
            projectId: projectId,
            sender: msg.sender
        });
        // validate pmpInputConfigs
        uint256 pmpInputConfigsLength = pmpInputConfigs.length;
        // @dev no coverage on else branch due to test complexity
        require(pmpInputConfigsLength <= 256, "PMP: Only <= 256 configs");
        for (uint256 i = 0; i < pmpInputConfigsLength; i++) {
            uint256 keyLengthBytes = bytes(pmpInputConfigs[i].key).length;
            // @dev max key length constraint is a reasonable gas guardrail
            require(
                keyLengthBytes > 0 && keyLengthBytes < 256,
                "PMP: pmpKey cannot be empty or exceed 255 bytes"
            );
            _validatePMPConfig(pmpInputConfigs[i].pmpConfig);
        }
        // store pmpInputConfigs data in ProjectConfig struct
        // @dev load projectConfig storage pointer
        ProjectConfig storage projectConfig = projectConfigs[coreContract][
            projectId
        ];
        // increment config nonce
        // @dev solidity ^0.8 reverts on overflow (greater than 255 edits not supported)
        uint8 newConfigNonce = projectConfig.configNonce + 1;
        projectConfig.configNonce = newConfigNonce;

        // efficiently sync pmp keys to ProjectConfig
        // copy input pmp keys to memory array for efficient passing to _syncPMPKeys
        string[] memory pmpKeys = new string[](pmpInputConfigsLength);
        for (uint256 i = 0; i < pmpInputConfigsLength; i++) {
            pmpKeys[i] = pmpInputConfigs[i].key;
        }
        _syncPMPKeys({inputKeys: pmpKeys, projectConfig: projectConfig});
        // store pmp configs in ProjectConfig struct's mapping
        for (uint256 i = 0; i < pmpInputConfigsLength; i++) {
            // store pmpConfigStorage in ProjectConfig struct's mapping
            PMPConfigStorage storage pmpConfigStorage = projectConfig
                .pmpConfigsStorage[_getStringHash(pmpKeys[i])];
            {
                // validate that any current pmp at this key is not locked
                uint256 currentPPMLockedAfterTimestamp = pmpConfigStorage
                    .pmpLockedAfterTimestamp;
                require(
                    currentPPMLockedAfterTimestamp == 0 ||
                        currentPPMLockedAfterTimestamp > block.timestamp,
                    "PMP: pmp is locked and cannot be updated"
                );
            }
            // update highestConfigNonce
            pmpConfigStorage.highestConfigNonce = newConfigNonce;
            // copy function input pmpConfig data to pmpConfigStorage
            PMPConfig memory inputPMPConfig = pmpInputConfigs[i].pmpConfig;
            pmpConfigStorage.authOption = inputPMPConfig.authOption;
            pmpConfigStorage.paramType = inputPMPConfig.paramType;
            pmpConfigStorage.pmpLockedAfterTimestamp = inputPMPConfig
                .pmpLockedAfterTimestamp;
            pmpConfigStorage.authAddress = inputPMPConfig.authAddress;
            // @dev length already validated <= 255 in _validatePMPConfig, safe to cast unchecked
            pmpConfigStorage.selectOptionsLength = uint8(
                inputPMPConfig.selectOptions.length
            );
            // @dev ImmutableStringArray is optimized for the case where the array is empty.
            ImmutableStringArray.store(
                pmpConfigStorage.selectOptions,
                inputPMPConfig.selectOptions
            );
            pmpConfigStorage.minRange = inputPMPConfig.minRange;
            pmpConfigStorage.maxRange = inputPMPConfig.maxRange;
        }

        // emit event
        emit ProjectConfigured({
            coreContract: coreContract,
            projectId: projectId,
            pmpInputConfigs: pmpInputConfigs,
            projectConfigNonce: newConfigNonce
        });
    }

    /**
     * @notice Configure the parameters for a specific token according to project constraints.
     * WARNING: This contract represents an open protocol for parameter configuration, and does not
     * restrict usage to Art Blocks or its affiliates. Use with caution. The contract does assume
     * that the core contract conforms to the IGenArt721CoreContractV3_Base interface for authentication,
     * and that the core contract is using the ERC721 standard for token management. These assumptions
     * may not hold for all core contracts, especially those outside of the Art Blocks ecosystem.
     * WARNING: Hook calls may revert the transaction, and may cause unexpected behavior.
     * The artist is solely responsible for configuring hooks and validating their behavior.
     * Improper configuration or hooks with unexpected behavior may result in denial of service,
     * unexpected behavior, or other issues.
     * ERC-721 Token-level wallet delegation for the TokenOwner role is supported via delegate.xyz v2.
     * For opt-in granular control of rights specific to these operations, vault owners may define subdelegations
     * with bytes32 rights "postmintparameters". Per the delegate.xyz v2 specification, delegations made with the
     * empty string "" will be interpreted as a full delegation of all rights.
     * @param coreContract The address of the core contract.
     * @param tokenId The tokenId of the token to configure.
     * @param pmpInputs The parameter inputs to configure for the token.
     * @dev Validates each parameter input against the project's configuration.
     * @dev Stores the configured parameters for the token.
     * @dev Calls the post-configuration hook if one is configured for the project.
     * @dev Uses nonReentrant modifier to prevent reentrancy attacks during hook calls or auth checks.
     */
    function configureTokenParams(
        address coreContract,
        uint256 tokenId,
        PMPInput[] calldata pmpInputs
    ) external nonReentrant {
        ProjectConfig storage projectConfig = projectConfigs[coreContract][
            ABHelpers.tokenIdToProjectId(tokenId)
        ];
        // preallocate memory for auth addresses of each pmpInput
        address[] memory authAddresses = new address[](pmpInputs.length);
        // assign each pmpInput to the token
        // @dev pmpInputs processed sequentially in order of input
        for (uint256 i = 0; i < pmpInputs.length; i++) {
            bytes32 pmpKeyHash = _getStringHash(pmpInputs[i].key);
            PMPInput memory pmpInput = pmpInputs[i];
            PMPStorage storage tokenPMP = tokenPMPs[coreContract][tokenId][
                pmpKeyHash
            ];
            PMPConfigStorage storage pmpConfigStorage = projectConfig
                .pmpConfigsStorage[pmpKeyHash];
            // validate pmpInput + record the authenticated address used to configure the pmpInput
            authAddresses[i] = _validatePMPInputAndAuth({
                tokenId: tokenId,
                coreContract: coreContract,
                pmpInput: pmpInput,
                pmpConfigStorage: pmpConfigStorage,
                projectConfigNonce: projectConfig.configNonce
            });
            // store pmpInput data in PMPStorage struct
            tokenPMP.configuredParamType = pmpConfigStorage.paramType;
            // only assign the value that affects param type (gas savings)
            if (pmpConfigStorage.paramType == ParamType.String) {
                if (pmpInput.configuringArtistString) {
                    tokenPMP.artistConfiguredValueString = pmpInput
                        .configuredValueString;
                } else {
                    tokenPMP.nonArtistConfiguredValueString = pmpInput
                        .configuredValueString;
                }
            } else {
                tokenPMP.configuredValue = pmpInput.configuredValue;
            }
            // call post-config hook if configured for the project
            // @dev this function is nonreentrant for additional reentrancy protection
            // @dev intentionally revert entire transaction if hook reverts to prevent latent failure
            if (address(projectConfig.tokenPMPPostConfigHook) != address(0)) {
                projectConfig.tokenPMPPostConfigHook.onTokenPMPConfigure({
                    coreContract: coreContract,
                    tokenId: tokenId,
                    pmpInput: pmpInput
                });
            }
        }

        // emit event
        emit TokenParamsConfigured({
            coreContract: coreContract,
            tokenId: tokenId,
            pmpInputs: pmpInputs,
            authAddresses: authAddresses
        });
    }

    /**
     * @notice Get the token parameters for a given token.
     * If none are configured, the tokenParams should be empty.
     * WARNING: Hook calls may revert the transaction, and may cause unexpected behavior.
     * The artist is solely responsible for configuring hooks and validating their behavior.
     * Improper configuration or hooks with unexpected behavior may result in denial of service,
     * unexpected behavior, or other issues.
     * @param coreContract The address of the core contract to call.
     * @param tokenId The tokenId of the token to get data for.
     * @return tokenParams An array of token parameters for the queried token.
     */
    function getTokenParams(
        address coreContract,
        uint256 tokenId
    )
        external
        view
        override
        returns (IWeb3Call.TokenParam[] memory tokenParams)
    {
        uint256 projectId = ABHelpers.tokenIdToProjectId(tokenId);
        ProjectConfig storage projectConfig = projectConfigs[coreContract][
            projectId
        ];
        string[] memory pmpKeys = projectConfig.pmpKeys.getAll();
        uint256 pmpKeysLength = pmpKeys.length;
        // @dev initialize tokenParams array with maximum possible length
        tokenParams = new IWeb3Call.TokenParam[](pmpKeysLength);
        uint256 populatedParamsIndex = 0; // @dev index of next populated tokenParam
        for (uint256 i = 0; i < pmpKeysLength; i++) {
            // load pmp value
            bytes32 pmpKeyHash = _getStringHash(pmpKeys[i]);
            (bool isConfigured, string memory value) = _getPMPValue({
                pmpConfigStorage: projectConfig.pmpConfigsStorage[pmpKeyHash],
                pmp: tokenPMPs[coreContract][tokenId][pmpKeyHash]
            });
            // continue when param is unconfigured
            if (!isConfigured) {
                // continue without incrementing populatedParamsIndex
                continue;
            }
            // append configured to tokenParams array
            tokenParams[populatedParamsIndex] = IWeb3Call.TokenParam({
                key: pmpKeys[i],
                value: value
            });
            populatedParamsIndex++;
        }
        // truncate tokenParams array to populatedParamsIndex via assembly
        assembly {
            // directly modify the memory array length in memory
            mstore(tokenParams, populatedParamsIndex)
        }

        // call augmentation hook if configured for the project
        if (address(projectConfig.tokenPMPReadAugmentationHook) != address(0)) {
            // assign return value to the augmented tokenParams
            // @dev executed in read-only context, artist-configured hook contract (not entirely arbitrary)
            tokenParams = projectConfig
                .tokenPMPReadAugmentationHook
                .onTokenPMPReadAugmentation({
                    coreContract: coreContract,
                    tokenId: tokenId,
                    tokenParams: tokenParams
                });
        }

        // @dev implicitly returns tokenParams
    }

    // ---- introspection view functions ----

    /**
     * @notice Get the project config for a given project.
     * @param coreContract The address of the core contract to call.
     * @param projectId The projectId of the project to get data for.
     * @return pmpKeys The configured pmpKeys for the project.
     * @return configNonce The config nonce for the project.
     * @return tokenPMPPostConfigHook The tokenPMPPostConfigHook for the project.
     * @return tokenPMPReadAugmentationHook The tokenPMPReadAugmentationHook for the project.
     */
    function getProjectConfig(
        address coreContract,
        uint256 projectId
    )
        external
        view
        returns (
            string[] memory pmpKeys,
            uint8 configNonce,
            IPMPConfigureHook tokenPMPPostConfigHook,
            IPMPAugmentHook tokenPMPReadAugmentationHook
        )
    {
        ProjectConfig storage projectConfig = projectConfigs[coreContract][
            projectId
        ];
        // @dev edge case - uninitialized pmpKeys - empty array returned by getAll()
        pmpKeys = projectConfig.pmpKeys.getAll();
        configNonce = projectConfig.configNonce;
        tokenPMPPostConfigHook = projectConfig.tokenPMPPostConfigHook;
        tokenPMPReadAugmentationHook = projectConfig
            .tokenPMPReadAugmentationHook;
    }

    /**
     * @notice Checks if the given wallet has the owner role for the given token.
     * It returns true if the wallet is the owner of the token or if the wallet
     * is a delegate of the token owner; otherwise it returns false.
     * Reverts if an invalid coreContract or tokenId is provided.
     * Provided for convenience, as the same check is performed in the
     * configureTokenParams function.
     * @param wallet The wallet address to check.
     * @param coreContract The address of the core contract to call.
     * @param tokenId The tokenId of the token to check.
     * @return isTokenOwnerOrDelegate_ True if the wallet is the owner or a delegate of the token,
     * false otherwise.
     */
    function isTokenOwnerOrDelegate(
        address wallet,
        address coreContract,
        uint256 tokenId
    ) external view returns (bool isTokenOwnerOrDelegate_) {
        (isTokenOwnerOrDelegate_, ) = _isTokenOwnerOrDelegate({
            tokenId: tokenId,
            coreContract: coreContract,
            sender: wallet
        });
    }

    /**
     * @notice Get the PMP config from storage for a given project and pmpKey.
     * @dev Returns the storage values, even if unconfigured or not part of the
     * active project config. Check latestConfigNonce to verify if the pmpKey
     * is part of the active project config.
     * @dev any populated select options are loaded and returned as a string array
     * @param coreContract The address of the core contract to call.
     * @param projectId The projectId of the project to get data for.
     * @param pmpKey The pmpKey of the pmp to get data for.
     * @return pmpConfigView The PMP config for the given project and pmpKey.
     */
    function getProjectPMPConfig(
        address coreContract,
        uint256 projectId,
        string memory pmpKey
    ) external view returns (PMPConfigView memory pmpConfigView) {
        PMPConfigStorage storage pmpConfigStorage = projectConfigs[
            coreContract
        ][projectId].pmpConfigsStorage[_getStringHash(pmpKey)];
        // load values from storage
        pmpConfigView.highestConfigNonce = pmpConfigStorage.highestConfigNonce;
        pmpConfigView.authOption = pmpConfigStorage.authOption;
        pmpConfigView.paramType = pmpConfigStorage.paramType;
        pmpConfigView.pmpLockedAfterTimestamp = pmpConfigStorage
            .pmpLockedAfterTimestamp;
        pmpConfigView.authAddress = pmpConfigStorage.authAddress;
        pmpConfigView.selectOptionsLength = pmpConfigStorage
            .selectOptionsLength;
        pmpConfigView.selectOptions = pmpConfigStorage.selectOptions.getAll();
        pmpConfigView.minRange = pmpConfigStorage.minRange;
        pmpConfigView.maxRange = pmpConfigStorage.maxRange;
    }

    /**
     * @notice Get the PMP storage for a given token and pmpKey.
     * Returns the PMP storage struct for the queried token and pmpKey. The storage
     * may be stale if the token has been reconfigured, or empty if never configured.
     * @param coreContract The address of the core contract to call.
     * @param tokenId The tokenId of the token to get data for.
     * @param pmpKey The pmpKey of the pmp to get data for.
     * @return pmp The PMP storage for the given token and pmpKey.
     */
    function getTokenPMPStorage(
        address coreContract,
        uint256 tokenId,
        string memory pmpKey
    ) external view returns (PMPStorage memory pmp) {
        pmp = tokenPMPs[coreContract][tokenId][_getStringHash(pmpKey)];
    }

    /**
     * @notice Synchronizes the project's parameter keys with the provided input keys.
     * @dev Only updates the storage if the keys have changed to optimize gas usage.
     * @param inputKeys The new parameter keys to store.
     * @param projectConfig The project configuration storage reference.
     */
    function _syncPMPKeys(
        string[] memory inputKeys,
        ProjectConfig storage projectConfig
    ) internal {
        string[] memory currentKeys = projectConfig.pmpKeys.getAll();
        // determine if inputKeys are different from currentKeys to avoid expensive operation
        // @dev in general, we don't expect many changes to pmpKeys after initial configuration
        bool keysChanged = false;
        if (inputKeys.length != currentKeys.length) {
            keysChanged = true;
        } else {
            for (uint256 i = 0; i < inputKeys.length; i++) {
                if (!inputKeys[i].equal(currentKeys[i])) {
                    keysChanged = true;
                    break;
                }
            }
        }
        // if keys changed, update projectConfig's pmpKeys (expensive operation)
        if (keysChanged) {
            ImmutableStringArray.store(projectConfig.pmpKeys, inputKeys);
        }
    }

    /**
     * @notice Validates a PMP configuration. Ensures arbitrary input is valid and intentional.
     * @dev Verifies parameter types, authorization options, and constraints.
     * @param pmpConfig The PMP configuration to validate.
     */
    function _validatePMPConfig(PMPConfig calldata pmpConfig) internal view {
        // memoize paramType and authOption
        ParamType paramType = pmpConfig.paramType;
        AuthOption authOption = pmpConfig.authOption;
        // require type is not unconfigured
        require(
            paramType != ParamType.Unconfigured,
            "PMP: paramType is unconfigured"
        );
        // validate locked after timestamp is in the future, or is zero (unlimited)
        require(
            pmpConfig.pmpLockedAfterTimestamp == 0 ||
                pmpConfig.pmpLockedAfterTimestamp > block.timestamp,
            "PMP: pmpLockedAfterTimestamp is in the past and not unlimited (zero)"
        );
        // validate enums are within bounds
        // @dev no coverage else branch, this is redundant as used due to solidity compiler checks on function enum inputs
        require(
            authOption <= AuthOption.ArtistAndTokenOwnerAndAddress,
            "PMP: Invalid authOption"
        );
        // @dev no coverage else branch, this is redundant as used due to solidity compiler checks on function enum inputs
        require(paramType <= ParamType.String, "PMP: Invalid paramType");
        // only artist+ authentication types are supported for string params
        if (paramType == ParamType.String) {
            require(
                authOption == AuthOption.Artist ||
                    authOption == AuthOption.ArtistAndTokenOwner ||
                    authOption == AuthOption.ArtistAndTokenOwnerAndAddress ||
                    authOption == AuthOption.ArtistAndAddress,
                "PMP: String params must have artist+ authentication"
            );
        }
        // validate auth with address has non-zero auth address
        if (
            authOption == AuthOption.Address ||
            authOption == AuthOption.TokenOwnerAndAddress ||
            authOption == AuthOption.ArtistAndAddress ||
            authOption == AuthOption.ArtistAndTokenOwnerAndAddress
        ) {
            require(
                pmpConfig.authAddress != address(0),
                "PMP: authAddress is zero"
            );
        } else {
            // auth address must be zero for any non-address auth option
            require(
                pmpConfig.authAddress == address(0),
                "PMP: authAddress is not zero"
            );
        }
        // validate appropriate fields are empty
        if (
            paramType == ParamType.Bool ||
            paramType == ParamType.String ||
            paramType == ParamType.HexColor
        ) {
            // @dev should have all fields empty
            require(
                pmpConfig.selectOptions.length == 0,
                "PMP: selectOptions is not empty"
            );
            // @dev min/max range for hex color checked during assignment, should be empty in config
            require(pmpConfig.minRange == 0, "PMP: minRange is not empty");
            require(pmpConfig.maxRange == 0, "PMP: maxRange is not empty");
        } else if (paramType == ParamType.Select) {
            // @dev select should have selectOptions and empty min/max range values
            require(
                pmpConfig.selectOptions.length > 0,
                "PMP: selectOptions is empty"
            );
            require(
                pmpConfig.selectOptions.length < 256,
                "PMP: selectOptions length > 255"
            );
            // @dev do not check if options are unique on-chain
            // require min/max range values are empty
            require(pmpConfig.minRange == 0, "PMP: minRange is not empty");
            require(pmpConfig.maxRange == 0, "PMP: maxRange is not empty");
        } else if (
            paramType == ParamType.Uint256Range ||
            paramType == ParamType.Int256Range ||
            paramType == ParamType.DecimalRange ||
            paramType == ParamType.Timestamp
        ) {
            // @dev range params should have empty selectOptions
            require(
                pmpConfig.selectOptions.length == 0,
                "PMP: selectOptions is not empty"
            );
            // require minRange is less than maxRange
            if (paramType == ParamType.Int256Range) {
                // cast minRange and maxRange to int256
                int256 minRange = int256(uint256(pmpConfig.minRange));
                int256 maxRange = int256(uint256(pmpConfig.maxRange));
                require(minRange < maxRange, "PMP: minRange >= maxRange");
            } else {
                // cast minRange and maxRange to uint256
                // @dev lt works for uint256, decimal, or timestamp types cast as uint256
                uint256 minRange = uint256(pmpConfig.minRange);
                uint256 maxRange = uint256(pmpConfig.maxRange);
                require(minRange < maxRange, "PMP: minRange >= maxRange");
                // additional guardrails on timestamp range
                if (paramType == ParamType.Timestamp) {
                    // require maxRange is not gt _TIMESTAMP_MAX
                    require(
                        maxRange <= _TIMESTAMP_MAX,
                        "PMP: maxRange > _TIMESTAMP_MAX"
                    );
                }
            }
        } else {
            // @dev should never reach, no coverage
            revert("PMP: Invalid paramType");
        }
    }

    /**
     * @notice Validates a PMP input against the project's configuration.
     * Includes auth checks based on the pmpKey's current authOption.
     * @dev Checks authorization, parameter type consistency, and value constraints.
     * @dev Checks that pmp param is included in most recently configured PMP config for token's project.
     * @param tokenId The token ID for which the parameter is being configured.
     * @param coreContract The address of the core contract.
     * @param pmpInput The parameter input to validate.
     * @param pmpConfigStorage The project's configuration storage for this parameter.
     * @param projectConfigNonce The project's current configuration nonce.
     * @return permissionedAddress the address used to make the update, accounting for delegation.
     */
    function _validatePMPInputAndAuth(
        uint256 tokenId,
        address coreContract,
        PMPInput memory pmpInput,
        PMPConfigStorage storage pmpConfigStorage,
        uint8 projectConfigNonce
    ) internal view returns (address permissionedAddress) {
        // check that the param is part of the project's most recently configured PMP params
        // @dev use config nonce to check if param is part of most recently configured PMP params
        require(
            pmpConfigStorage.highestConfigNonce == projectConfigNonce,
            "PMP: param not part of most recently configured PMP params"
        );
        // check that the param type matches and is not unconfigured
        require(
            pmpInput.configuredParamType == pmpConfigStorage.paramType,
            "PMP: paramType mismatch"
        );
        // @dev checking value in memory for lower gas cost
        require(
            pmpInput.configuredParamType != ParamType.Unconfigured,
            "PMP: input paramType is unconfigured"
        );

        // ensure caller has appropriate auth
        bool isAuthenticated;
        {
            AuthOption authOption = pmpConfigStorage.authOption;
            // if artist may configure, check and authenticate if not already authenticated
            if (
                authOption == AuthOption.Artist ||
                authOption == AuthOption.ArtistAndAddress ||
                authOption == AuthOption.ArtistAndTokenOwner ||
                authOption == AuthOption.ArtistAndTokenOwnerAndAddress
            ) {
                bool isArtist;
                (isArtist, permissionedAddress) = _isArtist({
                    tokenId: tokenId,
                    coreContract: coreContract,
                    sender: msg.sender
                });
                isAuthenticated = isArtist;
            }
            // if address may configure, check and authenticate if not already authenticated
            if (
                !isAuthenticated &&
                (authOption == AuthOption.Address ||
                    authOption == AuthOption.TokenOwnerAndAddress ||
                    authOption == AuthOption.ArtistAndAddress ||
                    authOption == AuthOption.ArtistAndTokenOwnerAndAddress)
            ) {
                permissionedAddress = pmpConfigStorage.authAddress;
                isAuthenticated = permissionedAddress == msg.sender;
            }
            // if token owner or delegate may configure, check and authenticate
            // @dev check token ownerlast to enable pre-mint PMP configuration by non-token-owner
            if (
                !isAuthenticated &&
                (authOption == AuthOption.TokenOwner ||
                    authOption == AuthOption.ArtistAndTokenOwner ||
                    authOption == AuthOption.TokenOwnerAndAddress ||
                    authOption == AuthOption.ArtistAndTokenOwnerAndAddress)
            ) {
                bool isTokenOwnerOrDelegate_;
                (
                    isTokenOwnerOrDelegate_,
                    permissionedAddress
                ) = _isTokenOwnerOrDelegate({
                    tokenId: tokenId,
                    coreContract: coreContract,
                    sender: msg.sender
                });
                isAuthenticated = isTokenOwnerOrDelegate_;
            }
        }
        // if not authenticated, revert with appropriate auth error
        if (!isAuthenticated) {
            AuthOption authOption = pmpConfigStorage.authOption;
            if (authOption == AuthOption.Artist) {
                revert("PMP: artist auth required");
            } else if (authOption == AuthOption.TokenOwner) {
                revert("PMP: token owner auth required");
            } else if (authOption == AuthOption.ArtistAndTokenOwner) {
                revert("PMP: artist and token owner auth required");
            } else if (authOption == AuthOption.Address) {
                revert("PMP: address auth required");
            } else if (authOption == AuthOption.ArtistAndTokenOwnerAndAddress) {
                revert("PMP: artist and token owner and address auth required");
            } else if (authOption == AuthOption.ArtistAndAddress) {
                revert("PMP: artist and address auth required");
            } else if (authOption == AuthOption.TokenOwnerAndAddress) {
                revert("PMP: token owner and address auth required");
            } else {
                // @dev no coverage, this should never be reached
                revert("PMP: invalid authOption");
            }
        }
        // @dev auth check is complete

        // ensure properly configured value
        ParamType paramType = pmpConfigStorage.paramType;
        // range checks for non-string params
        if (paramType != ParamType.String) {
            if (paramType == ParamType.Select) {
                require(
                    uint256(pmpInput.configuredValue) <
                        pmpConfigStorage.selectOptionsLength,
                    "PMP: selectOptions index out of bounds"
                );
            } else if (paramType == ParamType.Bool) {
                require(
                    pmpInput.configuredValue == bytes32(0) ||
                        uint256(pmpInput.configuredValue) == 1,
                    "PMP: bool param value must be 0 or 1"
                );
            } else if (
                paramType == ParamType.Uint256Range ||
                paramType == ParamType.DecimalRange
            ) {
                require(
                    pmpInput.configuredValue >= pmpConfigStorage.minRange &&
                        pmpInput.configuredValue <= pmpConfigStorage.maxRange,
                    "PMP: param value out of bounds"
                );
            } else if (paramType == ParamType.Int256Range) {
                require(
                    int256(uint256(pmpInput.configuredValue)) >= // @dev ensure this converts as expected
                        int256(uint256(pmpConfigStorage.minRange)) &&
                        int256(uint256(pmpInput.configuredValue)) <=
                        int256(uint256(pmpConfigStorage.maxRange)),
                    "PMP: param value out of bounds"
                );
            } else if (paramType == ParamType.Timestamp) {
                require(
                    uint256(pmpInput.configuredValue) <= _TIMESTAMP_MAX &&
                        uint256(pmpInput.configuredValue) >= _TIMESTAMP_MIN, // @dev no coverage, this is redundant due to being assigned zero
                    "PMP: param value out of bounds"
                );
            } else if (paramType == ParamType.HexColor) {
                require(
                    uint256(pmpInput.configuredValue) <= _HEX_COLOR_MAX, // @dev minimum hex color of zero implicitly passed by using uint256
                    "PMP: invalid hex color"
                );
            } else {
                // @dev no coverage, this should never be reached
                revert("PMP: invalid paramType");
            }
        }

        // string and non-string checks
        if (pmpConfigStorage.paramType == ParamType.String) {
            require(
                pmpInput.configuredValue == bytes32(0),
                "PMP: value must be empty for string params"
            );
            // require artist is caller if configuring artist string
            if (pmpInput.configuringArtistString) {
                // require artist is caller
                (bool isArtist, ) = _isArtist({
                    tokenId: tokenId,
                    coreContract: coreContract,
                    sender: msg.sender
                });
                require(
                    isArtist,
                    "PMP: artist auth required to configure artist string"
                );
            }
        } else {
            // non-string - ensure configured string is empty
            require(
                bytes(pmpInput.configuredValueString).length == 0,
                "PMP: non-string param must have empty string value"
            );
            // non-string - ensure configuring artist string is false, because it is not relevant for non-string params
            require(
                !pmpInput.configuringArtistString,
                "PMP: artist string cannot be configured for non-string params"
            );
        }
    }

    /**
     * @notice Gets the value of a PMP as a formatted string.
     * @dev Used internally to retrieve a PMP value in string format for external consumption.
     * @param pmpConfigStorage The config storage for the PMP.
     * @param pmp The PMP storage instance.
     * @return isConfigured Whether the PMP has been configured for the token.
     * @return value The string representation of the PMP value, or empty if not configured.
     */
    function _getPMPValue(
        PMPConfigStorage storage pmpConfigStorage,
        PMPStorage storage pmp
    ) internal view returns (bool isConfigured, string memory value) {
        ParamType configuredParamType = pmp.configuredParamType;
        // unconfigured param for token
        if (
            configuredParamType == ParamType.Unconfigured || // unconfigured for token
            configuredParamType != pmpConfigStorage.paramType // stale - token configured param type is different from project config
        ) {
            return (false, "");
        }
        // if string, return value
        if (pmpConfigStorage.paramType == ParamType.String) {
            // return artist configured value if present
            if (bytes(pmp.artistConfiguredValueString).length > 0) {
                return (true, pmp.artistConfiguredValueString);
            }
            // return non-artist configured value if present
            if (bytes(pmp.nonArtistConfiguredValueString).length > 0) {
                return (true, pmp.nonArtistConfiguredValueString);
            }
            // empty string is considered not configured
            return (false, "");
        }
        if (configuredParamType == ParamType.Select) {
            // return unconfigured if index is out of bounds (obviously stale)
            if (
                uint256(pmp.configuredValue) >=
                pmpConfigStorage.selectOptionsLength
            ) {
                return (false, "");
            }
            return (
                true,
                pmpConfigStorage.selectOptions.get(uint256(pmp.configuredValue))
            );
        }
        if (configuredParamType == ParamType.Bool) {
            return (true, pmp.configuredValue == bytes32(0) ? "false" : "true");
        }
        if (
            configuredParamType == ParamType.Uint256Range ||
            configuredParamType == ParamType.HexColor ||
            configuredParamType == ParamType.Timestamp ||
            configuredParamType == ParamType.DecimalRange
        ) {
            // verify configured value is within bounds (obviously stale if not)
            uint256 configuredValue = uint256(pmp.configuredValue);
            uint256 maxRange = configuredParamType == ParamType.HexColor
                ? _HEX_COLOR_MAX
                : uint256(pmpConfigStorage.maxRange);
            uint256 minRange = configuredParamType == ParamType.HexColor
                ? 0
                : uint256(pmpConfigStorage.minRange);
            if (
                configuredValue < uint256(minRange) ||
                configuredValue > uint256(maxRange)
            ) {
                return (false, "");
            }
            // handle decimal case
            if (configuredParamType == ParamType.DecimalRange) {
                return (true, _uintToDecimalString(configuredValue));
            }
            // handle hex color case
            if (configuredParamType == ParamType.HexColor) {
                return (true, _uintToHexColorString(configuredValue));
            }
            // handle other cases - uint256 and timestamp
            return (true, configuredValue.toString());
        }
        if (configuredParamType == ParamType.Int256Range) {
            // verify configured value is within bounds (obviously stale if not)
            int256 configuredValue = int256(uint256(pmp.configuredValue));
            if (
                configuredValue < int256(uint256(pmpConfigStorage.minRange)) ||
                configuredValue > int256(uint256(pmpConfigStorage.maxRange))
            ) {
                return (false, "");
            }
            return (true, configuredValue.toStringSigned());
        }
        // @dev should never reach
        // @dev no coverage, unreachable
        revert("PMP: Unhandled ParamType");
    }

    /**
     * @notice Enforces that the caller is the artist of the specified project.
     * @dev Reverts if the caller is not the artist.
     * @dev Assumes Art Blocks V3 Core Contract interface.
     * @param coreContract The address of the core contract.
     * @param projectId The project ID to check artist permissions for.
     * @param sender The address to check if it's the artist.
     */
    function _onlyArtist(
        address coreContract,
        uint256 projectId,
        address sender
    ) internal view {
        require(
            IGenArt721CoreContractV3_Base(coreContract)
                .projectIdToArtistAddress(projectId) == sender,
            "PMP: only artist"
        );
    }

    /**
     * @notice Checks if an address is the artist of the project associated with a token.
     * @dev Assumes Art Blocks V3 Core Contract interface.
     * @param tokenId The token ID to get the project ID from.
     * @param coreContract The address of the core contract.
     * @param sender The address to check if it's the artist.
     * @return isArtist true if the sender is the artist, false otherwise.
     * @return artistAddress the address of the artist.
     */
    function _isArtist(
        uint256 tokenId,
        address coreContract,
        address sender
    ) internal view returns (bool isArtist, address artistAddress) {
        uint256 projectId = ABHelpers.tokenIdToProjectId(tokenId);
        artistAddress = IGenArt721CoreContractV3_Base(coreContract)
            .projectIdToArtistAddress(projectId);
        isArtist = artistAddress == sender;
    }

    /**
     * @notice Checks if an address is the owner of a token.
     * Supports ERC-721 Token-level wallet delegation for the TokenOwner role via delegate.xyz v2,
     * using the delegate.xyz v2 "postmintparameters" subdelegation rights.
     * @dev Assumes Art Blocks V3 Core Contract interface.
     * @param tokenId The token ID to check ownership for.
     * @param coreContract The address of the core contract.
     * @param sender The address to check if it's the token owner.
     * @return isTokenOwnerOrDelegate_ true if the sender is the token owner or delegate of the token owner, false otherwise.
     * @return tokenOwner the address of the token owner.
     * @dev Always execute within a nonReentrant context.
     */
    function _isTokenOwnerOrDelegate(
        uint256 tokenId,
        address coreContract,
        address sender
    ) internal view returns (bool isTokenOwnerOrDelegate_, address tokenOwner) {
        // @dev leading interaction - only execute within a nonReentrant context
        tokenOwner = IERC721(coreContract).ownerOf(tokenId);
        isTokenOwnerOrDelegate_ =
            (tokenOwner == sender) ||
            delegateRegistry.checkDelegateForERC721({
                to: sender, // hot wallet
                from: tokenOwner, // vault
                contract_: coreContract, // ERC-721 contract
                tokenId: tokenId, // tokenId
                rights: bytes32(DELEGATION_REGISTRY_TOKEN_OWNER_RIGHTS) // opt-in granular control of rights
            });
    }

    /**
     * @notice Computes the keccak256 hash of a string.
     * @dev Used to create mapping keys from string values.
     * @param str The string to hash.
     * @return The bytes32 hash of the input string.
     */
    function _getStringHash(string memory str) internal pure returns (bytes32) {
        return keccak256(abi.encode(str));
    }

    /**
     * @notice Converts a uint256 to a decimal string representation.
     * @dev Converts integer and fractional parts separately and combines them.
     * @param number The uint256 value to convert to decimal string.
     * @return A string representation of the decimal number.
     */
    function _uintToDecimalString(
        uint256 number
    ) private pure returns (string memory) {
        uint256 integerPart = number / _DECIMAL_PRECISION; // Integer part
        uint256 fractionalPart = number % _DECIMAL_PRECISION; // Fractional part

        // Convert integer and fractional parts to strings
        string memory intStr = integerPart.toString();
        string memory fracStr = fractionalPart.toString();

        // Pad fractional part with zeros if necessary
        while (bytes(fracStr).length < _DECIMAL_PRECISION_DIGITS) {
            fracStr = string(abi.encodePacked("0", fracStr));
        }

        // Combine integer and fractional parts with a decimal point
        return string(abi.encodePacked(intStr, ".", fracStr));
    }

    /**
     * @notice Converts a uint256 to a hex color string.
     * @dev forked from OpenZeppelin's Strings library to use # prefix
     * @dev Assumes the value is a valid hex color.
     * @param value The uint256 value to convert to hex color string.
     * @return A string representation of the hex color.
     */
    function _uintToHexColorString(
        uint256 value
    ) private pure returns (string memory) {
        uint256 localValue = value;
        bytes memory buffer = new bytes(2 * 3 + 1);
        buffer[0] = "#";
        for (uint256 i = 2 * 3; i > 0; --i) {
            buffer[i] = _HEX_DIGITS[localValue & 0xf];
            localValue >>= 4;
        }
        if (localValue != 0) {
            // @dev no coverage, this is redundant due to use of max hex color value
            revert("PMP: invalid hex color");
        }
        return string(buffer);
    }
}

File 2 of 22 : IERC165.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/IERC165.sol)

pragma solidity ^0.8.20;

import {IERC165} from "../utils/introspection/IERC165.sol";

// 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) (utils/introspection/ERC165.sol)

pragma solidity ^0.8.20;

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

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

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

pragma solidity ^0.8.20;

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

/**
 * @dev Library used to query support of an interface declared via {IERC165}.
 *
 * Note that these functions return the actual result of the query: they do not
 * `revert` if an interface is not supported. It is up to the caller to decide
 * what to do in these cases.
 */
library ERC165Checker {
    // As per the EIP-165 spec, no interface should ever match 0xffffffff
    bytes4 private constant INTERFACE_ID_INVALID = 0xffffffff;

    /**
     * @dev Returns true if `account` supports the {IERC165} interface.
     */
    function supportsERC165(address account) internal view returns (bool) {
        // Any contract that implements ERC165 must explicitly indicate support of
        // InterfaceId_ERC165 and explicitly indicate non-support of InterfaceId_Invalid
        return
            supportsERC165InterfaceUnchecked(account, type(IERC165).interfaceId) &&
            !supportsERC165InterfaceUnchecked(account, INTERFACE_ID_INVALID);
    }

    /**
     * @dev Returns true if `account` supports the interface defined by
     * `interfaceId`. Support for {IERC165} itself is queried automatically.
     *
     * See {IERC165-supportsInterface}.
     */
    function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool) {
        // query support of both ERC165 as per the spec and support of _interfaceId
        return supportsERC165(account) && supportsERC165InterfaceUnchecked(account, interfaceId);
    }

    /**
     * @dev Returns a boolean array where each value corresponds to the
     * interfaces passed in and whether they're supported or not. This allows
     * you to batch check interfaces for a contract where your expectation
     * is that some interfaces may not be supported.
     *
     * See {IERC165-supportsInterface}.
     */
    function getSupportedInterfaces(
        address account,
        bytes4[] memory interfaceIds
    ) internal view returns (bool[] memory) {
        // an array of booleans corresponding to interfaceIds and whether they're supported or not
        bool[] memory interfaceIdsSupported = new bool[](interfaceIds.length);

        // query support of ERC165 itself
        if (supportsERC165(account)) {
            // query support of each interface in interfaceIds
            for (uint256 i = 0; i < interfaceIds.length; i++) {
                interfaceIdsSupported[i] = supportsERC165InterfaceUnchecked(account, interfaceIds[i]);
            }
        }

        return interfaceIdsSupported;
    }

    /**
     * @dev Returns true if `account` supports all the interfaces defined in
     * `interfaceIds`. Support for {IERC165} itself is queried automatically.
     *
     * Batch-querying can lead to gas savings by skipping repeated checks for
     * {IERC165} support.
     *
     * See {IERC165-supportsInterface}.
     */
    function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool) {
        // query support of ERC165 itself
        if (!supportsERC165(account)) {
            return false;
        }

        // query support of each interface in interfaceIds
        for (uint256 i = 0; i < interfaceIds.length; i++) {
            if (!supportsERC165InterfaceUnchecked(account, interfaceIds[i])) {
                return false;
            }
        }

        // all interfaces supported
        return true;
    }

    /**
     * @notice Query if a contract implements an interface, does not check ERC165 support
     * @param account The address of the contract to query for support of an interface
     * @param interfaceId The interface identifier, as specified in ERC-165
     * @return true if the contract at account indicates support of the interface with
     * identifier interfaceId, false otherwise
     * @dev Assumes that account contains a contract that supports ERC165, otherwise
     * the behavior of this method is undefined. This precondition can be checked
     * with {supportsERC165}.
     *
     * Some precompiled contracts will falsely indicate support for a given interface, so caution
     * should be exercised when using this function.
     *
     * Interface identification is specified in ERC-165.
     */
    function supportsERC165InterfaceUnchecked(address account, bytes4 interfaceId) internal view returns (bool) {
        // prepare call
        bytes memory encodedParams = abi.encodeCall(IERC165.supportsInterface, (interfaceId));

        // perform static call
        bool success;
        uint256 returnSize;
        uint256 returnValue;
        assembly {
            success := staticcall(30000, account, add(encodedParams, 0x20), mload(encodedParams), 0x00, 0x20)
            returnSize := returndatasize()
            returnValue := mload(0x00)
        }

        return success && returnSize >= 0x20 && returnValue > 0;
    }
}

// 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
// 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
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/SignedMath.sol)

pragma solidity ^0.8.20;

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

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

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

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

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

pragma solidity ^0.8.20;

/**
 * @dev Contract module that helps prevent reentrant calls to a function.
 *
 * Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
 * available, which can be applied to functions to make sure there are no nested
 * (reentrant) calls to them.
 *
 * Note that because there is a single `nonReentrant` guard, functions marked as
 * `nonReentrant` may not call one another. This can be worked around by making
 * those functions `private`, and then adding `external` `nonReentrant` entry
 * points to them.
 *
 * TIP: If you would like to learn more about reentrancy and alternative ways
 * to protect against it, check out our blog post
 * https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
 */
abstract contract ReentrancyGuard {
    // Booleans are more expensive than uint256 or any type that takes up a full
    // word because each write operation emits an extra SLOAD to first read the
    // slot's contents, replace the bits taken up by the boolean, and then write
    // back. This is the compiler's defense against contract upgrades and
    // pointer aliasing, and it cannot be disabled.

    // The values being non-zero value makes deployment a bit more expensive,
    // but in exchange the refund on every call to nonReentrant will be lower in
    // amount. Since refunds are capped to a percentage of the total
    // transaction's gas, it is best to keep them low in cases like this one, to
    // increase the likelihood of the full refund coming into effect.
    uint256 private constant NOT_ENTERED = 1;
    uint256 private constant ENTERED = 2;

    uint256 private _status;

    /**
     * @dev Unauthorized reentrant call.
     */
    error ReentrancyGuardReentrantCall();

    constructor() {
        _status = NOT_ENTERED;
    }

    /**
     * @dev Prevents a contract from calling itself, directly or indirectly.
     * Calling a `nonReentrant` function from another `nonReentrant`
     * function is not supported. It is possible to prevent this from happening
     * by making the `nonReentrant` function external, and making it call a
     * `private` function that does the actual work.
     */
    modifier nonReentrant() {
        _nonReentrantBefore();
        _;
        _nonReentrantAfter();
    }

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be NOT_ENTERED
        if (_status == ENTERED) {
            revert ReentrancyGuardReentrantCall();
        }

        // Any calls to nonReentrant after this point will fail
        _status = ENTERED;
    }

    function _nonReentrantAfter() private {
        // By storing the original value once again, a refund is triggered (see
        // https://eips.ethereum.org/EIPS/eip-2200)
        _status = NOT_ENTERED;
    }

    /**
     * @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
     * `nonReentrant` function in the call stack.
     */
    function _reentrancyGuardEntered() internal view returns (bool) {
        return _status == ENTERED;
    }
}

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

pragma solidity ^0.8.20;

import {Math} from "./math/Math.sol";
import {SignedMath} from "./math/SignedMath.sol";

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

    /**
     * @dev The `value` string doesn't fit in the specified `length`.
     */
    error StringsInsufficientHexLength(uint256 value, uint256 length);

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

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

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

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

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

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

// SPDX-License-Identifier: LGPL-3.0-only
// Created By: Art Blocks Inc.

pragma solidity ^0.8.0;

interface IAdminACLV0 {
    /**
     * @notice Token ID `_tokenId` minted to `_to`.
     * @param previousSuperAdmin The previous superAdmin address.
     * @param newSuperAdmin The new superAdmin address.
     * @param genArt721CoreAddressesToUpdate Array of genArt721Core
     * addresses to update to the new superAdmin, for indexing purposes only.
     */
    event SuperAdminTransferred(
        address indexed previousSuperAdmin,
        address indexed newSuperAdmin,
        address[] genArt721CoreAddressesToUpdate
    );

    /// Type of the Admin ACL contract, e.g. "AdminACLV0"
    function AdminACLType() external view returns (string memory);

    /// super admin address
    function superAdmin() external view returns (address);

    /**
     * @notice Calls transferOwnership on other contract from this contract.
     * This is useful for updating to a new AdminACL contract.
     * @dev this function should be gated to only superAdmin-like addresses.
     */
    function transferOwnershipOn(
        address _contract,
        address _newAdminACL
    ) external;

    /**
     * @notice Calls renounceOwnership on other contract from this contract.
     * @dev this function should be gated to only superAdmin-like addresses.
     */
    function renounceOwnershipOn(address _contract) external;

    /**
     * @notice Checks if sender `_sender` is allowed to call function with selector
     * `_selector` on contract `_contract`.
     */
    function allowed(
        address _sender,
        address _contract,
        bytes4 _selector
    ) external returns (bool);
}

// SPDX-License-Identifier: CC0-1.0
pragma solidity >=0.8.13;

/**
 * @title IDelegateRegistry
 * @custom:version 2.0
 * @custom:author foobar (0xfoobar)
 * @notice A standalone immutable registry storing delegated permissions from one address to another
 */
interface IDelegateRegistry {
    /// @notice Delegation type, NONE is used when a delegation does not exist or is revoked
    enum DelegationType {
        NONE,
        ALL,
        CONTRACT,
        ERC721,
        ERC20,
        ERC1155
    }

    /// @notice Struct for returning delegations
    struct Delegation {
        DelegationType type_;
        address to;
        address from;
        bytes32 rights;
        address contract_;
        uint256 tokenId;
        uint256 amount;
    }

    /// @notice Emitted when an address delegates or revokes rights for their entire wallet
    event DelegateAll(
        address indexed from,
        address indexed to,
        bytes32 rights,
        bool enable
    );

    /// @notice Emitted when an address delegates or revokes rights for a contract address
    event DelegateContract(
        address indexed from,
        address indexed to,
        address indexed contract_,
        bytes32 rights,
        bool enable
    );

    /// @notice Emitted when an address delegates or revokes rights for an ERC721 tokenId
    event DelegateERC721(
        address indexed from,
        address indexed to,
        address indexed contract_,
        uint256 tokenId,
        bytes32 rights,
        bool enable
    );

    /// @notice Emitted when an address delegates or revokes rights for an amount of ERC20 tokens
    event DelegateERC20(
        address indexed from,
        address indexed to,
        address indexed contract_,
        bytes32 rights,
        uint256 amount
    );

    /// @notice Emitted when an address delegates or revokes rights for an amount of an ERC1155 tokenId
    event DelegateERC1155(
        address indexed from,
        address indexed to,
        address indexed contract_,
        uint256 tokenId,
        bytes32 rights,
        uint256 amount
    );

    /// @notice Thrown if multicall calldata is malformed
    error MulticallFailed();

    /**
     * -----------  WRITE -----------
     */

    /**
     * @notice Call multiple functions in the current contract and return the data from all of them if they all succeed
     * @param data The encoded function data for each of the calls to make to this contract
     * @return results The results from each of the calls passed in via data
     */
    function multicall(
        bytes[] calldata data
    ) external payable returns (bytes[] memory results);

    /**
     * @notice Allow the delegate to act on behalf of `msg.sender` for all contracts
     * @param to The address to act as delegate
     * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
     * @param enable Whether to enable or disable this delegation, true delegates and false revokes
     * @return delegationHash The unique identifier of the delegation
     */
    function delegateAll(
        address to,
        bytes32 rights,
        bool enable
    ) external payable returns (bytes32 delegationHash);

    /**
     * @notice Allow the delegate to act on behalf of `msg.sender` for a specific contract
     * @param to The address to act as delegate
     * @param contract_ The contract whose rights are being delegated
     * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
     * @param enable Whether to enable or disable this delegation, true delegates and false revokes
     * @return delegationHash The unique identifier of the delegation
     */
    function delegateContract(
        address to,
        address contract_,
        bytes32 rights,
        bool enable
    ) external payable returns (bytes32 delegationHash);

    /**
     * @notice Allow the delegate to act on behalf of `msg.sender` for a specific ERC721 token
     * @param to The address to act as delegate
     * @param contract_ The contract whose rights are being delegated
     * @param tokenId The token id to delegate
     * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
     * @param enable Whether to enable or disable this delegation, true delegates and false revokes
     * @return delegationHash The unique identifier of the delegation
     */
    function delegateERC721(
        address to,
        address contract_,
        uint256 tokenId,
        bytes32 rights,
        bool enable
    ) external payable returns (bytes32 delegationHash);

    /**
     * @notice Allow the delegate to act on behalf of `msg.sender` for a specific amount of ERC20 tokens
     * @dev The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound)
     * @param to The address to act as delegate
     * @param contract_ The address for the fungible token contract
     * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
     * @param amount The amount to delegate, > 0 delegates and 0 revokes
     * @return delegationHash The unique identifier of the delegation
     */
    function delegateERC20(
        address to,
        address contract_,
        bytes32 rights,
        uint256 amount
    ) external payable returns (bytes32 delegationHash);

    /**
     * @notice Allow the delegate to act on behalf of `msg.sender` for a specific amount of ERC1155 tokens
     * @dev The actual amount is not encoded in the hash, just the existence of a amount (since it is an upper bound)
     * @param to The address to act as delegate
     * @param contract_ The address of the contract that holds the token
     * @param tokenId The token id to delegate
     * @param rights Specific subdelegation rights granted to the delegate, pass an empty bytestring to encompass all rights
     * @param amount The amount of that token id to delegate, > 0 delegates and 0 revokes
     * @return delegationHash The unique identifier of the delegation
     */
    function delegateERC1155(
        address to,
        address contract_,
        uint256 tokenId,
        bytes32 rights,
        uint256 amount
    ) external payable returns (bytes32 delegationHash);

    /**
     * ----------- CHECKS -----------
     */

    /**
     * @notice Check if `to` is a delegate of `from` for the entire wallet
     * @param to The potential delegate address
     * @param from The potential address who delegated rights
     * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
     * @return valid Whether delegate is granted to act on the from's behalf
     */
    function checkDelegateForAll(
        address to,
        address from,
        bytes32 rights
    ) external view returns (bool);

    /**
     * @notice Check if `to` is a delegate of `from` for the specified `contract_` or the entire wallet
     * @param to The delegated address to check
     * @param contract_ The specific contract address being checked
     * @param from The cold wallet who issued the delegation
     * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
     * @return valid Whether delegate is granted to act on from's behalf for entire wallet or that specific contract
     */
    function checkDelegateForContract(
        address to,
        address from,
        address contract_,
        bytes32 rights
    ) external view returns (bool);

    /**
     * @notice Check if `to` is a delegate of `from` for the specific `contract` and `tokenId`, the entire `contract_`, or the entire wallet
     * @param to The delegated address to check
     * @param contract_ The specific contract address being checked
     * @param tokenId The token id for the token to delegating
     * @param from The wallet that issued the delegation
     * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
     * @return valid Whether delegate is granted to act on from's behalf for entire wallet, that contract, or that specific tokenId
     */
    function checkDelegateForERC721(
        address to,
        address from,
        address contract_,
        uint256 tokenId,
        bytes32 rights
    ) external view returns (bool);

    /**
     * @notice Returns the amount of ERC20 tokens the delegate is granted rights to act on the behalf of
     * @param to The delegated address to check
     * @param contract_ The address of the token contract
     * @param from The cold wallet who issued the delegation
     * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
     * @return balance The delegated balance, which will be 0 if the delegation does not exist
     */
    function checkDelegateForERC20(
        address to,
        address from,
        address contract_,
        bytes32 rights
    ) external view returns (uint256);

    /**
     * @notice Returns the amount of a ERC1155 tokens the delegate is granted rights to act on the behalf of
     * @param to The delegated address to check
     * @param contract_ The address of the token contract
     * @param tokenId The token id to check the delegated amount of
     * @param from The cold wallet who issued the delegation
     * @param rights Specific rights to check for, pass the zero value to ignore subdelegations and check full delegations only
     * @return balance The delegated balance, which will be 0 if the delegation does not exist
     */
    function checkDelegateForERC1155(
        address to,
        address from,
        address contract_,
        uint256 tokenId,
        bytes32 rights
    ) external view returns (uint256);

    /**
     * ----------- ENUMERATIONS -----------
     */

    /**
     * @notice Returns all enabled delegations a given delegate has received
     * @param to The address to retrieve delegations for
     * @return delegations Array of Delegation structs
     */
    function getIncomingDelegations(
        address to
    ) external view returns (Delegation[] memory delegations);

    /**
     * @notice Returns all enabled delegations an address has given out
     * @param from The address to retrieve delegations for
     * @return delegations Array of Delegation structs
     */
    function getOutgoingDelegations(
        address from
    ) external view returns (Delegation[] memory delegations);

    /**
     * @notice Returns all hashes associated with enabled delegations an address has received
     * @param to The address to retrieve incoming delegation hashes for
     * @return delegationHashes Array of delegation hashes
     */
    function getIncomingDelegationHashes(
        address to
    ) external view returns (bytes32[] memory delegationHashes);

    /**
     * @notice Returns all hashes associated with enabled delegations an address has given out
     * @param from The address to retrieve outgoing delegation hashes for
     * @return delegationHashes Array of delegation hashes
     */
    function getOutgoingDelegationHashes(
        address from
    ) external view returns (bytes32[] memory delegationHashes);

    /**
     * @notice Returns the delegations for a given array of delegation hashes
     * @param delegationHashes is an array of hashes that correspond to delegations
     * @return delegations Array of Delegation structs, return empty structs for nonexistent or revoked delegations
     */
    function getDelegationsFromHashes(
        bytes32[] calldata delegationHashes
    ) external view returns (Delegation[] memory delegations);

    /**
     * ----------- STORAGE ACCESS -----------
     */

    /**
     * @notice Allows external contracts to read arbitrary storage slots
     */
    function readSlot(bytes32 location) external view returns (bytes32);

    /**
     * @notice Allows external contracts to read an arbitrary array of storage slots
     */
    function readSlots(
        bytes32[] calldata locations
    ) external view returns (bytes32[] memory);
}

// SPDX-License-Identifier: LGPL-3.0-only
// Created By: Art Blocks Inc.

pragma solidity ^0.8.0;

import "./IAdminACLV0.sol";
import "./IGenArt721CoreProjectScriptV1.sol";

/**
 * @title This interface is intended to house interface items that are common
 * across all GenArt721CoreContractV3 flagship and derivative implementations.
 * This interface extends the IManifold royalty interface in order to
 * add support the Royalty Registry by default.
 * @author Art Blocks Inc.
 */
interface IGenArt721CoreContractV3_Base is IGenArt721CoreProjectScriptV1 {
    // This interface emits generic events that contain fields that indicate
    // which parameter has been updated. This is sufficient for application
    // state management, while also simplifying the contract and indexing code.
    // This was done as an alternative to having custom events that emit what
    // field-values have changed for each event, given that changed values can
    // be introspected by indexers due to the design of this smart contract
    // exposing these state changes via publicly viewable fields.

    /**
     * @notice Event emitted when the Art Blocks Curation Registry contract is updated.
     * @dev only utilized by subset of V3 core contracts (Art Blocks Curated contracts)
     * @param artblocksCurationRegistryAddress Address of Art Blocks Curation Registry contract.
     */
    event ArtBlocksCurationRegistryContractUpdated(
        address indexed artblocksCurationRegistryAddress
    );

    /**
     * @notice Project's royalty splitter was updated to `_splitter`.
     * @dev New event in v3.2
     * @param projectId The project ID.
     * @param royaltySplitter The new splitter address to receive royalties.
     */
    event ProjectRoyaltySplitterUpdated(
        uint256 indexed projectId,
        address indexed royaltySplitter
    );

    // The following fields are used to indicate which contract-level parameter
    // has been updated in the `PlatformUpdated` event:
    // @dev only append to the end of this enum in the case of future updates
    enum PlatformUpdatedFields {
        FIELD_NEXT_PROJECT_ID, // 0
        FIELD_NEW_PROJECTS_FORBIDDEN, // 1
        FIELD_DEFAULT_BASE_URI, // 2
        FIELD_RANDOMIZER_ADDRESS, // 3
        FIELD_NEXT_CORE_CONTRACT, // 4
        FIELD_ARTBLOCKS_DEPENDENCY_REGISTRY_ADDRESS, // 5
        FIELD_ARTBLOCKS_ON_CHAIN_GENERATOR_ADDRESS, // 6
        FIELD_PROVIDER_SALES_ADDRESSES, // 7
        FIELD_PROVIDER_PRIMARY_SALES_PERCENTAGES, // 8
        FIELD_PROVIDER_SECONDARY_SALES_BPS, // 9
        FIELD_SPLIT_PROVIDER, // 10
        FIELD_BYTECODE_STORAGE_READER // 11
    }

    // The following fields are used to indicate which project-level parameter
    // has been updated in the `ProjectUpdated` event:
    // @dev only append to the end of this enum in the case of future updates
    enum ProjectUpdatedFields {
        FIELD_PROJECT_COMPLETED, // 0
        FIELD_PROJECT_ACTIVE, // 1
        FIELD_PROJECT_ARTIST_ADDRESS, // 2
        FIELD_PROJECT_PAUSED, // 3
        FIELD_PROJECT_CREATED, // 4
        FIELD_PROJECT_NAME, // 5
        FIELD_PROJECT_ARTIST_NAME, // 6
        FIELD_PROJECT_SECONDARY_MARKET_ROYALTY_PERCENTAGE, // 7
        FIELD_PROJECT_DESCRIPTION, // 8
        FIELD_PROJECT_WEBSITE, // 9
        FIELD_PROJECT_LICENSE, // 10
        FIELD_PROJECT_MAX_INVOCATIONS, // 11
        FIELD_PROJECT_SCRIPT, // 12
        FIELD_PROJECT_SCRIPT_TYPE, // 13
        FIELD_PROJECT_ASPECT_RATIO, // 14
        FIELD_PROJECT_BASE_URI, // 15
        FIELD_PROJECT_PROVIDER_SECONDARY_FINANCIALS // 16
    }

    /**
     * @notice Error codes for the GenArt721 contract. Used by the GenArt721Error
     * custom error.
     * @dev only append to the end of this enum in the case of future updates
     */
    enum ErrorCodes {
        OnlyNonZeroAddress, // 0
        OnlyNonEmptyString, // 1
        OnlyNonEmptyBytes, // 2
        TokenDoesNotExist, // 3
        ProjectDoesNotExist, // 4
        OnlyUnlockedProjects, // 5
        OnlyAdminACL, // 6
        OnlyArtist, // 7
        OnlyArtistOrAdminACL, // 8
        OnlyAdminACLOrRenouncedArtist, // 9
        OnlyMinterContract, // 10
        MaxInvocationsReached, // 11
        ProjectMustExistAndBeActive, // 12
        PurchasesPaused, // 13
        OnlyRandomizer, // 14
        TokenHashAlreadySet, // 15
        NoZeroHashSeed, // 16
        OverMaxSumOfPercentages, // 17
        IndexOutOfBounds, // 18
        OverMaxSumOfBPS, // 19
        MaxOf100Percent, // 20
        PrimaryPayeeIsZeroAddress, // 21
        SecondaryPayeeIsZeroAddress, // 22
        MustMatchArtistProposal, // 23
        NewProjectsForbidden, // 24
        NewProjectsAlreadyForbidden, // 25
        OnlyArtistOrAdminIfLocked, // 26
        OverMaxSecondaryRoyaltyPercentage, // 27
        OnlyMaxInvocationsDecrease, // 28
        OnlyGteInvocations, // 29
        ScriptIdOutOfRange, // 30
        NoScriptsToRemove, // 31
        ScriptTypeAndVersionFormat, // 32
        AspectRatioTooLong, // 33
        AspectRatioNoNumbers, // 34
        AspectRatioImproperFormat, // 35
        OnlyNullPlatformProvider, // 36
        ContractInitialized // 37
    }

    /**
     * @notice Emits an error code `_errorCode` in the GenArt721Error event.
     * @dev Emitting error codes instead of error strings saves significant
     * contract bytecode size, allowing for more contract functionality within
     * the 24KB contract size limit.
     * @param _errorCode The error code to emit. See ErrorCodes enum.
     */
    error GenArt721Error(ErrorCodes _errorCode);

    /**
     * @notice Token ID `_tokenId` minted to `_to`.
     */
    event Mint(address indexed _to, uint256 indexed _tokenId);

    /**
     * @notice currentMinter updated to `_currentMinter`.
     * @dev Implemented starting with V3 core
     */
    event MinterUpdated(address indexed _currentMinter);

    /**
     * @notice Platform updated on bytes32-encoded field `_field`.
     */
    event PlatformUpdated(bytes32 indexed _field);

    /**
     * @notice Project ID `_projectId` updated on bytes32-encoded field
     * `_update`.
     */
    event ProjectUpdated(uint256 indexed _projectId, bytes32 indexed _update);

    event ProposedArtistAddressesAndSplits(
        uint256 indexed _projectId,
        address _artistAddress,
        address _additionalPayeePrimarySales,
        uint256 _additionalPayeePrimarySalesPercentage,
        address _additionalPayeeSecondarySales,
        uint256 _additionalPayeeSecondarySalesPercentage
    );

    event AcceptedArtistAddressesAndSplits(uint256 indexed _projectId);

    // version and type of the core contract
    // coreVersion is a string of the form "0.x.y"
    function coreVersion() external view returns (string memory);

    // coreType is a string of the form "GenArt721CoreV3"
    function coreType() external view returns (string memory);

    // owner (pre-V3 was named admin) of contract
    // this is expected to be an Admin ACL contract for V3
    function owner() external view returns (address);

    // Admin ACL contract for V3, will be at the address owner()
    function adminACLContract() external returns (IAdminACLV0);

    // backwards-compatible (pre-V3) admin - equal to owner()
    function admin() external view returns (address);

    /**
     * Function determining if _sender is allowed to call function with
     * selector _selector on contract `_contract`. Intended to be used with
     * peripheral contracts such as minters, as well as internally by the
     * core contract itself.
     */
    function adminACLAllowed(
        address _sender,
        address _contract,
        bytes4 _selector
    ) external returns (bool);

    /// getter function of public variable
    function startingProjectId() external view returns (uint256);

    // getter function of public variable
    function nextProjectId() external view returns (uint256);

    // getter function of public mapping
    function tokenIdToProjectId(
        uint256 tokenId
    ) external view returns (uint256 projectId);

    // @dev this is not available in V0
    function isMintWhitelisted(address minter) external view returns (bool);

    function projectIdToArtistAddress(
        uint256 _projectId
    ) external view returns (address payable);

    function projectIdToSecondaryMarketRoyaltyPercentage(
        uint256 _projectId
    ) external view returns (uint256);

    function projectURIInfo(
        uint256 _projectId
    ) external view returns (string memory projectBaseURI);

    // @dev new function in V3
    function projectStateData(
        uint256 _projectId
    )
        external
        view
        returns (
            uint256 invocations,
            uint256 maxInvocations,
            bool active,
            bool paused,
            uint256 completedTimestamp,
            bool locked
        );

    function projectDetails(
        uint256 _projectId
    )
        external
        view
        returns (
            string memory projectName,
            string memory artist,
            string memory description,
            string memory website,
            string memory license
        );

    function projectScriptDetails(
        uint256 _projectId
    )
        external
        view
        returns (
            string memory scriptTypeAndVersion,
            string memory aspectRatio,
            uint256 scriptCount
        );

    function projectScriptByIndex(
        uint256 _projectId,
        uint256 _index
    ) external view returns (string memory);

    function tokenIdToHash(uint256 _tokenId) external view returns (bytes32);

    // function to set a token's hash (must be guarded)
    function setTokenHash_8PT(uint256 _tokenId, bytes32 _hash) external;

    // @dev gas-optimized signature in V3 for `mint`
    function mint_Ecf(
        address _to,
        uint256 _projectId,
        address _by
    ) external returns (uint256 tokenId);
}

// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity ^0.8.0;

/**
 * @dev This interface is implemented by GenArt721CoreV3 and above
 */
interface IGenArt721CoreProjectScriptV1 {
    function projectScriptDetails(
        uint256 _projectId
    )
        external
        view
        returns (
            string memory scriptTypeAndVersion,
            string memory aspectRatio,
            uint256 scriptCount
        );

    function projectScriptBytecodeAddressByIndex(
        uint256 _projectId,
        uint256 _index
    ) external view returns (address);
}

// SPDX-License-Identifier: LGPL-3.0-only
// Creatd By: Art Blocks Inc.

pragma solidity ^0.8.0;

import {IPMPV0} from "./IPMPV0.sol";
import {IWeb3Call} from "./IWeb3Call.sol";

import {IERC165} from "@openzeppelin-5.0/contracts/interfaces/IERC165.sol";

interface IPMPAugmentHook is IERC165 {
    /**
     * @notice Augment the token parameters for a given token.
     * @dev This hook is called when a token's PMPs are read.
     * @dev This must return all desired tokenParams, not just additional data.
     * @param coreContract The address of the core contract to call.
     * @param tokenId The tokenId of the token to get data for.
     * @param tokenParams The token parameters for the queried token.
     * @return augmentedTokenParams The augmented token parameters.
     */
    function onTokenPMPReadAugmentation(
        address coreContract,
        uint256 tokenId,
        IWeb3Call.TokenParam[] calldata tokenParams
    )
        external
        view
        returns (IWeb3Call.TokenParam[] memory augmentedTokenParams);

    // @dev ERC156 function supportsInterface must be implemented and broadcast true for this interfaceId
}

// SPDX-License-Identifier: LGPL-3.0-only
// Creatd By: Art Blocks Inc.

pragma solidity ^0.8.0;

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

import {IERC165} from "@openzeppelin-5.0/contracts/interfaces/IERC165.sol";

interface IPMPConfigureHook is IERC165 {
    /**
     * @notice Execution logic to be executed when a token's PMP is configured.
     * @dev This hook is executed after the PMP is configured.
     * @param coreContract The address of the core contract that was configured.
     * @param tokenId The tokenId of the token that was configured.
     * @param pmpInput The PMP input that was used to successfully configure the token.
     */
    function onTokenPMPConfigure(
        address coreContract,
        uint256 tokenId,
        IPMPV0.PMPInput calldata pmpInput
    ) external;

    // @dev ERC156 function supportsInterface must be implemented and broadcast true for this interfaceId
}

// SPDX-License-Identifier: LGPL-3.0-only
// Created By: Art Blocks Inc.

pragma solidity ^0.8.0;

import {IPMPConfigureHook} from "./IPMPConfigureHook.sol";
import {IPMPAugmentHook} from "./IPMPAugmentHook.sol";
import {IWeb3Call} from "./IWeb3Call.sol";

import {ImmutableStringArray} from "../../libs/v0.8.x/ImmutableStringArray.sol";

/**
 * @title Project Metadata Parameters (PMP) Interface, V0
 * @author Art Blocks Inc.
 * @notice Interface for the Project Metadata Parameters (PMP) contract that defines
 * how projects can expose configurable parameters for tokens. This interface establishes
 * the standard for parameter configuration, validation, and retrieval.
 * @dev This interface extends the IWeb3Call interface to provide compatibility with
 * existing Web3 infrastructure.
 */
interface IPMPV0 is IWeb3Call {
    /**
     * @notice Emitted when project hooks are configured.
     * @param coreContract The address of the core contract.
     * @param projectId The project ID for which hooks were configured.
     * @param tokenPMPPostConfigHook The hook to call after a token's PMP is configured.
     * @param tokenPMPReadAugmentationHook The hook to call when reading a token's PMPs.
     */
    event ProjectHooksConfigured(
        address coreContract,
        uint256 projectId,
        IPMPConfigureHook tokenPMPPostConfigHook,
        IPMPAugmentHook tokenPMPReadAugmentationHook
    );

    /**
     * @notice Emitted when a project's available parameters are configured.
     * @param coreContract The address of the core contract.
     * @param projectId The project ID for which parameters were configured.
     * @param pmpInputConfigs Array of parameter configurations defining the available parameters.
     * @param projectConfigNonce The nonce of the project configuration.
     */
    event ProjectConfigured(
        address coreContract,
        uint256 projectId,
        PMPInputConfig[] pmpInputConfigs,
        uint8 projectConfigNonce
    );

    /**
     * @notice Emitted when a token's parameters are configured.
     * @param coreContract The address of the core contract.
     * @param tokenId The token ID for which parameters were configured.
     * @param pmpInputs Array of parameter inputs that were configured.
     * @param authAddresses Array of addresses that authenticated the parameters. Aligned by index with pmpInputs.
     */
    event TokenParamsConfigured(
        address coreContract,
        uint256 tokenId,
        PMPInput[] pmpInputs,
        address[] authAddresses
    );

    /**
     * @notice Emitted when the delegation registry is updated.
     * @dev At the time of writing, the delegate.xyz v2 contract is the only
     * delegate registry contract that is supported.
     * @param delegationRegistry The address of the new delegation registry.
     */
    event DelegationRegistryUpdated(address delegationRegistry);

    /**
     * @notice Defines who can configure a parameter for a token.
     * @dev Enum ordering is relied on (ArtistAndTokenOwnerAndAddress being last).
     */
    enum AuthOption {
        Artist,
        TokenOwner,
        Address,
        ArtistAndTokenOwner,
        ArtistAndAddress,
        TokenOwnerAndAddress,
        ArtistAndTokenOwnerAndAddress
    }

    /**
     * @notice Defines the type of parameter that can be configured.
     * @dev Enum ordering is relied on (String being last).
     */
    enum ParamType {
        Unconfigured, // @dev default value, used to check if PMP is configured
        Select,
        Bool,
        Uint256Range,
        Int256Range,
        DecimalRange,
        HexColor,
        Timestamp,
        String // utilizes string in PMP struct, all other param types utilize the generic bytes32 param
    }

    /**
     * @notice Structure for a parameter configuration input.
     * @dev Used when configuring a project's available parameters.
     */
    struct PMPInputConfig {
        string key; // slot 0: 32 bytes
        PMPConfig pmpConfig; // slot 1: 32 bytes
    }

    /**
     * @notice Structure for a parameter configuration.
     * @dev Defines the constraints and options for a parameter.
     */
    struct PMPConfig {
        AuthOption authOption; // slot 0: 1 byte
        ParamType paramType; // slot 0: 1 byte
        uint48 pmpLockedAfterTimestamp; // slot 0: 6 bytes
        address authAddress; // slot 0: 20 bytes
        string[] selectOptions; // slot 1: 32 bytes
        // @dev use bytes32 for all range types for SSTORE efficiency
        // @dev minRange and maxRange cast to defined numeric type when verifying assigned PMP values
        bytes32 minRange; // slot 2: 32 bytes
        bytes32 maxRange; // slot 3: 32 bytes
    }

    /**
     * @notice View structure for a parameter configuration.
     * @dev Used when reading a project's available parameters.
     * @dev any populated select options are loaded and returned as a string array
     * instead of remaining as a pointer to a data contract
     */
    struct PMPConfigView {
        uint8 highestConfigNonce;
        AuthOption authOption;
        ParamType paramType;
        uint48 pmpLockedAfterTimestamp;
        address authAddress;
        uint8 selectOptionsLength;
        string[] selectOptions;
        bytes32 minRange;
        bytes32 maxRange;
    }

    /**
     * @notice Structure for a parameter input.
     * @dev Used when configuring a token's parameters.
     */
    struct PMPInput {
        string key; // slot 0: 32 bytes
        ParamType configuredParamType;
        // @dev store values as bytes32 for efficiency, cast appropriately when reading
        bytes32 configuredValue;
        bool configuringArtistString;
        string configuredValueString;
    }

    /**
     * @notice Storage structure for a configured parameter.
     * @dev Includes both artist and non-artist configured string values.
     */
    struct PMPStorage {
        ParamType configuredParamType; // slot 0: 1 byte
        // @dev store values as bytes32 for efficiency, cast appropriately when reading
        bytes32 configuredValue; // slot 1: 32 bytes
        string artistConfiguredValueString; // slot 2: 32 bytes
        string nonArtistConfiguredValueString; // slot 3: 32 bytes
    }

    /**
     * @notice Configure the hooks for a project.
     * @param coreContract The address of the core contract.
     * @param projectId The project ID to configure hooks for.
     * @param tokenPMPPostConfigHook The hook to call after a token's PMP is configured.
     * @param tokenPMPReadAugmentationHook The hook to call when reading a token's PMPs.
     */
    function configureProjectHooks(
        address coreContract,
        uint256 projectId,
        IPMPConfigureHook tokenPMPPostConfigHook,
        IPMPAugmentHook tokenPMPReadAugmentationHook
    ) external;

    /**
     * @notice Configure the available parameters for a project.
     * @param coreContract The address of the core contract.
     * @param projectId The project ID to configure parameters for.
     * @param pmpInputConfigs Array of parameter configurations defining the available parameters.
     */
    function configureProject(
        address coreContract,
        uint256 projectId,
        PMPInputConfig[] calldata pmpInputConfigs
    ) external;

    /**
     * @notice Configure the parameters for a specific token.
     * @param coreContract The address of the core contract.
     * @param tokenId The token ID to configure parameters for.
     * @param pmpInputs Array of parameter inputs to configure.
     */
    function configureTokenParams(
        address coreContract,
        uint256 tokenId,
        PMPInput[] calldata pmpInputs
    ) external;

    /**
     * @notice Checks if the given wallet has the owner role for the given token.
     * It returns true if the wallet is the owner of the token or if the wallet
     * is a delegate of the token owner; otherwise it returns false.
     * Reverts if an invalid coreContract or tokenId is provided.
     * Provided for convenience, as the same check is performed in the
     * configureTokenParams function.
     * @param wallet The wallet address to check.
     * @param coreContract The address of the core contract to call.
     * @param tokenId The tokenId of the token to check.
     * @return isTokenOwnerOrDelegate_ True if the wallet is the owner or a delegate of the token,
     * false otherwise.
     */
    function isTokenOwnerOrDelegate(
        address wallet,
        address coreContract,
        uint256 tokenId
    ) external view returns (bool isTokenOwnerOrDelegate_);
}

File 18 of 22 : IWeb3Call.sol
// SPDX-License-Identifier: LGPL-3.0-only
// Creatd By: Art Blocks Inc.

pragma solidity ^0.8.0;

interface IWeb3Call {
    /**
     * @notice TokenParam struct defines the parameters for a given token.
     * @param key The key of the token parameter.
     * @param value The value of the token parameter.
     */
    struct TokenParam {
        string key;
        string value;
    }

    /**
     * @notice Get the token parameters for a given token.
     * If none are configured, the tokenParams should be empty.
     * @param coreContract The address of the core contract to call.
     * @param tokenId The tokenId of the token to get data for.
     * @return tokenParams An array of token parameters for the queried token.
     */
    function getTokenParams(
        address coreContract,
        uint256 tokenId
    ) external view returns (TokenParam[] memory tokenParams);
}

// SPDX-License-Identifier: LGPL-3.0-only
// Created By: Art Blocks Inc.

pragma solidity ^0.8.0;

/**
 * @title Art Blocks Helpers Library
 * @notice This library contains helper functions for common operations in the
 * Art Blocks ecosystem of smart contracts.
 * @author Art Blocks Inc.
 */

library ABHelpers {
    uint256 constant ONE_MILLION = 1_000_000;

    /**
     * @notice Function to convert token id to project id.
     * @param tokenId The id of the token.
     */
    function tokenIdToProjectId(
        uint256 tokenId
    ) internal pure returns (uint256) {
        // int division properly rounds down
        // @dev no way to disable division by zero check in solidity v0.8.24, so not unchecked
        return tokenId / ONE_MILLION;
    }

    /**
     * @notice Function to convert token id to token number.
     * @param tokenId The id of the token.
     */
    function tokenIdToTokenNumber(
        uint256 tokenId
    ) internal pure returns (uint256) {
        // mod returns remainder, which is the token number
        // @dev no way to disable mod zero check in solidity, so not unchecked
        return tokenId % ONE_MILLION;
    }

    /**
     * @notice Function to convert token id to token invocation.
     * @dev token invocation is the token number plus one, because token #0 is
     * invocation 1.
     * @param tokenId The id of the token.
     */
    function tokenIdToTokenInvocation(
        uint256 tokenId
    ) internal pure returns (uint256) {
        unchecked {
            // mod returns remainder, which is the token number
            // @dev no way to disable mod zero check in solidity, unchecked to optimize gas for addition
            return (tokenId % ONE_MILLION) + 1;
        }
    }

    /**
     * @notice Function to convert project id and token number to token id.
     * @param projectId The id of the project.
     * @param tokenNumber The token number.
     */
    function tokenIdFromProjectIdAndTokenNumber(
        uint256 projectId,
        uint256 tokenNumber
    ) internal pure returns (uint256) {
        // @dev intentionally not unchecked to ensure overflow detection, which
        // would likley only occur in a malicious call
        return (projectId * ONE_MILLION) + tokenNumber;
    }
}

// SPDX-License-Identifier: LGPL-3.0-only
// Created By: Art Blocks Inc.

pragma solidity ^0.8.0;

import {SSTORE2} from "./SSTORE2.sol"; // Import SSTORE2 library

/**
 * @title ImmutableStringArray
 * @author Art Blocks Inc.
 * @notice This library is optimized to store immutable arrays of strings much more efficiently than using native Solidity storage arrays.
 * It uses a single SSTORE2 contract to store the length + packed offsets + packed string data.
 * Allows overwriting the pointer to point to a new immutable array.
 */
library ImmutableStringArray {
    /**
     * @notice Struct to store the SSTORE2 pointer to the packed string array.
     * @dev dataPointer is the SSTORE2 pointer to the packed string array. Assigned to address(0) if the array is empty.
     */
    struct StringArray {
        address dataPointer; // SSTORE2 pointer storing length + packed offsets + packed string data
    }

    /**
     * @notice Stores a packed immutable string array using a single SSTORE2 contract.
     * Allows overwriting the pointer to point to a new immutable array.
     * @dev Optimized for the case where the array is empty by assigning the dataPointer to address(0).
     * @param storageArray The storage reference to store the packed array.
     * @param strings The array of strings to pack.
     */
    function store(
        StringArray storage storageArray,
        string[] memory strings
    ) internal {
        uint256 arrayLength = strings.length;
        // edge case - empty array
        if (arrayLength == 0) {
            storageArray.dataPointer = address(0);
            return;
        }

        uint64[] memory offsets = new uint64[](arrayLength);

        // compute total bytes length and offsets (ensuring no overflow)
        uint256 totalStringBytesLength;
        for (uint256 i = 0; i < arrayLength; i++) {
            offsets[i] = uint64(totalStringBytesLength);
            totalStringBytesLength += bytes(strings[i]).length;
        }
        // realistically will not overflow uint64, but check for security guarantees
        // @dev no coverage on else (difficulty of triggering)
        require(
            totalStringBytesLength <= type(uint64).max,
            "Offset exceeds uint64 limit"
        );

        // prepare the combined storage structure (length + packed offsets + packed strings)
        // @dev 8 bytes for length, 8 bytes per offset, totalStringBytesLength bytes for strings
        // over-allocate 32 bytes for memory safety
        // note: shorten length by 32 bytes at end of function before returning
        uint256 packedDataLength = 8 +
            (arrayLength * 8) +
            totalStringBytesLength;
        bytes memory packedData = new bytes(packedDataLength + 0x20);
        uint256 ptr;

        // store the length of the strings array in the first 8 bytes of the packedData
        assembly ("memory-safe") {
            ptr := add(packedData, 0x20) // pointer to first byte of packedData
            mstore(ptr, shl(192, arrayLength)) // left-align only 8 bytes in the 32-byte slot
            ptr := add(ptr, 8) // move pointer forward by 8 bytes
        }

        // store offsets in packed `uint64` format
        for (uint256 i = 0; i < arrayLength; i++) {
            uint256 offset = offsets[i];
            assembly ("memory-safe") {
                mstore(ptr, shl(192, offset))
                // move pointer ahead 8 bytes for uint64
                ptr := add(ptr, 8)
            }
        }

        // pack the strings efficiently, using assembly to copy 32 bytes at a time
        for (uint i = 0; i < arrayLength; i++) {
            bytes memory currentString = bytes(strings[i]);
            uint currentLength = currentString.length;
            uint currentPtr;

            assembly ("memory-safe") {
                currentPtr := add(currentString, 0x20) // start of current string's data
            }

            // copy the full 32-byte chunks
            uint chunks = currentLength / 32;
            uint remainder = currentLength % 32;
            // store the full 32 byte chunks
            for (uint j = 0; j < chunks; j++) {
                assembly ("memory-safe") {
                    let chunk := mload(currentPtr) // load 32 bytes of the current string
                    mstore(ptr, chunk) // store the 32 bytes into the result
                    ptr := add(ptr, 0x20) // move the result pointer forward by 32 bytes
                    currentPtr := add(currentPtr, 0x20) // move the current string pointer forward by 32 bytes
                }
            }
            // store any partial chunks
            if (remainder > 0) {
                bytes32 chunk;
                assembly ("memory-safe") {
                    chunk := mload(currentPtr) // load full 32-byte word
                    // safe to write past end of final array - 32 bytes of buffer at end of array
                    mstore(ptr, chunk) // store the final chunk's remaining bytes
                    ptr := add(ptr, remainder) // move the result pointer forward by the remainder length for next iteration
                }
            }
        }

        // remove the buffer from the packedData bytes array length
        assembly ("memory-safe") {
            ptr := packedData
            mstore(ptr, packedDataLength)
        }

        // Store all packed data in a SSTORE2 contract, store result in storage
        storageArray.dataPointer = SSTORE2.write(packedData);
    }

    /**
     * @notice Retrieves the total number of stored strings from SSTORE2.
     * @param storageArray The storage reference containing packed strings.
     * @return count The count of stored strings.
     */
    function length(
        StringArray storage storageArray
    ) internal view returns (uint256 count) {
        // edge case - unassigned dataPointer or empty array
        if (storageArray.dataPointer == address(0)) {
            return 0;
        }

        // @dev this could be more efficient by only reading the first 8 bytes of the SSTORE2 contract
        // but prefer to keep simple
        bytes memory allData = SSTORE2.read(storageArray.dataPointer);

        assembly ("memory-safe") {
            count := shr(192, mload(add(allData, 32)))
        }
    }

    /**
     * @notice Retrieves a single string from storage by index.
     * @param storageArray The storage reference containing packed strings.
     * @param index The index of the string to retrieve.
     * @return result The retrieved string.
     */
    function get(
        StringArray storage storageArray,
        uint256 index
    ) internal view returns (string memory result) {
        // edge case - unassigned dataPointer or empty array
        if (storageArray.dataPointer == address(0)) {
            revert("Index out of bounds");
        }
        bytes memory allData = SSTORE2.read(storageArray.dataPointer);
        uint256 offsetsStart;
        uint64 start;
        uint64 end;
        // load the total length of the strings array from the first 8 bytes of the SSTORE2 contract
        uint256 arrayLength;
        assembly ("memory-safe") {
            arrayLength := shr(192, mload(add(allData, 32)))
        }

        require(index < arrayLength, "Index out of bounds");
        if (index + 1 == arrayLength) {
            uint256 allDataLength = allData.length;
            assembly ("memory-safe") {
                offsetsStart := add(allData, 40) // skipping first 8 bytes (length)
                start := shr(192, mload(add(offsetsStart, mul(index, 8)))) // load offsets[index] as uint64
                end := sub(
                    allDataLength,
                    add(8, mul(arrayLength, 8)) // subtract 8 bytes for overall array length, and 8*length for the offsets section
                )
            }
        } else {
            assembly ("memory-safe") {
                offsetsStart := add(allData, 40) // skipping first 8 bytes (length)
                start := shr(192, mload(add(offsetsStart, mul(index, 8)))) // load offsets[index] as uint64
                end := shr(192, mload(add(offsetsStart, mul(add(index, 1), 8)))) // load offsets[index + 1]
            }
        }

        uint256 strLength = end - start;
        // over-allocate 32 bytes for memory safety
        bytes memory strBytes = new bytes(strLength + 32);
        uint256 ptr;
        assembly ("memory-safe") {
            ptr := add(strBytes, 0x20) // pointer to first byte of strBytes
        }

        // efficiently copy 32 bytes at a time from allData to strBytes
        for (uint256 i = 0; i < strLength; i += 32) {
            // offset = 32 bytes for length + 8 bytes for overall array length + 8 bytes per offset * length of array + start of string + i
            uint256 offset = 32 + 8 + (arrayLength * 8) + start + i;
            assembly ("memory-safe") {
                let chunk := mload(add(allData, offset)) // load 32 bytes of the current string
                mstore(ptr, chunk) // store the 32 bytes into the result (buffer keeps this memory safe)
                ptr := add(ptr, 0x20) // move the result pointer forward by 32 bytes
            }
        }

        // remove the buffer length from the strBytes bytes array length
        assembly ("memory-safe") {
            ptr := strBytes
            mstore(ptr, strLength)
        }
        result = string(strBytes);
    }

    /**
     * @notice Retrieves all stored strings in a single batch call.
     * @param storageArray The storage reference containing packed strings.
     * @return results An array of all stored strings.
     */
    function getAll(
        StringArray storage storageArray
    ) internal view returns (string[] memory results) {
        // edge case - unassigned dataPointer or empty array
        if (storageArray.dataPointer == address(0)) {
            return new string[](0);
        }
        // load the packed data from the SSTORE2 contract
        bytes memory allData = SSTORE2.read(storageArray.dataPointer);
        // load the total length of the strings array from the first 8 bytes of the SSTORE2 contract
        uint256 arrayLength;
        assembly ("memory-safe") {
            arrayLength := shr(192, mload(add(allData, 32)))
        }

        // for each string in the array, populate results
        results = new string[](arrayLength);

        for (uint256 index = 0; index < arrayLength; index++) {
            // @dev start and end are calculated for each index for code simplicity/code re-use from get() function
            uint256 offsetsStart;
            uint64 start;
            uint64 end;

            if (index + 1 == arrayLength) {
                uint256 allDataLength = allData.length;
                assembly ("memory-safe") {
                    offsetsStart := add(allData, 40) // Skipping first 8 bytes (length)
                    start := shr(192, mload(add(offsetsStart, mul(index, 8)))) // Load offsets[index] as uint64
                    end := sub(
                        allDataLength,
                        add(8, mul(arrayLength, 8)) // subtract 8 bytes for overall array length, and 8*length for the offsets section
                    )
                }
            } else {
                assembly ("memory-safe") {
                    offsetsStart := add(allData, 40) // Skipping first 8 bytes (length)
                    start := shr(192, mload(add(offsetsStart, mul(index, 8)))) // Load offsets[index] as uint64
                    end := shr(
                        192,
                        mload(add(offsetsStart, mul(add(index, 1), 8)))
                    ) // Load offsets[index + 1]
                }
            }

            uint256 strLength = end - start;
            // over-allocate 32 bytes for memory safety
            bytes memory strBytes = new bytes(strLength + 32);
            uint256 ptr;
            assembly ("memory-safe") {
                ptr := add(strBytes, 0x20) // pointer to first byte of strBytes
            }

            // efficiently copy 32 bytes at a time from allData to strBytes
            for (uint256 i = 0; i < strLength; i += 32) {
                // offset = 32 bytes for length + 8 bytes for overall array length + 8 bytes per offset * length of array + start of string + i
                uint256 offset = 32 + 8 + (arrayLength * 8) + start + i;
                assembly ("memory-safe") {
                    let chunk := mload(add(allData, offset)) // Load 32 bytes of the current string
                    mstore(ptr, chunk) // Store the 32 bytes into the result (buffer keeps this memory safe)
                    ptr := add(ptr, 0x20) // Move the result pointer forward by 32 bytes
                }
            }

            // remove the buffer length from the strBytes bytes array length
            assembly ("memory-safe") {
                ptr := strBytes
                mstore(ptr, strLength)
            }

            // store the string in the results array
            results[index] = string(strBytes);
        }

        // results is returned by reference, so no need for explicit return
    }
}

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;

/// @notice Read and write to persistent storage at a fraction of the cost.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SSTORE2.sol)
/// @author Modified from 0xSequence (https://github.com/0xSequence/sstore2/blob/master/contracts/SSTORE2.sol)
library SSTORE2 {
    uint256 internal constant DATA_OFFSET = 1; // We skip the first byte as it's a STOP opcode to ensure the contract can't be called.

    /*//////////////////////////////////////////////////////////////
                               WRITE LOGIC
    //////////////////////////////////////////////////////////////*/

    function write(bytes memory data) internal returns (address pointer) {
        // Prefix the bytecode with a STOP opcode to ensure it cannot be called.
        bytes memory runtimeCode = abi.encodePacked(hex"00", data);

        bytes memory creationCode = abi.encodePacked(
            //---------------------------------------------------------------------------------------------------------------//
            // Opcode  | Opcode + Arguments  | Description  | Stack View                                                     //
            //---------------------------------------------------------------------------------------------------------------//
            // 0x60    |  0x600B             | PUSH1 11     | codeOffset                                                     //
            // 0x59    |  0x59               | MSIZE        | 0 codeOffset                                                   //
            // 0x81    |  0x81               | DUP2         | codeOffset 0 codeOffset                                        //
            // 0x38    |  0x38               | CODESIZE     | codeSize codeOffset 0 codeOffset                               //
            // 0x03    |  0x03               | SUB          | (codeSize - codeOffset) 0 codeOffset                           //
            // 0x80    |  0x80               | DUP          | (codeSize - codeOffset) (codeSize - codeOffset) 0 codeOffset   //
            // 0x92    |  0x92               | SWAP3        | codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset)   //
            // 0x59    |  0x59               | MSIZE        | 0 codeOffset (codeSize - codeOffset) 0 (codeSize - codeOffset) //
            // 0x39    |  0x39               | CODECOPY     | 0 (codeSize - codeOffset)                                      //
            // 0xf3    |  0xf3               | RETURN       |                                                                //
            //---------------------------------------------------------------------------------------------------------------//
            hex"60_0B_59_81_38_03_80_92_59_39_F3", // Returns all code in the contract except for the first 11 (0B in hex) bytes.
            runtimeCode // The bytecode we want the contract to have after deployment. Capped at 1 byte less than the code size limit.
        );

        /// @solidity memory-safe-assembly
        assembly {
            // Deploy a new contract with the generated creation code.
            // We start 32 bytes into the code to avoid copying the byte length.
            pointer := create(0, add(creationCode, 32), mload(creationCode))
        }

        require(pointer != address(0), "DEPLOYMENT_FAILED");
    }

    /*//////////////////////////////////////////////////////////////
                               READ LOGIC
    //////////////////////////////////////////////////////////////*/

    function read(address pointer) internal view returns (bytes memory) {
        return
            readBytecode(
                pointer,
                DATA_OFFSET,
                pointer.code.length - DATA_OFFSET
            );
    }

    function read(
        address pointer,
        uint256 start
    ) internal view returns (bytes memory) {
        start += DATA_OFFSET;

        return readBytecode(pointer, start, pointer.code.length - start);
    }

    function read(
        address pointer,
        uint256 start,
        uint256 end
    ) internal view returns (bytes memory) {
        start += DATA_OFFSET;
        end += DATA_OFFSET;

        require(pointer.code.length >= end, "OUT_OF_BOUNDS");

        return readBytecode(pointer, start, end - start);
    }

    /*//////////////////////////////////////////////////////////////
                          INTERNAL HELPER LOGIC
    //////////////////////////////////////////////////////////////*/

    function readBytecode(
        address pointer,
        uint256 start,
        uint256 size
    ) private view returns (bytes memory data) {
        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            data := mload(0x40)

            // Update the free memory pointer to prevent overriding our data.
            // We use and(x, not(31)) as a cheaper equivalent to sub(x, mod(x, 32)).
            // Adding 31 to size and running the result through the logic above ensures
            // the memory pointer remains word-aligned, following the Solidity convention.
            mstore(0x40, add(data, and(add(add(size, 32), 31), not(31))))

            // Store the size of the data in the first 32 byte chunk of free memory.
            mstore(data, size)

            // Copy the code into memory right after the 32 bytes we used to store the size.
            extcodecopy(pointer, add(data, 32), start, size)
        }
    }
}

File 22 of 22 : Web3Call.sol
// SPDX-License-Identifier: LGPL-3.0-only
// Created By: Art Blocks Inc.

pragma solidity ^0.8.0;

import {IWeb3Call} from "../interfaces/v0.8.x/IWeb3Call.sol";

import {ERC165} from "@openzeppelin-5.0/contracts/utils/introspection/ERC165.sol";

/**
 * @title Abstract Web3Call contract
 * @author Art Blocks Inc.
 * @notice This abstract can be inherited by any contract that wants to implement the Web3Call interface.
 * It indicates support for the IWeb3Call interface via ERC165 interface detection, and ensures that all
 * child contracts implement the required IWeb3Call functions.
 */
abstract contract Web3Call is IWeb3Call, ERC165 {
    function supportsInterface(
        bytes4 interfaceId
    ) public view override returns (bool) {
        return
            interfaceId == type(IWeb3Call).interfaceId ||
            super.supportsInterface(interfaceId);
    }
}

Settings
{
  "optimizer": {
    "enabled": true,
    "runs": 10
  },
  "evmVersion": "paris",
  "outputSelection": {
    "*": {
      "*": [
        "evm.bytecode",
        "evm.deployedBytecode",
        "devdoc",
        "userdoc",
        "metadata",
        "abi"
      ]
    }
  },
  "libraries": {}
}

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"contract IDelegateRegistry","name":"delegateRegistry_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"delegationRegistry","type":"address"}],"name":"DelegationRegistryUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"coreContract","type":"address"},{"indexed":false,"internalType":"uint256","name":"projectId","type":"uint256"},{"components":[{"internalType":"string","name":"key","type":"string"},{"components":[{"internalType":"enum IPMPV0.AuthOption","name":"authOption","type":"uint8"},{"internalType":"enum IPMPV0.ParamType","name":"paramType","type":"uint8"},{"internalType":"uint48","name":"pmpLockedAfterTimestamp","type":"uint48"},{"internalType":"address","name":"authAddress","type":"address"},{"internalType":"string[]","name":"selectOptions","type":"string[]"},{"internalType":"bytes32","name":"minRange","type":"bytes32"},{"internalType":"bytes32","name":"maxRange","type":"bytes32"}],"internalType":"struct IPMPV0.PMPConfig","name":"pmpConfig","type":"tuple"}],"indexed":false,"internalType":"struct IPMPV0.PMPInputConfig[]","name":"pmpInputConfigs","type":"tuple[]"},{"indexed":false,"internalType":"uint8","name":"projectConfigNonce","type":"uint8"}],"name":"ProjectConfigured","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"coreContract","type":"address"},{"indexed":false,"internalType":"uint256","name":"projectId","type":"uint256"},{"indexed":false,"internalType":"contract IPMPConfigureHook","name":"tokenPMPPostConfigHook","type":"address"},{"indexed":false,"internalType":"contract IPMPAugmentHook","name":"tokenPMPReadAugmentationHook","type":"address"}],"name":"ProjectHooksConfigured","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"coreContract","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"components":[{"internalType":"string","name":"key","type":"string"},{"internalType":"enum IPMPV0.ParamType","name":"configuredParamType","type":"uint8"},{"internalType":"bytes32","name":"configuredValue","type":"bytes32"},{"internalType":"bool","name":"configuringArtistString","type":"bool"},{"internalType":"string","name":"configuredValueString","type":"string"}],"indexed":false,"internalType":"struct IPMPV0.PMPInput[]","name":"pmpInputs","type":"tuple[]"},{"indexed":false,"internalType":"address[]","name":"authAddresses","type":"address[]"}],"name":"TokenParamsConfigured","type":"event"},{"inputs":[],"name":"DELEGATION_REGISTRY_TOKEN_OWNER_RIGHTS","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"coreContract","type":"address"},{"internalType":"uint256","name":"projectId","type":"uint256"},{"components":[{"internalType":"string","name":"key","type":"string"},{"components":[{"internalType":"enum IPMPV0.AuthOption","name":"authOption","type":"uint8"},{"internalType":"enum IPMPV0.ParamType","name":"paramType","type":"uint8"},{"internalType":"uint48","name":"pmpLockedAfterTimestamp","type":"uint48"},{"internalType":"address","name":"authAddress","type":"address"},{"internalType":"string[]","name":"selectOptions","type":"string[]"},{"internalType":"bytes32","name":"minRange","type":"bytes32"},{"internalType":"bytes32","name":"maxRange","type":"bytes32"}],"internalType":"struct IPMPV0.PMPConfig","name":"pmpConfig","type":"tuple"}],"internalType":"struct IPMPV0.PMPInputConfig[]","name":"pmpInputConfigs","type":"tuple[]"}],"name":"configureProject","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"coreContract","type":"address"},{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"contract IPMPConfigureHook","name":"tokenPMPPostConfigHook","type":"address"},{"internalType":"contract IPMPAugmentHook","name":"tokenPMPReadAugmentationHook","type":"address"}],"name":"configureProjectHooks","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"coreContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"components":[{"internalType":"string","name":"key","type":"string"},{"internalType":"enum IPMPV0.ParamType","name":"configuredParamType","type":"uint8"},{"internalType":"bytes32","name":"configuredValue","type":"bytes32"},{"internalType":"bool","name":"configuringArtistString","type":"bool"},{"internalType":"string","name":"configuredValueString","type":"string"}],"internalType":"struct IPMPV0.PMPInput[]","name":"pmpInputs","type":"tuple[]"}],"name":"configureTokenParams","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"delegateRegistry","outputs":[{"internalType":"contract IDelegateRegistry","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"coreContract","type":"address"},{"internalType":"uint256","name":"projectId","type":"uint256"}],"name":"getProjectConfig","outputs":[{"internalType":"string[]","name":"pmpKeys","type":"string[]"},{"internalType":"uint8","name":"configNonce","type":"uint8"},{"internalType":"contract IPMPConfigureHook","name":"tokenPMPPostConfigHook","type":"address"},{"internalType":"contract IPMPAugmentHook","name":"tokenPMPReadAugmentationHook","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"coreContract","type":"address"},{"internalType":"uint256","name":"projectId","type":"uint256"},{"internalType":"string","name":"pmpKey","type":"string"}],"name":"getProjectPMPConfig","outputs":[{"components":[{"internalType":"uint8","name":"highestConfigNonce","type":"uint8"},{"internalType":"enum IPMPV0.AuthOption","name":"authOption","type":"uint8"},{"internalType":"enum IPMPV0.ParamType","name":"paramType","type":"uint8"},{"internalType":"uint48","name":"pmpLockedAfterTimestamp","type":"uint48"},{"internalType":"address","name":"authAddress","type":"address"},{"internalType":"uint8","name":"selectOptionsLength","type":"uint8"},{"internalType":"string[]","name":"selectOptions","type":"string[]"},{"internalType":"bytes32","name":"minRange","type":"bytes32"},{"internalType":"bytes32","name":"maxRange","type":"bytes32"}],"internalType":"struct IPMPV0.PMPConfigView","name":"pmpConfigView","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"coreContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"string","name":"pmpKey","type":"string"}],"name":"getTokenPMPStorage","outputs":[{"components":[{"internalType":"enum IPMPV0.ParamType","name":"configuredParamType","type":"uint8"},{"internalType":"bytes32","name":"configuredValue","type":"bytes32"},{"internalType":"string","name":"artistConfiguredValueString","type":"string"},{"internalType":"string","name":"nonArtistConfiguredValueString","type":"string"}],"internalType":"struct IPMPV0.PMPStorage","name":"pmp","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"coreContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getTokenParams","outputs":[{"components":[{"internalType":"string","name":"key","type":"string"},{"internalType":"string","name":"value","type":"string"}],"internalType":"struct IWeb3Call.TokenParam[]","name":"tokenParams","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"},{"internalType":"address","name":"coreContract","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"isTokenOwnerOrDelegate","outputs":[{"internalType":"bool","name":"isTokenOwnerOrDelegate_","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]

60a06040523480156200001157600080fd5b5060405162004ea438038062004ea4833981016040819052620000349162000082565b60016000556001600160a01b03811660808190526040519081527f836360d1b094a7de3c3eab3d1185f3a5939467c23d4a12709dbdbf8c8d7e2f3b9060200160405180910390a150620000b4565b6000602082840312156200009557600080fd5b81516001600160a01b0381168114620000ad57600080fd5b9392505050565b608051614dce620000d66000396000818160cb0152612c060152614dce6000f3fe608060405234801561001057600080fd5b50600436106100995760003560e01c806301ffc9a71461009e57806313bfffac146100c65780633c303afb1461010557806354ca22371461011a5780636f8637a01461013d57806398a6e11814610150578063af544a611461017a578063b69c984d1461018d578063ba8dba48146101a0578063c2b4f411146101c0578063dc809b1a146101e0575b600080fd5b6100b16100ac3660046138a9565b610200565b60405190151581526020015b60405180910390f35b6100ed7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016100bd565b61011861011336600461393f565b610237565b005b61012d61012836600461399a565b610719565b6040516100bd9493929190613a16565b61011861014b36600461393f565b61077c565b61016c71706f73746d696e74706172616d657465727360701b81565b6040519081526020016100bd565b6100b1610188366004613a9d565b610a9a565b61011861019b366004613ade565b610ab0565b6101b36101ae366004613c3e565b610ca4565b6040516100bd9190613d2a565b6101d36101ce36600461399a565b610e0c565b6040516100bd9190613e4b565b6101f36101ee366004613c3e565b61102c565b6040516100bd9190613e5e565b60006001600160e01b0319821663c2b4f41160e01b148061023157506301ffc9a760e01b6001600160e01b03198316145b92915050565b61023f611202565b61024a84843361122c565b8061010081111561029d5760405162461bcd60e51b8152602060048201526018602482015277504d503a204f6e6c79203c3d2032353620636f6e6669677360401b60448201526064015b60405180910390fd5b60005b818110156103925760008484838181106102bc576102bc613ebb565b90506020028101906102ce9190613ed1565b6102d89080613ef1565b91505080158015906102eb575061010081105b61034f5760405162461bcd60e51b815260206004820152602f60248201527f504d503a20706d704b65792063616e6e6f7420626520656d707479206f72206560448201526e78636565642032353520627974657360881b6064820152608401610294565b61038985858481811061036457610364613ebb565b90506020028101906103769190613ed1565b610384906020810190613f37565b6112e8565b506001016102a0565b506001600160a01b0385166000908152600160208181526040808420888552909152822060028101549092916103cb9160ff1690613f63565b60028301805460ff191660ff831617905590506000836001600160401b038111156103f8576103f8613b31565b60405190808252806020026020018201604052801561042b57816020015b60608152602001906001900390816104165790505b50905060005b848110156104c25786868281811061044b5761044b613ebb565b905060200281019061045d9190613ed1565b6104679080613ef1565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050845185925084915081106104af576104af613ebb565b6020908102919091010152600101610431565b506104cd8184611a61565b60005b848110156106c55760008460010160006105028585815181106104f5576104f5613ebb565b6020026020010151611af9565b8152602081019190915260400160002080549091506301000000900465ffffffffffff1680158061053257504281115b61058f5760405162461bcd60e51b815260206004820152602860248201527f504d503a20706d70206973206c6f636b656420616e642063616e6e6f74206265604482015267081d5c19185d195960c21b6064820152608401610294565b50805460ff191660ff851617815560008888848181106105b1576105b1613ebb565b90506020028101906105c39190613ed1565b6105d1906020810190613f37565b6105da9061405d565b8051835491925090839061ff0019166101008360068111156105fe576105fe613c96565b021790555060208101518254839062ff000019166201000083600881111561062857610628613c96565b02179055506040810151825460608301516080840151805160ff16600160e81b0260ff60e81b196001600160a01b03909316600160481b02600160481b600160e81b031965ffffffffffff909616630100000002959095166301000000600160e81b03199094169390931793909317161783556106a9906001840190611b29565b60a0810151600283015560c001516003909101556001016104d0565b507f935e8b8dc0c19500cc0d4c29698f1af13be297f62b71eda8dd0e1a0bc6927fca88888888866040516106fd9594939291906141bf565b60405180910390a1505050506107136001600055565b50505050565b6001600160a01b03821660009081526001602090815260408083208484529091528120606091908190819061074d81611deb565b6002820154600390920154909860ff831698506001600160a01b03610100909304831697509116945092505050565b610784611202565b6001600160a01b0384166000908152600160205260408120816107a686612015565b815260200190815260200160002090506000838390506001600160401b038111156107d3576107d3613b31565b6040519080825280602002602001820160405280156107fc578160200160208202803683370190505b50905060005b83811015610a4e57600061087786868481811061082157610821613ebb565b9050602002810190610833919061435e565b61083d9080613ef1565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611af992505050565b9050600086868481811061088d5761088d613ebb565b905060200281019061089f919061435e565b6108a890614382565b6001600160a01b038a1660009081526002602081815260408084208d85528252808420878552825280842060018b019092529092209088015492935090916108fa908b908d908690859060ff16612024565b86868151811061090c5761090c613ebb565b6001600160a01b039290921660209283029190910190910152805482546201000090910460ff1690839060ff1916600183600881111561094e5761094e613c96565b02179055506008815462010000900460ff16600881111561097157610971613c96565b036109ae5782606001511561099a576080830151600283019061099490826144c2565b506109b9565b6080830151600383019061099490826144c2565b604083015160018301555b600287015461010090046001600160a01b031615610a3e57600287015460405163677e713560e01b81526101009091046001600160a01b03169063677e713590610a0b908e908e908890600401614581565b600060405180830381600087803b158015610a2557600080fd5b505af1158015610a39573d6000803e3d6000fd5b505050505b5050600190920191506108029050565b507f3dfcd927ac9e2552184b368328e6cca04f875ef453930277d7dfa53f40dbeac48686868685604051610a8695949392919061463d565b60405180910390a150506107136001600055565b6000610aa7828486612b2a565b50949350505050565b610ab8611202565b610ac384843361122c565b6001600160a01b03821615610b5757610ae38263677e713560e01b612c7b565b610b57576040805162461bcd60e51b81526020600482015260248101919091527f504d503a20746f6b656e504d50506f7374436f6e666967486f6f6b20646f657360448201527f206e6f7420696d706c656d656e742049504d50436f6e666967757265486f6f6b6064820152608401610294565b6001600160a01b03811615610bf757610b77816358f8699f60e01b612c7b565b610bf75760405162461bcd60e51b8152602060048201526044602482018190527f504d503a20746f6b656e504d50526561644175676d656e746174696f6e486f6f908201527f6b20646f6573206e6f7420696d706c656d656e742049504d504175676d656e74606482015263486f6f6b60e01b608482015260a401610294565b6001600160a01b038481166000818152600160209081526040808320888452825291829020600281018054610100600160a81b031916610100898816908102919091179091556003820180546001600160a01b031916968816968717905583519485529184018890529183015260608201929092527fd7a93580965b5dbb5c8a723af5a48167de5f41dc65e456be819bf739da05ce389060800160405180910390a1506107136001600055565b610cf360408051610120810190915260008082526020820190815260200160008152600060208201819052604082018190526060808301829052608083015260a0820181905260c09091015290565b6001600160a01b038416600090815260016020818152604080842087855290915282200181610d2185611af9565b8152602080820192909252604001600020805460ff8082168652919350918401916101009004166006811115610d5957610d59613c96565b90816006811115610d6c57610d6c613c96565b9052508054604083019062010000900460ff166008811115610d9057610d90613c96565b90816008811115610da357610da3613c96565b90525080546301000000810465ffffffffffff166060840152600160481b81046001600160a01b03166080840152600160e81b900460ff1660a0830152610dec60018201611deb565b60c0830152600281015460e0830152600301546101008201529392505050565b60606000610e1983612015565b6001600160a01b03851660009081526001602090815260408083208484529091528120919250610e4882611deb565b8051909150806001600160401b03811115610e6557610e65613b31565b604051908082528060200260200182016040528015610eaa57816020015b6040805180820190915260608082526020820152815260200190600190039081610e835790505b5094506000805b82811015610f8c576000610ed08583815181106104f5576104f5613ebb565b600081815260018801602090815260408083206001600160a01b038f168452600283528184208e85528352818420858552909252822092935090918291610f1691612c9e565b9150915081610f2757505050610f84565b6040518060400160405280888681518110610f4457610f44613ebb565b60200260200101518152602001828152508a8681518110610f6757610f67613ebb565b60200260200101819052508480610f7d9061474b565b9550505050505b600101610eb1565b5080865260038401546001600160a01b0316156110215760038401546040516358f8699f60e01b81526001600160a01b03909116906358f8699f90610fd9908b908b908b90600401614764565b600060405180830381865afa158015610ff6573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261101e91908101906147d8565b95505b505050505092915050565b61105660408051608081019091528060008152600060208201526060604082018190529081015290565b6001600160a01b038416600090815260026020908152604080832086845290915281209061108384611af9565b8152602081019190915260409081016000208151608081019092528054829060ff1660088111156110b6576110b6613c96565b60088111156110c7576110c7613c96565b8152602001600182015481526020016002820180546110e590614438565b80601f016020809104026020016040519081016040528092919081815260200182805461111190614438565b801561115e5780601f106111335761010080835404028352916020019161115e565b820191906000526020600020905b81548152906001019060200180831161114157829003601f168201915b5050505050815260200160038201805461117790614438565b80601f01602080910402602001604051908101604052809291908181526020018280546111a390614438565b80156111f05780601f106111c5576101008083540402835291602001916111f0565b820191906000526020600020905b8154815290600101906020018083116111d357829003601f168201915b50505050508152505090509392505050565b60026000540361122557604051633ee5aeb560e01b815260040160405180910390fd5b6002600055565b60405163a47d29cb60e01b8152600481018390526001600160a01b03808316919085169063a47d29cb90602401602060405180830381865afa158015611276573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061129a91906148e3565b6001600160a01b0316146112e35760405162461bcd60e51b815260206004820152601060248201526f1413540e881bdb9b1e48185c9d1a5cdd60821b6044820152606401610294565b505050565b60006112fa6040830160208401614900565b9050600061130b602084018461491b565b9050600082600881111561132157611321613c96565b0361136e5760405162461bcd60e51b815260206004820152601e60248201527f504d503a20706172616d5479706520697320756e636f6e6669677572656400006044820152606401610294565b61137e6060840160408501614936565b65ffffffffffff1615806113a857504261139e6060850160408601614936565b65ffffffffffff16115b6114285760405162461bcd60e51b8152602060048201526044602482018190527f504d503a20706d704c6f636b6564416674657254696d657374616d7020697320908201527f696e20746865207061737420616e64206e6f7420756e6c696d6974656420287a60648201526365726f2960e01b608482015260a401610294565b600681600681111561143c5761143c613c96565b11156114845760405162461bcd60e51b81526020600482015260176024820152762826a81d1024b73b30b634b21030baba3427b83a34b7b760491b6044820152606401610294565b600882600881111561149857611498613c96565b11156114b65760405162461bcd60e51b815260040161029490614951565b60088260088111156114ca576114ca613c96565b036115a05760008160068111156114e3576114e3613c96565b1480611500575060038160068111156114fe576114fe613c96565b145b8061151c5750600681600681111561151a5761151a613c96565b145b806115385750600481600681111561153657611536613c96565b145b6115a05760405162461bcd60e51b815260206004820152603360248201527f504d503a20537472696e6720706172616d73206d75737420686176652061727460448201527234b9ba159030baba3432b73a34b1b0ba34b7b760691b6064820152608401610294565b60028160068111156115b4576115b4613c96565b14806115d1575060058160068111156115cf576115cf613c96565b145b806115ed575060048160068111156115eb576115eb613c96565b145b806116095750600681600681111561160757611607613c96565b145b156116765760006116206080850160608601614981565b6001600160a01b0316036116715760405162461bcd60e51b8152602060048201526018602482015277504d503a206175746841646472657373206973207a65726f60401b6044820152606401610294565b6116dd565b60006116886080850160608601614981565b6001600160a01b0316146116dd5760405162461bcd60e51b815260206004820152601c60248201527b504d503a206175746841646472657373206973206e6f74207a65726f60201b6044820152606401610294565b60028260088111156116f1576116f1613c96565b148061170e5750600882600881111561170c5761170c613c96565b145b8061172a5750600682600881111561172857611728613c96565b145b156117f95761173c608084018461499e565b15905061175b5760405162461bcd60e51b8152600401610294906149e7565b60a0830135156117aa5760405162461bcd60e51b815260206004820152601a602482015279504d503a206d696e52616e6765206973206e6f7420656d70747960301b6044820152606401610294565b60c0830135156112e35760405162461bcd60e51b815260206004820152601a602482015279504d503a206d617852616e6765206973206e6f7420656d70747960301b6044820152606401610294565b600182600881111561180d5761180d613c96565b036118cd576000611821608085018561499e565b90501161186e5760405162461bcd60e51b815260206004820152601b60248201527a504d503a2073656c6563744f7074696f6e7320697320656d70747960281b6044820152606401610294565b61010061187e608085018561499e565b90501061175b5760405162461bcd60e51b815260206004820152601f60248201527f504d503a2073656c6563744f7074696f6e73206c656e677468203e20323535006044820152606401610294565b60038260088111156118e1576118e1613c96565b14806118fe575060048260088111156118fc576118fc613c96565b145b8061191a5750600582600881111561191857611918613c96565b145b806119365750600782600881111561193457611934613c96565b145b15611a4957611948608084018461499e565b1590506119675760405162461bcd60e51b8152600401610294906149e7565b600482600881111561197b5761197b613c96565b036119b05760a083013560c08401358082126119a95760405162461bcd60e51b815260040161029490614a1e565b5050505050565b60a083013560c08401358082106119d95760405162461bcd60e51b815260040161029490614a1e565b60078460088111156119ed576119ed613c96565b036119a9576001600160401b038111156119a95760405162461bcd60e51b815260206004820152601e60248201527f504d503a206d617852616e6765203e205f54494d455354414d505f4d415800006044820152606401610294565b60405162461bcd60e51b815260040161029490614951565b6000611a6c82611deb565b905060008151845114611a8157506001611ae9565b60005b8451811015611ae757611ad2838281518110611aa257611aa2613ebb565b6020026020010151868381518110611abc57611abc613ebb565b602002602001015161311190919063ffffffff16565b611adf5760019150611ae7565b600101611a84565b505b8015610713576107138385611b29565b600081604051602001611b0c9190614a51565b604051602081830303815290604052805190602001209050919050565b80516000819003611b4657505080546001600160a01b0319169055565b6000816001600160401b03811115611b6057611b60613b31565b604051908082528060200260200182016040528015611b89578160200160208202803683370190505b5090506000805b83811015611bfb5781838281518110611bab57611bab613ebb565b60200260200101906001600160401b031690816001600160401b031681525050848181518110611bdd57611bdd613ebb565b60200260200101515182611bf19190614a64565b9150600101611b90565b506001600160401b03811115611c515760405162461bcd60e51b815260206004820152601b60248201527a13d9999cd95d08195e18d959591cc81d5a5b9d0d8d081b1a5b5a5d602a1b6044820152606401610294565b600081611c5f856008614a77565b611c6a906008614a64565b611c749190614a64565b90506000611c83826020614a64565b6001600160401b03811115611c9a57611c9a613b31565b6040519080825280601f01601f191660200182016040528015611cc4576020820181803683370190505b5060c086901b602082015290506028810160005b86811015611d1d576000868281518110611cf457611cf4613ebb565b602090810291909101015160c01b6001600160c01b031916835250600890910190600101611cd8565b5060005b86811015611db7576000888281518110611d3d57611d3d613ebb565b6020026020010151905060008151905060006020830190506000602083611d649190614aa4565b90506000611d73602085614ab8565b905060005b82811015611d96578351885260209788019790930192600101611d78565b508015611da65782518752958601955b505060019093019250611d21915050565b505081815280611dc681613136565b88546001600160a01b0319166001600160a01b03919091161790975550505050505050565b80546060906001600160a01b0316611e2f576040805160008082526020820190925290611e28565b6060815260200190600190039081611e135790505b5092915050565b8154600090611e46906001600160a01b03166131db565b602081015190915060c01c806001600160401b03811115611e6957611e69613b31565b604051908082528060200260200182016040528015611e9c57816020015b6060815260200190600190039081611e875790505b50925060005b8181101561200d576000808084611eba856001614a64565b03611ee8576000865190506028870193506008850284015160c01c9250600886026008018103915050611f0a565b6028860192506008840283015160c01c91506008600185010283015160c01c90505b6000611f168383614acc565b6001600160401b031690506000611f2e826020614a64565b6001600160401b03811115611f4557611f45613b31565b6040519080825280601f01601f191660200182016040528015611f6f576020820181803683370190505b5090506020810160005b83811015611fd5576000816001600160401b038816611f998c6008614a77565b611fa4906028614a64565b611fae9190614a64565b611fb89190614a64565b8b0151835250602091820191611fce9082614a64565b9050611f79565b50819050828152818a8881518110611fef57611fef613ebb565b60200260200101819052505050505050508080600101915050611ea2565b505050919050565b6000610231620f424083614aa4565b815460009060ff8381169116146120a05760405162461bcd60e51b815260206004820152603a60248201527f504d503a20706172616d206e6f742070617274206f66206d6f737420726563656044820152796e746c7920636f6e6669677572656420504d5020706172616d7360301b6064820152608401610294565b825462010000900460ff1660088111156120bc576120bc613c96565b846020015160088111156120d2576120d2613c96565b146121195760405162461bcd60e51b81526020600482015260176024820152760a09aa07440e0c2e4c2daa8f2e0ca40dad2e6dac2e8c6d604b1b6044820152606401610294565b60008460200151600881111561213157612131613c96565b0361218a5760405162461bcd60e51b8152602060048201526024808201527f504d503a20696e70757420706172616d5479706520697320756e636f6e6669676044820152631d5c995960e21b6064820152608401610294565b8254600090610100900460ff16818160068111156121aa576121aa613c96565b14806121c7575060048160068111156121c5576121c5613c96565b145b806121e3575060038160068111156121e1576121e1613c96565b145b806121ff575060068160068111156121fd576121fd613c96565b145b156122175760006122118989336131fc565b94509250505b811580156122895750600281600681111561223457612234613c96565b14806122515750600581600681111561224f5761224f613c96565b145b8061226d5750600481600681111561226b5761226b613c96565b145b806122895750600681600681111561228757612287613c96565b145b156122a8578454600160481b90046001600160a01b0316925033831491505b8115801561231a575060018160068111156122c5576122c5613c96565b14806122e2575060038160068111156122e0576122e0613c96565b145b806122fe575060058160068111156122fc576122fc613c96565b145b8061231a5750600681600681111561231857612318613c96565b145b1561233257600061232c898933612b2a565b94509250505b5080612675578354610100900460ff16600081600681111561235657612356613c96565b0361239f5760405162461bcd60e51b81526020600482015260196024820152781413540e88185c9d1a5cdd08185d5d1a081c995c5d5a5c9959603a1b6044820152606401610294565b60018160068111156123b3576123b3613c96565b036124005760405162461bcd60e51b815260206004820152601e60248201527f504d503a20746f6b656e206f776e6572206175746820726571756972656400006044820152606401610294565b600381600681111561241457612414613c96565b036124735760405162461bcd60e51b815260206004820152602960248201527f504d503a2061727469737420616e6420746f6b656e206f776e65722061757468604482015268081c995c5d5a5c995960ba1b6064820152608401610294565b600281600681111561248757612487613c96565b036124d15760405162461bcd60e51b815260206004820152601a6024820152791413540e881859191c995cdcc8185d5d1a081c995c5d5a5c995960321b6044820152606401610294565b60068160068111156124e5576124e5613c96565b036125505760405162461bcd60e51b815260206004820152603560248201527f504d503a2061727469737420616e6420746f6b656e206f776e657220616e64206044820152741859191c995cdcc8185d5d1a081c995c5d5a5c9959605a1b6064820152608401610294565b600481600681111561256457612564613c96565b036125bf5760405162461bcd60e51b815260206004820152602560248201527f504d503a2061727469737420616e6420616464726573732061757468207265716044820152641d5a5c995960da1b6064820152608401610294565b60058160068111156125d3576125d3613c96565b036126335760405162461bcd60e51b815260206004820152602a60248201527f504d503a20746f6b656e206f776e657220616e642061646472657373206175746044820152691a081c995c5d5a5c995960b21b6064820152608401610294565b60405162461bcd60e51b81526020600482015260176024820152762826a81d1034b73b30b634b21030baba3427b83a34b7b760491b6044820152606401610294565b835462010000900460ff16600881600881111561269457612694613c96565b146129265760018160088111156126ad576126ad613c96565b036127255784546040870151600160e81b90910460ff16116127205760405162461bcd60e51b815260206004820152602660248201527f504d503a2073656c6563744f7074696f6e7320696e646578206f7574206f6620604482015265626f756e647360d01b6064820152608401610294565b612926565b600281600881111561273957612739613c96565b036127ab5760408601511580612753575060408601516001145b6127205760405162461bcd60e51b8152602060048201526024808201527f504d503a20626f6f6c20706172616d2076616c7565206d7573742062652030206044820152636f72203160e01b6064820152608401610294565b60038160088111156127bf576127bf613c96565b14806127dc575060058160088111156127da576127da613c96565b145b1561281d57846002015486604001511015801561280157508460030154866040015111155b6127205760405162461bcd60e51b815260040161029490614aec565b600481600881111561283157612831613c96565b0361287157600285015460408701511280159061280157506003850154604087015113156127205760405162461bcd60e51b815260040161029490614aec565b600781600881111561288557612885613c96565b036128a55760408601516001600160401b03108015906128015750612926565b60068160088111156128b9576128b9613c96565b036128e557604086015162ffffff10156127205760405162461bcd60e51b815260040161029490614b23565b60405162461bcd60e51b8152602060048201526016602482015275504d503a20696e76616c696420706172616d5479706560501b6044820152606401610294565b6008855462010000900460ff16600881111561294457612944613c96565b03612a39576040860151156129ae5760405162461bcd60e51b815260206004820152602a60248201527f504d503a2076616c7565206d75737420626520656d70747920666f7220737472604482015269696e6720706172616d7360b01b6064820152608401610294565b856060015115612a345760006129c58989336131fc565b50905080612a325760405162461bcd60e51b815260206004820152603460248201527f504d503a20617274697374206175746820726571756972656420746f20636f6e6044820152736669677572652061727469737420737472696e6760601b6064820152608401610294565b505b612b1f565b60808601515115612aa75760405162461bcd60e51b815260206004820152603260248201527f504d503a206e6f6e2d737472696e6720706172616d206d757374206861766520604482015271656d70747920737472696e672076616c756560701b6064820152608401610294565b856060015115612b1f5760405162461bcd60e51b815260206004820152603d60248201527f504d503a2061727469737420737472696e672063616e6e6f7420626520636f6e60448201527f6669677572656420666f72206e6f6e2d737472696e6720706172616d730000006064820152608401610294565b505095945050505050565b6040516331a9108f60e11b81526004810184905260009081906001600160a01b03851690636352211e90602401602060405180830381865afa158015612b74573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b9891906148e3565b9050826001600160a01b0316816001600160a01b03161480612c715750604051632e7cda1d60e21b81526001600160a01b038481166004830152828116602483015285811660448301526064820187905271706f73746d696e74706172616d657465727360701b60848301527f0000000000000000000000000000000000000000000000000000000000000000169063b9f368749060a401602060405180830381865afa158015612c4d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c719190614b53565b9150935093915050565b6000612c8683613298565b8015612c975750612c9783836132cb565b9392505050565b805460009060609060ff1682816008811115612cbc57612cbc613c96565b1480612cf45750845462010000900460ff166008811115612cdf57612cdf613c96565b816008811115612cf157612cf1613c96565b14155b15612d1257505060408051602081019091526000808252915061310a565b6008855462010000900460ff166008811115612d3057612d30613c96565b03612e2e576000846002018054612d4690614438565b90501115612dea57600184600201808054612d6090614438565b80601f0160208091040260200160405190810160405280929190818152602001828054612d8c90614438565b8015612dd95780601f10612dae57610100808354040283529160200191612dd9565b820191906000526020600020905b815481529060010190602001808311612dbc57829003601f168201915b50505050509050925092505061310a565b6000846003018054612dfb90614438565b90501115612e1557600184600301808054612d6090614438565b505060408051602081019091526000808252915061310a565b6001816008811115612e4257612e42613c96565b03612e945784546001850154600160e81b90910460ff1611612e7757505060408051602081019091526000808252915061310a565b600184810154612e8a9087830190613355565b925092505061310a565b6002816008811115612ea857612ea8613c96565b03612f025760018481015415612eda57604051806040016040528060048152602001637472756560e01b815250612e8a565b6040518060400160405280600581526020016466616c736560d81b815250925092505061310a565b6003816008811115612f1657612f16613c96565b1480612f3357506006816008811115612f3157612f31613c96565b145b80612f4f57506007816008811115612f4d57612f4d613c96565b145b80612f6b57506005816008811115612f6957612f69613c96565b145b1561305c57600184015460006006836008811115612f8b57612f8b613c96565b14612f9a576003870154612f9f565b62ffffff5b905060006006846008811115612fb757612fb7613c96565b14612fc6576002880154612fc9565b60005b905080831080612fd857508183115b15612ffc57600060405180602001604052806000815250955095505050505061310a565b600584600881111561301057613010613c96565b0361302d576001613020846134ff565b955095505050505061310a565b600684600881111561304157613041613c96565b03613051576001613020846135ac565b600161302084613695565b600481600881111561307057613070613c96565b036130c7576001840154600286015481128061308f5750600386015481135b156130b15760006040518060200160405280600081525093509350505061310a565b60016130bc82613727565b93509350505061310a565b60405162461bcd60e51b8152602060048201526018602482015277504d503a20556e68616e646c656420506172616d5479706560401b6044820152606401610294565b9250929050565b600081518351148015612c975750508051602091820120825192909101919091201490565b6000808260405160200161314a9190614b70565b604051602081830303815290604052905060008160405160200161316e9190614b96565b60405160208183030381529060405290508051602082016000f092506001600160a01b0383166131d45760405162461bcd60e51b81526020600482015260116024820152701111541313d65351539517d19052531151607a1b6044820152606401610294565b5050919050565b60606102318260016131f7816001600160a01b0384163b614bca565b613799565b600080600061320a86612015565b60405163a47d29cb60e01b8152600481018290529091506001600160a01b0386169063a47d29cb90602401602060405180830381865afa158015613252573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061327691906148e3565b9150836001600160a01b0316826001600160a01b031614925050935093915050565b60006132ab826301ffc9a760e01b6132cb565b801561023157506132c4826001600160e01b03196132cb565b1592915050565b6040516001600160e01b031982166024820152600090819060440160408051601f19818403018152919052602080820180516001600160e01b03166301ffc9a760e01b178152825192935060009283928392909183918a617530fa92503d9150600051905082801561333e575060208210155b801561334a5750600081115b979650505050505050565b81546060906001600160a01b031661337f5760405162461bcd60e51b815260040161029490614bdd565b8254600090613396906001600160a01b03166131db565b9050600080600080602085015160c01c90508087106133c75760405162461bcd60e51b815260040161029490614bdd565b806133d3886001614a64565b03613401576000855190506028860194506008880285015160c01c9350600882026008018103925050613423565b6028850193506008870284015160c01c92506008600188010284015160c01c91505b600061342f8484614acc565b6001600160401b031690506000613447826020614a64565b6001600160401b0381111561345e5761345e613b31565b6040519080825280601f01601f191660200182016040528015613488576020820181803683370190505b5090506020810160005b838110156134ee576000816001600160401b0389166134b2886008614a77565b6134bd906028614a64565b6134c79190614a64565b6134d19190614a64565b8a01518352506020918201916134e79082614a64565b9050613492565b505090815298975050505050505050565b6060600061350e600a80614cee565b6135189084614aa4565b90506000613527600a80614cee565b6135319085614ab8565b9050600061353e83613695565b9050600061354b83613695565b90505b600a8151101561357f57806040516020016135699190614cfa565b604051602081830303815290604052905061354e565b8181604051602001613592929190614d16565b604051602081830303815290604052945050505050919050565b6040805160078082528183019092526060918391600091602082018180368337019050509050602360f81b816000815181106135ea576135ea613ebb565b60200101906001600160f81b031916908160001a90535060065b8015613676576f181899199a1a9b1b9c1cb0b131b232b360811b83600f166010811061363257613632613ebb565b1a60f81b82828151811061364857613648613ebb565b60200101906001600160f81b031916908160001a90535060049290921c9161366f81614d52565b9050613604565b508115612c975760405162461bcd60e51b815260040161029490614b23565b606060006136a2836137bc565b60010190506000816001600160401b038111156136c1576136c1613b31565b6040519080825280601f01601f1916602001820160405280156136eb576020820181803683370190505b5090508181016020015b600019016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a85049450846136f557509392505050565b6060600082126137465760405180602001604052806000815250613761565b604051806040016040528060018152602001602d60f81b8152505b61377261376d84613892565b613695565b604051602001613783929190614d69565b6040516020818303038152906040529050919050565b60408051603f8301601f19168101909152818152818360208301863c9392505050565b60008072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b83106137fb5772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6904ee2d6d415b85acef8160201b8310613825576904ee2d6d415b85acef8160201b830492506020015b662386f26fc10000831061384357662386f26fc10000830492506010015b6305f5e100831061385b576305f5e100830492506008015b612710831061386f57612710830492506004015b60648310613881576064830492506002015b600a83106102315760010192915050565b6000808212156138a55781600003610231565b5090565b6000602082840312156138bb57600080fd5b81356001600160e01b031981168114612c9757600080fd5b6001600160a01b03811681146138e857600080fd5b50565b80356138f6816138d3565b919050565b60008083601f84011261390d57600080fd5b5081356001600160401b0381111561392457600080fd5b6020830191508360208260051b850101111561310a57600080fd5b6000806000806060858703121561395557600080fd5b8435613960816138d3565b93506020850135925060408501356001600160401b0381111561398257600080fd5b61398e878288016138fb565b95989497509550505050565b600080604083850312156139ad57600080fd5b82356139b8816138d3565b946020939093013593505050565b60005b838110156139e15781810151838201526020016139c9565b50506000910152565b60008151808452613a028160208601602086016139c6565b601f01601f19169290920160200192915050565b6000608082016080835280875180835260a08501915060a08160051b86010192506020808a0160005b83811015613a6d57609f19888703018552613a5b8683516139ea565b95509382019390820190600101613a3f565b5050505060ff9690961660208401526001600160a01b039485166040840152929093166060909101529392505050565b600080600060608486031215613ab257600080fd5b8335613abd816138d3565b92506020840135613acd816138d3565b929592945050506040919091013590565b60008060008060808587031215613af457600080fd5b8435613aff816138d3565b9350602085013592506040850135613b16816138d3565b91506060850135613b26816138d3565b939692955090935050565b634e487b7160e01b600052604160045260246000fd5b60405160e081016001600160401b0381118282101715613b6957613b69613b31565b60405290565b604080519081016001600160401b0381118282101715613b6957613b69613b31565b604051601f8201601f191681016001600160401b0381118282101715613bb957613bb9613b31565b604052919050565b60006001600160401b03821115613bda57613bda613b31565b50601f01601f191660200190565b600082601f830112613bf957600080fd5b8135613c0c613c0782613bc1565b613b91565b818152846020838601011115613c2157600080fd5b816020850160208301376000918101602001919091529392505050565b600080600060608486031215613c5357600080fd5b8335613c5e816138d3565b92506020840135915060408401356001600160401b03811115613c8057600080fd5b613c8c86828701613be8565b9150509250925092565b634e487b7160e01b600052602160045260246000fd5b60078110613cbc57613cbc613c96565b9052565b60098110613cbc57613cbc613c96565b60008282518085526020808601955060208260051b8401016020860160005b84811015613d1d57601f19868403018952613d0b8383516139ea565b98840198925090830190600101613cef565b5090979650505050505050565b60208152613d3e60208201835160ff169052565b60006020830151613d526040840182613cac565b506040830151613d656060840182613cc0565b50606083015165ffffffffffff811660808401525060808301516001600160a01b03811660a08401525060a083015160ff811660c08401525060c08301516101208060e0850152613dba610140850183613cd0565b60e086015161010086810191909152909501519301929092525090919050565b600082825180855260208086019550808260051b84010181860160005b84811015613d1d57601f19868403018952815160408151818652613e1d828701826139ea565b91505085820151915084810386860152613e3781836139ea565b9a86019a9450505090830190600101613df7565b602081526000612c976020830184613dda565b60208152613e70602082018351613cc0565b602082015160408201526000604083015160806060840152613e9560a08401826139ea565b90506060840151601f19848303016080850152613eb282826139ea565b95945050505050565b634e487b7160e01b600052603260045260246000fd5b60008235603e19833603018112613ee757600080fd5b9190910192915050565b6000808335601e19843603018112613f0857600080fd5b8301803591506001600160401b03821115613f2257600080fd5b60200191503681900382131561310a57600080fd5b6000823560de19833603018112613ee757600080fd5b634e487b7160e01b600052601160045260246000fd5b60ff818116838216019081111561023157610231613f4d565b8035600781106138f657600080fd5b8035600981106138f657600080fd5b803565ffffffffffff811681146138f657600080fd5b60006001600160401b03821115613fc957613fc9613b31565b5060051b60200190565b600082601f830112613fe457600080fd5b81356020613ff4613c0783613fb0565b82815260059290921b8401810191818101908684111561401357600080fd5b8286015b848110156140525780356001600160401b038111156140365760008081fd5b6140448986838b0101613be8565b845250918301918301614017565b509695505050505050565b600060e0823603121561406f57600080fd5b614077613b47565b61408083613f7c565b815261408e60208401613f8b565b602082015261409f60408401613f9a565b60408201526140b0606084016138eb565b606082015260808301356001600160401b038111156140ce57600080fd5b6140da36828601613fd3565b60808301525060a083013560a082015260c083013560c082015280915050919050565b6000808335601e1984360301811261411457600080fd5b83016020810192503590506001600160401b0381111561413357600080fd5b80360382131561310a57600080fd5b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6000838385526020808601955060208560051b8301018460005b87811015613d1d57848303601f190189526141a082886140fd565b6141ab858284614142565b9a86019a9450505090830190600101614185565b6001600160a01b038681168252602080830187905260806040808501829052848201879052600093919260a0929183870160058a901b880185018b885b8c81101561433b578a8303609f190184528135368f9003603e1901811261422257600080fd5b8e0161422e81806140fd565b87865261423e8887018284614142565b9150508782013560de1983360301811261425757600080fd5b858203898701529091019060e06142768261427185613f7c565b613cac565b614281898401613f8b565b61428d8a840182613cc0565b5065ffffffffffff6142a0898501613f9a565b16888301526060808401356142b4816138d3565b8b1690830152828c013536849003601e190181126142d157600080fd5b830189810190356001600160401b038111156142ec57600080fd5b8060051b36038213156142fe57600080fd5b828e850152614310838501828461416b565b858e0135858f015260c0958601359590940194909452505094870194935050908501906001016141fc565b505060ff8a1660608a0152965061435495505050505050565b9695505050505050565b60008235609e19833603018112613ee757600080fd5b80151581146138e857600080fd5b600060a0823603121561439457600080fd5b60405160a081016001600160401b0380821183831017156143b7576143b7613b31565b8160405284359150808211156143cc57600080fd5b6143d836838701613be8565b83526143e660208601613f8b565b6020840152604085013560408401526060850135915061440582614374565b816060840152608085013591508082111561441f57600080fd5b5061442c36828601613be8565b60808301525092915050565b600181811c9082168061444c57607f821691505b60208210810361446c57634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156112e3576000816000526020600020601f850160051c8101602086101561449b5750805b601f850160051c820191505b818110156144ba578281556001016144a7565b505050505050565b81516001600160401b038111156144db576144db613b31565b6144ef816144e98454614438565b84614472565b602080601f831160018114614524576000841561450c5750858301515b600019600386901b1c1916600185901b1785556144ba565b600085815260208120601f198616915b8281101561455357888601518255948401946001909101908401614534565b50858210156145715787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60018060a01b0384168152826020820152606060408201526000825160a060608401526145b26101008401826139ea565b905060208401516145c66080850182613cc0565b50604084015160a08401526060840151151560c08401526080840151838203605f190160e085015261334a82826139ea565b60008151808452602080850194506020840160005b838110156146325781516001600160a01b03168752958201959082019060010161460d565b509495945050505050565b6001600160a01b038616815260208082018690526080604080840182905283820186905260009260a091828601600589901b870184018a87805b8c811015614725578a8403609f190185528235368f9003609e1901811261469c578283fd5b8e016146a881806140fd565b8a87526146b88b88018284614142565b9150506146c6898301613f8b565b6146d28a880182613cc0565b5087820135888701526060808301356146ea81614374565b1515908701526146fc828c01836140fd565b92508682038c880152614710828483614142565b978a0197965050509287019250600101614677565b505050878103606089015261473a818a6145f8565b9d9c50505050505050505050505050565b60006001820161475d5761475d613f4d565b5060010190565b60018060a01b0384168152826020820152606060408201526000613eb26060830184613dda565b600082601f83011261479c57600080fd5b81516147aa613c0782613bc1565b8181528460208386010111156147bf57600080fd5b6147d08260208301602087016139c6565b949350505050565b600060208083850312156147eb57600080fd5b82516001600160401b038082111561480257600080fd5b818501915085601f83011261481657600080fd5b8151614824613c0782613fb0565b81815260059190911b8301840190848101908883111561484357600080fd5b8585015b838110156148d65780518581111561485f5760008081fd5b86016040818c03601f19018113156148775760008081fd5b61487f613b6f565b89830151888111156148915760008081fd5b61489f8e8c8387010161478b565b8252509082015190878211156148b55760008081fd5b6148c38d8b8486010161478b565b818b015285525050918601918601614847565b5098975050505050505050565b6000602082840312156148f557600080fd5b8151612c97816138d3565b60006020828403121561491257600080fd5b612c9782613f8b565b60006020828403121561492d57600080fd5b612c9782613f7c565b60006020828403121561494857600080fd5b612c9782613f9a565b602080825260169082015275504d503a20496e76616c696420706172616d5479706560501b604082015260600190565b60006020828403121561499357600080fd5b8135612c97816138d3565b6000808335601e198436030181126149b557600080fd5b8301803591506001600160401b038211156149cf57600080fd5b6020019150600581901b360382131561310a57600080fd5b6020808252601f908201527f504d503a2073656c6563744f7074696f6e73206973206e6f7420656d70747900604082015260600190565b602080825260199082015278504d503a206d696e52616e6765203e3d206d617852616e676560381b604082015260600190565b602081526000612c9760208301846139ea565b8082018082111561023157610231613f4d565b808202811582820484141761023157610231613f4d565b634e487b7160e01b600052601260045260246000fd5b600082614ab357614ab3614a8e565b500490565b600082614ac757614ac7614a8e565b500690565b6001600160401b03828116828216039080821115611e2857611e28613f4d565b6020808252601e908201527f504d503a20706172616d2076616c7565206f7574206f6620626f756e64730000604082015260600190565b6020808252601690820152752826a81d1034b73b30b634b2103432bc1031b7b637b960511b604082015260600190565b600060208284031215614b6557600080fd5b8151612c9781614374565b6000815260008251614b898160018501602087016139c6565b9190910160010192915050565b6a600b5981380380925939f360a81b81528151600090614bbd81600b8501602087016139c6565b91909101600b0192915050565b8181038181111561023157610231613f4d565b602080825260139082015272496e646578206f7574206f6620626f756e647360681b604082015260600190565b600181815b80851115614c45578160001904821115614c2b57614c2b613f4d565b80851615614c3857918102915b93841c9390800290614c0f565b509250929050565b600082614c5c57506001610231565b81614c6957506000610231565b8160018114614c7f5760028114614c8957614ca5565b6001915050610231565b60ff841115614c9a57614c9a613f4d565b50506001821b610231565b5060208310610133831016604e8410600b8410161715614cc8575081810a610231565b614cd28383614c0a565b8060001904821115614ce657614ce6613f4d565b029392505050565b6000612c978383614c4d565b600360fc1b815260008251614b898160018501602087016139c6565b60008351614d288184602088016139c6565b601760f91b9083019081528351614d468160018401602088016139c6565b01600101949350505050565b600081614d6157614d61613f4d565b506000190190565b60008351614d7b8184602088016139c6565b835190830190614d8f8183602088016139c6565b0194935050505056fea26469706673582212200bede6c2fd14d9777b1f2a93c2b7e9c38cd1e28475e132481a6b6b474e0a5f9064736f6c6343000816003300000000000000000000000000000000000000447e69651d841bd8d104bed493

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106100995760003560e01c806301ffc9a71461009e57806313bfffac146100c65780633c303afb1461010557806354ca22371461011a5780636f8637a01461013d57806398a6e11814610150578063af544a611461017a578063b69c984d1461018d578063ba8dba48146101a0578063c2b4f411146101c0578063dc809b1a146101e0575b600080fd5b6100b16100ac3660046138a9565b610200565b60405190151581526020015b60405180910390f35b6100ed7f00000000000000000000000000000000000000447e69651d841bd8d104bed49381565b6040516001600160a01b0390911681526020016100bd565b61011861011336600461393f565b610237565b005b61012d61012836600461399a565b610719565b6040516100bd9493929190613a16565b61011861014b36600461393f565b61077c565b61016c71706f73746d696e74706172616d657465727360701b81565b6040519081526020016100bd565b6100b1610188366004613a9d565b610a9a565b61011861019b366004613ade565b610ab0565b6101b36101ae366004613c3e565b610ca4565b6040516100bd9190613d2a565b6101d36101ce36600461399a565b610e0c565b6040516100bd9190613e4b565b6101f36101ee366004613c3e565b61102c565b6040516100bd9190613e5e565b60006001600160e01b0319821663c2b4f41160e01b148061023157506301ffc9a760e01b6001600160e01b03198316145b92915050565b61023f611202565b61024a84843361122c565b8061010081111561029d5760405162461bcd60e51b8152602060048201526018602482015277504d503a204f6e6c79203c3d2032353620636f6e6669677360401b60448201526064015b60405180910390fd5b60005b818110156103925760008484838181106102bc576102bc613ebb565b90506020028101906102ce9190613ed1565b6102d89080613ef1565b91505080158015906102eb575061010081105b61034f5760405162461bcd60e51b815260206004820152602f60248201527f504d503a20706d704b65792063616e6e6f7420626520656d707479206f72206560448201526e78636565642032353520627974657360881b6064820152608401610294565b61038985858481811061036457610364613ebb565b90506020028101906103769190613ed1565b610384906020810190613f37565b6112e8565b506001016102a0565b506001600160a01b0385166000908152600160208181526040808420888552909152822060028101549092916103cb9160ff1690613f63565b60028301805460ff191660ff831617905590506000836001600160401b038111156103f8576103f8613b31565b60405190808252806020026020018201604052801561042b57816020015b60608152602001906001900390816104165790505b50905060005b848110156104c25786868281811061044b5761044b613ebb565b905060200281019061045d9190613ed1565b6104679080613ef1565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525050845185925084915081106104af576104af613ebb565b6020908102919091010152600101610431565b506104cd8184611a61565b60005b848110156106c55760008460010160006105028585815181106104f5576104f5613ebb565b6020026020010151611af9565b8152602081019190915260400160002080549091506301000000900465ffffffffffff1680158061053257504281115b61058f5760405162461bcd60e51b815260206004820152602860248201527f504d503a20706d70206973206c6f636b656420616e642063616e6e6f74206265604482015267081d5c19185d195960c21b6064820152608401610294565b50805460ff191660ff851617815560008888848181106105b1576105b1613ebb565b90506020028101906105c39190613ed1565b6105d1906020810190613f37565b6105da9061405d565b8051835491925090839061ff0019166101008360068111156105fe576105fe613c96565b021790555060208101518254839062ff000019166201000083600881111561062857610628613c96565b02179055506040810151825460608301516080840151805160ff16600160e81b0260ff60e81b196001600160a01b03909316600160481b02600160481b600160e81b031965ffffffffffff909616630100000002959095166301000000600160e81b03199094169390931793909317161783556106a9906001840190611b29565b60a0810151600283015560c001516003909101556001016104d0565b507f935e8b8dc0c19500cc0d4c29698f1af13be297f62b71eda8dd0e1a0bc6927fca88888888866040516106fd9594939291906141bf565b60405180910390a1505050506107136001600055565b50505050565b6001600160a01b03821660009081526001602090815260408083208484529091528120606091908190819061074d81611deb565b6002820154600390920154909860ff831698506001600160a01b03610100909304831697509116945092505050565b610784611202565b6001600160a01b0384166000908152600160205260408120816107a686612015565b815260200190815260200160002090506000838390506001600160401b038111156107d3576107d3613b31565b6040519080825280602002602001820160405280156107fc578160200160208202803683370190505b50905060005b83811015610a4e57600061087786868481811061082157610821613ebb565b9050602002810190610833919061435e565b61083d9080613ef1565b8080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250611af992505050565b9050600086868481811061088d5761088d613ebb565b905060200281019061089f919061435e565b6108a890614382565b6001600160a01b038a1660009081526002602081815260408084208d85528252808420878552825280842060018b019092529092209088015492935090916108fa908b908d908690859060ff16612024565b86868151811061090c5761090c613ebb565b6001600160a01b039290921660209283029190910190910152805482546201000090910460ff1690839060ff1916600183600881111561094e5761094e613c96565b02179055506008815462010000900460ff16600881111561097157610971613c96565b036109ae5782606001511561099a576080830151600283019061099490826144c2565b506109b9565b6080830151600383019061099490826144c2565b604083015160018301555b600287015461010090046001600160a01b031615610a3e57600287015460405163677e713560e01b81526101009091046001600160a01b03169063677e713590610a0b908e908e908890600401614581565b600060405180830381600087803b158015610a2557600080fd5b505af1158015610a39573d6000803e3d6000fd5b505050505b5050600190920191506108029050565b507f3dfcd927ac9e2552184b368328e6cca04f875ef453930277d7dfa53f40dbeac48686868685604051610a8695949392919061463d565b60405180910390a150506107136001600055565b6000610aa7828486612b2a565b50949350505050565b610ab8611202565b610ac384843361122c565b6001600160a01b03821615610b5757610ae38263677e713560e01b612c7b565b610b57576040805162461bcd60e51b81526020600482015260248101919091527f504d503a20746f6b656e504d50506f7374436f6e666967486f6f6b20646f657360448201527f206e6f7420696d706c656d656e742049504d50436f6e666967757265486f6f6b6064820152608401610294565b6001600160a01b03811615610bf757610b77816358f8699f60e01b612c7b565b610bf75760405162461bcd60e51b8152602060048201526044602482018190527f504d503a20746f6b656e504d50526561644175676d656e746174696f6e486f6f908201527f6b20646f6573206e6f7420696d706c656d656e742049504d504175676d656e74606482015263486f6f6b60e01b608482015260a401610294565b6001600160a01b038481166000818152600160209081526040808320888452825291829020600281018054610100600160a81b031916610100898816908102919091179091556003820180546001600160a01b031916968816968717905583519485529184018890529183015260608201929092527fd7a93580965b5dbb5c8a723af5a48167de5f41dc65e456be819bf739da05ce389060800160405180910390a1506107136001600055565b610cf360408051610120810190915260008082526020820190815260200160008152600060208201819052604082018190526060808301829052608083015260a0820181905260c09091015290565b6001600160a01b038416600090815260016020818152604080842087855290915282200181610d2185611af9565b8152602080820192909252604001600020805460ff8082168652919350918401916101009004166006811115610d5957610d59613c96565b90816006811115610d6c57610d6c613c96565b9052508054604083019062010000900460ff166008811115610d9057610d90613c96565b90816008811115610da357610da3613c96565b90525080546301000000810465ffffffffffff166060840152600160481b81046001600160a01b03166080840152600160e81b900460ff1660a0830152610dec60018201611deb565b60c0830152600281015460e0830152600301546101008201529392505050565b60606000610e1983612015565b6001600160a01b03851660009081526001602090815260408083208484529091528120919250610e4882611deb565b8051909150806001600160401b03811115610e6557610e65613b31565b604051908082528060200260200182016040528015610eaa57816020015b6040805180820190915260608082526020820152815260200190600190039081610e835790505b5094506000805b82811015610f8c576000610ed08583815181106104f5576104f5613ebb565b600081815260018801602090815260408083206001600160a01b038f168452600283528184208e85528352818420858552909252822092935090918291610f1691612c9e565b9150915081610f2757505050610f84565b6040518060400160405280888681518110610f4457610f44613ebb565b60200260200101518152602001828152508a8681518110610f6757610f67613ebb565b60200260200101819052508480610f7d9061474b565b9550505050505b600101610eb1565b5080865260038401546001600160a01b0316156110215760038401546040516358f8699f60e01b81526001600160a01b03909116906358f8699f90610fd9908b908b908b90600401614764565b600060405180830381865afa158015610ff6573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f1916820160405261101e91908101906147d8565b95505b505050505092915050565b61105660408051608081019091528060008152600060208201526060604082018190529081015290565b6001600160a01b038416600090815260026020908152604080832086845290915281209061108384611af9565b8152602081019190915260409081016000208151608081019092528054829060ff1660088111156110b6576110b6613c96565b60088111156110c7576110c7613c96565b8152602001600182015481526020016002820180546110e590614438565b80601f016020809104026020016040519081016040528092919081815260200182805461111190614438565b801561115e5780601f106111335761010080835404028352916020019161115e565b820191906000526020600020905b81548152906001019060200180831161114157829003601f168201915b5050505050815260200160038201805461117790614438565b80601f01602080910402602001604051908101604052809291908181526020018280546111a390614438565b80156111f05780601f106111c5576101008083540402835291602001916111f0565b820191906000526020600020905b8154815290600101906020018083116111d357829003601f168201915b50505050508152505090509392505050565b60026000540361122557604051633ee5aeb560e01b815260040160405180910390fd5b6002600055565b60405163a47d29cb60e01b8152600481018390526001600160a01b03808316919085169063a47d29cb90602401602060405180830381865afa158015611276573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061129a91906148e3565b6001600160a01b0316146112e35760405162461bcd60e51b815260206004820152601060248201526f1413540e881bdb9b1e48185c9d1a5cdd60821b6044820152606401610294565b505050565b60006112fa6040830160208401614900565b9050600061130b602084018461491b565b9050600082600881111561132157611321613c96565b0361136e5760405162461bcd60e51b815260206004820152601e60248201527f504d503a20706172616d5479706520697320756e636f6e6669677572656400006044820152606401610294565b61137e6060840160408501614936565b65ffffffffffff1615806113a857504261139e6060850160408601614936565b65ffffffffffff16115b6114285760405162461bcd60e51b8152602060048201526044602482018190527f504d503a20706d704c6f636b6564416674657254696d657374616d7020697320908201527f696e20746865207061737420616e64206e6f7420756e6c696d6974656420287a60648201526365726f2960e01b608482015260a401610294565b600681600681111561143c5761143c613c96565b11156114845760405162461bcd60e51b81526020600482015260176024820152762826a81d1024b73b30b634b21030baba3427b83a34b7b760491b6044820152606401610294565b600882600881111561149857611498613c96565b11156114b65760405162461bcd60e51b815260040161029490614951565b60088260088111156114ca576114ca613c96565b036115a05760008160068111156114e3576114e3613c96565b1480611500575060038160068111156114fe576114fe613c96565b145b8061151c5750600681600681111561151a5761151a613c96565b145b806115385750600481600681111561153657611536613c96565b145b6115a05760405162461bcd60e51b815260206004820152603360248201527f504d503a20537472696e6720706172616d73206d75737420686176652061727460448201527234b9ba159030baba3432b73a34b1b0ba34b7b760691b6064820152608401610294565b60028160068111156115b4576115b4613c96565b14806115d1575060058160068111156115cf576115cf613c96565b145b806115ed575060048160068111156115eb576115eb613c96565b145b806116095750600681600681111561160757611607613c96565b145b156116765760006116206080850160608601614981565b6001600160a01b0316036116715760405162461bcd60e51b8152602060048201526018602482015277504d503a206175746841646472657373206973207a65726f60401b6044820152606401610294565b6116dd565b60006116886080850160608601614981565b6001600160a01b0316146116dd5760405162461bcd60e51b815260206004820152601c60248201527b504d503a206175746841646472657373206973206e6f74207a65726f60201b6044820152606401610294565b60028260088111156116f1576116f1613c96565b148061170e5750600882600881111561170c5761170c613c96565b145b8061172a5750600682600881111561172857611728613c96565b145b156117f95761173c608084018461499e565b15905061175b5760405162461bcd60e51b8152600401610294906149e7565b60a0830135156117aa5760405162461bcd60e51b815260206004820152601a602482015279504d503a206d696e52616e6765206973206e6f7420656d70747960301b6044820152606401610294565b60c0830135156112e35760405162461bcd60e51b815260206004820152601a602482015279504d503a206d617852616e6765206973206e6f7420656d70747960301b6044820152606401610294565b600182600881111561180d5761180d613c96565b036118cd576000611821608085018561499e565b90501161186e5760405162461bcd60e51b815260206004820152601b60248201527a504d503a2073656c6563744f7074696f6e7320697320656d70747960281b6044820152606401610294565b61010061187e608085018561499e565b90501061175b5760405162461bcd60e51b815260206004820152601f60248201527f504d503a2073656c6563744f7074696f6e73206c656e677468203e20323535006044820152606401610294565b60038260088111156118e1576118e1613c96565b14806118fe575060048260088111156118fc576118fc613c96565b145b8061191a5750600582600881111561191857611918613c96565b145b806119365750600782600881111561193457611934613c96565b145b15611a4957611948608084018461499e565b1590506119675760405162461bcd60e51b8152600401610294906149e7565b600482600881111561197b5761197b613c96565b036119b05760a083013560c08401358082126119a95760405162461bcd60e51b815260040161029490614a1e565b5050505050565b60a083013560c08401358082106119d95760405162461bcd60e51b815260040161029490614a1e565b60078460088111156119ed576119ed613c96565b036119a9576001600160401b038111156119a95760405162461bcd60e51b815260206004820152601e60248201527f504d503a206d617852616e6765203e205f54494d455354414d505f4d415800006044820152606401610294565b60405162461bcd60e51b815260040161029490614951565b6000611a6c82611deb565b905060008151845114611a8157506001611ae9565b60005b8451811015611ae757611ad2838281518110611aa257611aa2613ebb565b6020026020010151868381518110611abc57611abc613ebb565b602002602001015161311190919063ffffffff16565b611adf5760019150611ae7565b600101611a84565b505b8015610713576107138385611b29565b600081604051602001611b0c9190614a51565b604051602081830303815290604052805190602001209050919050565b80516000819003611b4657505080546001600160a01b0319169055565b6000816001600160401b03811115611b6057611b60613b31565b604051908082528060200260200182016040528015611b89578160200160208202803683370190505b5090506000805b83811015611bfb5781838281518110611bab57611bab613ebb565b60200260200101906001600160401b031690816001600160401b031681525050848181518110611bdd57611bdd613ebb565b60200260200101515182611bf19190614a64565b9150600101611b90565b506001600160401b03811115611c515760405162461bcd60e51b815260206004820152601b60248201527a13d9999cd95d08195e18d959591cc81d5a5b9d0d8d081b1a5b5a5d602a1b6044820152606401610294565b600081611c5f856008614a77565b611c6a906008614a64565b611c749190614a64565b90506000611c83826020614a64565b6001600160401b03811115611c9a57611c9a613b31565b6040519080825280601f01601f191660200182016040528015611cc4576020820181803683370190505b5060c086901b602082015290506028810160005b86811015611d1d576000868281518110611cf457611cf4613ebb565b602090810291909101015160c01b6001600160c01b031916835250600890910190600101611cd8565b5060005b86811015611db7576000888281518110611d3d57611d3d613ebb565b6020026020010151905060008151905060006020830190506000602083611d649190614aa4565b90506000611d73602085614ab8565b905060005b82811015611d96578351885260209788019790930192600101611d78565b508015611da65782518752958601955b505060019093019250611d21915050565b505081815280611dc681613136565b88546001600160a01b0319166001600160a01b03919091161790975550505050505050565b80546060906001600160a01b0316611e2f576040805160008082526020820190925290611e28565b6060815260200190600190039081611e135790505b5092915050565b8154600090611e46906001600160a01b03166131db565b602081015190915060c01c806001600160401b03811115611e6957611e69613b31565b604051908082528060200260200182016040528015611e9c57816020015b6060815260200190600190039081611e875790505b50925060005b8181101561200d576000808084611eba856001614a64565b03611ee8576000865190506028870193506008850284015160c01c9250600886026008018103915050611f0a565b6028860192506008840283015160c01c91506008600185010283015160c01c90505b6000611f168383614acc565b6001600160401b031690506000611f2e826020614a64565b6001600160401b03811115611f4557611f45613b31565b6040519080825280601f01601f191660200182016040528015611f6f576020820181803683370190505b5090506020810160005b83811015611fd5576000816001600160401b038816611f998c6008614a77565b611fa4906028614a64565b611fae9190614a64565b611fb89190614a64565b8b0151835250602091820191611fce9082614a64565b9050611f79565b50819050828152818a8881518110611fef57611fef613ebb565b60200260200101819052505050505050508080600101915050611ea2565b505050919050565b6000610231620f424083614aa4565b815460009060ff8381169116146120a05760405162461bcd60e51b815260206004820152603a60248201527f504d503a20706172616d206e6f742070617274206f66206d6f737420726563656044820152796e746c7920636f6e6669677572656420504d5020706172616d7360301b6064820152608401610294565b825462010000900460ff1660088111156120bc576120bc613c96565b846020015160088111156120d2576120d2613c96565b146121195760405162461bcd60e51b81526020600482015260176024820152760a09aa07440e0c2e4c2daa8f2e0ca40dad2e6dac2e8c6d604b1b6044820152606401610294565b60008460200151600881111561213157612131613c96565b0361218a5760405162461bcd60e51b8152602060048201526024808201527f504d503a20696e70757420706172616d5479706520697320756e636f6e6669676044820152631d5c995960e21b6064820152608401610294565b8254600090610100900460ff16818160068111156121aa576121aa613c96565b14806121c7575060048160068111156121c5576121c5613c96565b145b806121e3575060038160068111156121e1576121e1613c96565b145b806121ff575060068160068111156121fd576121fd613c96565b145b156122175760006122118989336131fc565b94509250505b811580156122895750600281600681111561223457612234613c96565b14806122515750600581600681111561224f5761224f613c96565b145b8061226d5750600481600681111561226b5761226b613c96565b145b806122895750600681600681111561228757612287613c96565b145b156122a8578454600160481b90046001600160a01b0316925033831491505b8115801561231a575060018160068111156122c5576122c5613c96565b14806122e2575060038160068111156122e0576122e0613c96565b145b806122fe575060058160068111156122fc576122fc613c96565b145b8061231a5750600681600681111561231857612318613c96565b145b1561233257600061232c898933612b2a565b94509250505b5080612675578354610100900460ff16600081600681111561235657612356613c96565b0361239f5760405162461bcd60e51b81526020600482015260196024820152781413540e88185c9d1a5cdd08185d5d1a081c995c5d5a5c9959603a1b6044820152606401610294565b60018160068111156123b3576123b3613c96565b036124005760405162461bcd60e51b815260206004820152601e60248201527f504d503a20746f6b656e206f776e6572206175746820726571756972656400006044820152606401610294565b600381600681111561241457612414613c96565b036124735760405162461bcd60e51b815260206004820152602960248201527f504d503a2061727469737420616e6420746f6b656e206f776e65722061757468604482015268081c995c5d5a5c995960ba1b6064820152608401610294565b600281600681111561248757612487613c96565b036124d15760405162461bcd60e51b815260206004820152601a6024820152791413540e881859191c995cdcc8185d5d1a081c995c5d5a5c995960321b6044820152606401610294565b60068160068111156124e5576124e5613c96565b036125505760405162461bcd60e51b815260206004820152603560248201527f504d503a2061727469737420616e6420746f6b656e206f776e657220616e64206044820152741859191c995cdcc8185d5d1a081c995c5d5a5c9959605a1b6064820152608401610294565b600481600681111561256457612564613c96565b036125bf5760405162461bcd60e51b815260206004820152602560248201527f504d503a2061727469737420616e6420616464726573732061757468207265716044820152641d5a5c995960da1b6064820152608401610294565b60058160068111156125d3576125d3613c96565b036126335760405162461bcd60e51b815260206004820152602a60248201527f504d503a20746f6b656e206f776e657220616e642061646472657373206175746044820152691a081c995c5d5a5c995960b21b6064820152608401610294565b60405162461bcd60e51b81526020600482015260176024820152762826a81d1034b73b30b634b21030baba3427b83a34b7b760491b6044820152606401610294565b835462010000900460ff16600881600881111561269457612694613c96565b146129265760018160088111156126ad576126ad613c96565b036127255784546040870151600160e81b90910460ff16116127205760405162461bcd60e51b815260206004820152602660248201527f504d503a2073656c6563744f7074696f6e7320696e646578206f7574206f6620604482015265626f756e647360d01b6064820152608401610294565b612926565b600281600881111561273957612739613c96565b036127ab5760408601511580612753575060408601516001145b6127205760405162461bcd60e51b8152602060048201526024808201527f504d503a20626f6f6c20706172616d2076616c7565206d7573742062652030206044820152636f72203160e01b6064820152608401610294565b60038160088111156127bf576127bf613c96565b14806127dc575060058160088111156127da576127da613c96565b145b1561281d57846002015486604001511015801561280157508460030154866040015111155b6127205760405162461bcd60e51b815260040161029490614aec565b600481600881111561283157612831613c96565b0361287157600285015460408701511280159061280157506003850154604087015113156127205760405162461bcd60e51b815260040161029490614aec565b600781600881111561288557612885613c96565b036128a55760408601516001600160401b03108015906128015750612926565b60068160088111156128b9576128b9613c96565b036128e557604086015162ffffff10156127205760405162461bcd60e51b815260040161029490614b23565b60405162461bcd60e51b8152602060048201526016602482015275504d503a20696e76616c696420706172616d5479706560501b6044820152606401610294565b6008855462010000900460ff16600881111561294457612944613c96565b03612a39576040860151156129ae5760405162461bcd60e51b815260206004820152602a60248201527f504d503a2076616c7565206d75737420626520656d70747920666f7220737472604482015269696e6720706172616d7360b01b6064820152608401610294565b856060015115612a345760006129c58989336131fc565b50905080612a325760405162461bcd60e51b815260206004820152603460248201527f504d503a20617274697374206175746820726571756972656420746f20636f6e6044820152736669677572652061727469737420737472696e6760601b6064820152608401610294565b505b612b1f565b60808601515115612aa75760405162461bcd60e51b815260206004820152603260248201527f504d503a206e6f6e2d737472696e6720706172616d206d757374206861766520604482015271656d70747920737472696e672076616c756560701b6064820152608401610294565b856060015115612b1f5760405162461bcd60e51b815260206004820152603d60248201527f504d503a2061727469737420737472696e672063616e6e6f7420626520636f6e60448201527f6669677572656420666f72206e6f6e2d737472696e6720706172616d730000006064820152608401610294565b505095945050505050565b6040516331a9108f60e11b81526004810184905260009081906001600160a01b03851690636352211e90602401602060405180830381865afa158015612b74573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612b9891906148e3565b9050826001600160a01b0316816001600160a01b03161480612c715750604051632e7cda1d60e21b81526001600160a01b038481166004830152828116602483015285811660448301526064820187905271706f73746d696e74706172616d657465727360701b60848301527f00000000000000000000000000000000000000447e69651d841bd8d104bed493169063b9f368749060a401602060405180830381865afa158015612c4d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612c719190614b53565b9150935093915050565b6000612c8683613298565b8015612c975750612c9783836132cb565b9392505050565b805460009060609060ff1682816008811115612cbc57612cbc613c96565b1480612cf45750845462010000900460ff166008811115612cdf57612cdf613c96565b816008811115612cf157612cf1613c96565b14155b15612d1257505060408051602081019091526000808252915061310a565b6008855462010000900460ff166008811115612d3057612d30613c96565b03612e2e576000846002018054612d4690614438565b90501115612dea57600184600201808054612d6090614438565b80601f0160208091040260200160405190810160405280929190818152602001828054612d8c90614438565b8015612dd95780601f10612dae57610100808354040283529160200191612dd9565b820191906000526020600020905b815481529060010190602001808311612dbc57829003601f168201915b50505050509050925092505061310a565b6000846003018054612dfb90614438565b90501115612e1557600184600301808054612d6090614438565b505060408051602081019091526000808252915061310a565b6001816008811115612e4257612e42613c96565b03612e945784546001850154600160e81b90910460ff1611612e7757505060408051602081019091526000808252915061310a565b600184810154612e8a9087830190613355565b925092505061310a565b6002816008811115612ea857612ea8613c96565b03612f025760018481015415612eda57604051806040016040528060048152602001637472756560e01b815250612e8a565b6040518060400160405280600581526020016466616c736560d81b815250925092505061310a565b6003816008811115612f1657612f16613c96565b1480612f3357506006816008811115612f3157612f31613c96565b145b80612f4f57506007816008811115612f4d57612f4d613c96565b145b80612f6b57506005816008811115612f6957612f69613c96565b145b1561305c57600184015460006006836008811115612f8b57612f8b613c96565b14612f9a576003870154612f9f565b62ffffff5b905060006006846008811115612fb757612fb7613c96565b14612fc6576002880154612fc9565b60005b905080831080612fd857508183115b15612ffc57600060405180602001604052806000815250955095505050505061310a565b600584600881111561301057613010613c96565b0361302d576001613020846134ff565b955095505050505061310a565b600684600881111561304157613041613c96565b03613051576001613020846135ac565b600161302084613695565b600481600881111561307057613070613c96565b036130c7576001840154600286015481128061308f5750600386015481135b156130b15760006040518060200160405280600081525093509350505061310a565b60016130bc82613727565b93509350505061310a565b60405162461bcd60e51b8152602060048201526018602482015277504d503a20556e68616e646c656420506172616d5479706560401b6044820152606401610294565b9250929050565b600081518351148015612c975750508051602091820120825192909101919091201490565b6000808260405160200161314a9190614b70565b604051602081830303815290604052905060008160405160200161316e9190614b96565b60405160208183030381529060405290508051602082016000f092506001600160a01b0383166131d45760405162461bcd60e51b81526020600482015260116024820152701111541313d65351539517d19052531151607a1b6044820152606401610294565b5050919050565b60606102318260016131f7816001600160a01b0384163b614bca565b613799565b600080600061320a86612015565b60405163a47d29cb60e01b8152600481018290529091506001600160a01b0386169063a47d29cb90602401602060405180830381865afa158015613252573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061327691906148e3565b9150836001600160a01b0316826001600160a01b031614925050935093915050565b60006132ab826301ffc9a760e01b6132cb565b801561023157506132c4826001600160e01b03196132cb565b1592915050565b6040516001600160e01b031982166024820152600090819060440160408051601f19818403018152919052602080820180516001600160e01b03166301ffc9a760e01b178152825192935060009283928392909183918a617530fa92503d9150600051905082801561333e575060208210155b801561334a5750600081115b979650505050505050565b81546060906001600160a01b031661337f5760405162461bcd60e51b815260040161029490614bdd565b8254600090613396906001600160a01b03166131db565b9050600080600080602085015160c01c90508087106133c75760405162461bcd60e51b815260040161029490614bdd565b806133d3886001614a64565b03613401576000855190506028860194506008880285015160c01c9350600882026008018103925050613423565b6028850193506008870284015160c01c92506008600188010284015160c01c91505b600061342f8484614acc565b6001600160401b031690506000613447826020614a64565b6001600160401b0381111561345e5761345e613b31565b6040519080825280601f01601f191660200182016040528015613488576020820181803683370190505b5090506020810160005b838110156134ee576000816001600160401b0389166134b2886008614a77565b6134bd906028614a64565b6134c79190614a64565b6134d19190614a64565b8a01518352506020918201916134e79082614a64565b9050613492565b505090815298975050505050505050565b6060600061350e600a80614cee565b6135189084614aa4565b90506000613527600a80614cee565b6135319085614ab8565b9050600061353e83613695565b9050600061354b83613695565b90505b600a8151101561357f57806040516020016135699190614cfa565b604051602081830303815290604052905061354e565b8181604051602001613592929190614d16565b604051602081830303815290604052945050505050919050565b6040805160078082528183019092526060918391600091602082018180368337019050509050602360f81b816000815181106135ea576135ea613ebb565b60200101906001600160f81b031916908160001a90535060065b8015613676576f181899199a1a9b1b9c1cb0b131b232b360811b83600f166010811061363257613632613ebb565b1a60f81b82828151811061364857613648613ebb565b60200101906001600160f81b031916908160001a90535060049290921c9161366f81614d52565b9050613604565b508115612c975760405162461bcd60e51b815260040161029490614b23565b606060006136a2836137bc565b60010190506000816001600160401b038111156136c1576136c1613b31565b6040519080825280601f01601f1916602001820160405280156136eb576020820181803683370190505b5090508181016020015b600019016f181899199a1a9b1b9c1cb0b131b232b360811b600a86061a8153600a85049450846136f557509392505050565b6060600082126137465760405180602001604052806000815250613761565b604051806040016040528060018152602001602d60f81b8152505b61377261376d84613892565b613695565b604051602001613783929190614d69565b6040516020818303038152906040529050919050565b60408051603f8301601f19168101909152818152818360208301863c9392505050565b60008072184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b83106137fb5772184f03e93ff9f4daa797ed6e38ed64bf6a1f0160401b830492506040015b6904ee2d6d415b85acef8160201b8310613825576904ee2d6d415b85acef8160201b830492506020015b662386f26fc10000831061384357662386f26fc10000830492506010015b6305f5e100831061385b576305f5e100830492506008015b612710831061386f57612710830492506004015b60648310613881576064830492506002015b600a83106102315760010192915050565b6000808212156138a55781600003610231565b5090565b6000602082840312156138bb57600080fd5b81356001600160e01b031981168114612c9757600080fd5b6001600160a01b03811681146138e857600080fd5b50565b80356138f6816138d3565b919050565b60008083601f84011261390d57600080fd5b5081356001600160401b0381111561392457600080fd5b6020830191508360208260051b850101111561310a57600080fd5b6000806000806060858703121561395557600080fd5b8435613960816138d3565b93506020850135925060408501356001600160401b0381111561398257600080fd5b61398e878288016138fb565b95989497509550505050565b600080604083850312156139ad57600080fd5b82356139b8816138d3565b946020939093013593505050565b60005b838110156139e15781810151838201526020016139c9565b50506000910152565b60008151808452613a028160208601602086016139c6565b601f01601f19169290920160200192915050565b6000608082016080835280875180835260a08501915060a08160051b86010192506020808a0160005b83811015613a6d57609f19888703018552613a5b8683516139ea565b95509382019390820190600101613a3f565b5050505060ff9690961660208401526001600160a01b039485166040840152929093166060909101529392505050565b600080600060608486031215613ab257600080fd5b8335613abd816138d3565b92506020840135613acd816138d3565b929592945050506040919091013590565b60008060008060808587031215613af457600080fd5b8435613aff816138d3565b9350602085013592506040850135613b16816138d3565b91506060850135613b26816138d3565b939692955090935050565b634e487b7160e01b600052604160045260246000fd5b60405160e081016001600160401b0381118282101715613b6957613b69613b31565b60405290565b604080519081016001600160401b0381118282101715613b6957613b69613b31565b604051601f8201601f191681016001600160401b0381118282101715613bb957613bb9613b31565b604052919050565b60006001600160401b03821115613bda57613bda613b31565b50601f01601f191660200190565b600082601f830112613bf957600080fd5b8135613c0c613c0782613bc1565b613b91565b818152846020838601011115613c2157600080fd5b816020850160208301376000918101602001919091529392505050565b600080600060608486031215613c5357600080fd5b8335613c5e816138d3565b92506020840135915060408401356001600160401b03811115613c8057600080fd5b613c8c86828701613be8565b9150509250925092565b634e487b7160e01b600052602160045260246000fd5b60078110613cbc57613cbc613c96565b9052565b60098110613cbc57613cbc613c96565b60008282518085526020808601955060208260051b8401016020860160005b84811015613d1d57601f19868403018952613d0b8383516139ea565b98840198925090830190600101613cef565b5090979650505050505050565b60208152613d3e60208201835160ff169052565b60006020830151613d526040840182613cac565b506040830151613d656060840182613cc0565b50606083015165ffffffffffff811660808401525060808301516001600160a01b03811660a08401525060a083015160ff811660c08401525060c08301516101208060e0850152613dba610140850183613cd0565b60e086015161010086810191909152909501519301929092525090919050565b600082825180855260208086019550808260051b84010181860160005b84811015613d1d57601f19868403018952815160408151818652613e1d828701826139ea565b91505085820151915084810386860152613e3781836139ea565b9a86019a9450505090830190600101613df7565b602081526000612c976020830184613dda565b60208152613e70602082018351613cc0565b602082015160408201526000604083015160806060840152613e9560a08401826139ea565b90506060840151601f19848303016080850152613eb282826139ea565b95945050505050565b634e487b7160e01b600052603260045260246000fd5b60008235603e19833603018112613ee757600080fd5b9190910192915050565b6000808335601e19843603018112613f0857600080fd5b8301803591506001600160401b03821115613f2257600080fd5b60200191503681900382131561310a57600080fd5b6000823560de19833603018112613ee757600080fd5b634e487b7160e01b600052601160045260246000fd5b60ff818116838216019081111561023157610231613f4d565b8035600781106138f657600080fd5b8035600981106138f657600080fd5b803565ffffffffffff811681146138f657600080fd5b60006001600160401b03821115613fc957613fc9613b31565b5060051b60200190565b600082601f830112613fe457600080fd5b81356020613ff4613c0783613fb0565b82815260059290921b8401810191818101908684111561401357600080fd5b8286015b848110156140525780356001600160401b038111156140365760008081fd5b6140448986838b0101613be8565b845250918301918301614017565b509695505050505050565b600060e0823603121561406f57600080fd5b614077613b47565b61408083613f7c565b815261408e60208401613f8b565b602082015261409f60408401613f9a565b60408201526140b0606084016138eb565b606082015260808301356001600160401b038111156140ce57600080fd5b6140da36828601613fd3565b60808301525060a083013560a082015260c083013560c082015280915050919050565b6000808335601e1984360301811261411457600080fd5b83016020810192503590506001600160401b0381111561413357600080fd5b80360382131561310a57600080fd5b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6000838385526020808601955060208560051b8301018460005b87811015613d1d57848303601f190189526141a082886140fd565b6141ab858284614142565b9a86019a9450505090830190600101614185565b6001600160a01b038681168252602080830187905260806040808501829052848201879052600093919260a0929183870160058a901b880185018b885b8c81101561433b578a8303609f190184528135368f9003603e1901811261422257600080fd5b8e0161422e81806140fd565b87865261423e8887018284614142565b9150508782013560de1983360301811261425757600080fd5b858203898701529091019060e06142768261427185613f7c565b613cac565b614281898401613f8b565b61428d8a840182613cc0565b5065ffffffffffff6142a0898501613f9a565b16888301526060808401356142b4816138d3565b8b1690830152828c013536849003601e190181126142d157600080fd5b830189810190356001600160401b038111156142ec57600080fd5b8060051b36038213156142fe57600080fd5b828e850152614310838501828461416b565b858e0135858f015260c0958601359590940194909452505094870194935050908501906001016141fc565b505060ff8a1660608a0152965061435495505050505050565b9695505050505050565b60008235609e19833603018112613ee757600080fd5b80151581146138e857600080fd5b600060a0823603121561439457600080fd5b60405160a081016001600160401b0380821183831017156143b7576143b7613b31565b8160405284359150808211156143cc57600080fd5b6143d836838701613be8565b83526143e660208601613f8b565b6020840152604085013560408401526060850135915061440582614374565b816060840152608085013591508082111561441f57600080fd5b5061442c36828601613be8565b60808301525092915050565b600181811c9082168061444c57607f821691505b60208210810361446c57634e487b7160e01b600052602260045260246000fd5b50919050565b601f8211156112e3576000816000526020600020601f850160051c8101602086101561449b5750805b601f850160051c820191505b818110156144ba578281556001016144a7565b505050505050565b81516001600160401b038111156144db576144db613b31565b6144ef816144e98454614438565b84614472565b602080601f831160018114614524576000841561450c5750858301515b600019600386901b1c1916600185901b1785556144ba565b600085815260208120601f198616915b8281101561455357888601518255948401946001909101908401614534565b50858210156145715787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60018060a01b0384168152826020820152606060408201526000825160a060608401526145b26101008401826139ea565b905060208401516145c66080850182613cc0565b50604084015160a08401526060840151151560c08401526080840151838203605f190160e085015261334a82826139ea565b60008151808452602080850194506020840160005b838110156146325781516001600160a01b03168752958201959082019060010161460d565b509495945050505050565b6001600160a01b038616815260208082018690526080604080840182905283820186905260009260a091828601600589901b870184018a87805b8c811015614725578a8403609f190185528235368f9003609e1901811261469c578283fd5b8e016146a881806140fd565b8a87526146b88b88018284614142565b9150506146c6898301613f8b565b6146d28a880182613cc0565b5087820135888701526060808301356146ea81614374565b1515908701526146fc828c01836140fd565b92508682038c880152614710828483614142565b978a0197965050509287019250600101614677565b505050878103606089015261473a818a6145f8565b9d9c50505050505050505050505050565b60006001820161475d5761475d613f4d565b5060010190565b60018060a01b0384168152826020820152606060408201526000613eb26060830184613dda565b600082601f83011261479c57600080fd5b81516147aa613c0782613bc1565b8181528460208386010111156147bf57600080fd5b6147d08260208301602087016139c6565b949350505050565b600060208083850312156147eb57600080fd5b82516001600160401b038082111561480257600080fd5b818501915085601f83011261481657600080fd5b8151614824613c0782613fb0565b81815260059190911b8301840190848101908883111561484357600080fd5b8585015b838110156148d65780518581111561485f5760008081fd5b86016040818c03601f19018113156148775760008081fd5b61487f613b6f565b89830151888111156148915760008081fd5b61489f8e8c8387010161478b565b8252509082015190878211156148b55760008081fd5b6148c38d8b8486010161478b565b818b015285525050918601918601614847565b5098975050505050505050565b6000602082840312156148f557600080fd5b8151612c97816138d3565b60006020828403121561491257600080fd5b612c9782613f8b565b60006020828403121561492d57600080fd5b612c9782613f7c565b60006020828403121561494857600080fd5b612c9782613f9a565b602080825260169082015275504d503a20496e76616c696420706172616d5479706560501b604082015260600190565b60006020828403121561499357600080fd5b8135612c97816138d3565b6000808335601e198436030181126149b557600080fd5b8301803591506001600160401b038211156149cf57600080fd5b6020019150600581901b360382131561310a57600080fd5b6020808252601f908201527f504d503a2073656c6563744f7074696f6e73206973206e6f7420656d70747900604082015260600190565b602080825260199082015278504d503a206d696e52616e6765203e3d206d617852616e676560381b604082015260600190565b602081526000612c9760208301846139ea565b8082018082111561023157610231613f4d565b808202811582820484141761023157610231613f4d565b634e487b7160e01b600052601260045260246000fd5b600082614ab357614ab3614a8e565b500490565b600082614ac757614ac7614a8e565b500690565b6001600160401b03828116828216039080821115611e2857611e28613f4d565b6020808252601e908201527f504d503a20706172616d2076616c7565206f7574206f6620626f756e64730000604082015260600190565b6020808252601690820152752826a81d1034b73b30b634b2103432bc1031b7b637b960511b604082015260600190565b600060208284031215614b6557600080fd5b8151612c9781614374565b6000815260008251614b898160018501602087016139c6565b9190910160010192915050565b6a600b5981380380925939f360a81b81528151600090614bbd81600b8501602087016139c6565b91909101600b0192915050565b8181038181111561023157610231613f4d565b602080825260139082015272496e646578206f7574206f6620626f756e647360681b604082015260600190565b600181815b80851115614c45578160001904821115614c2b57614c2b613f4d565b80851615614c3857918102915b93841c9390800290614c0f565b509250929050565b600082614c5c57506001610231565b81614c6957506000610231565b8160018114614c7f5760028114614c8957614ca5565b6001915050610231565b60ff841115614c9a57614c9a613f4d565b50506001821b610231565b5060208310610133831016604e8410600b8410161715614cc8575081810a610231565b614cd28383614c0a565b8060001904821115614ce657614ce6613f4d565b029392505050565b6000612c978383614c4d565b600360fc1b815260008251614b898160018501602087016139c6565b60008351614d288184602088016139c6565b601760f91b9083019081528351614d468160018401602088016139c6565b01600101949350505050565b600081614d6157614d61613f4d565b506000190190565b60008351614d7b8184602088016139c6565b835190830190614d8f8183602088016139c6565b0194935050505056fea26469706673582212200bede6c2fd14d9777b1f2a93c2b7e9c38cd1e28475e132481a6b6b474e0a5f9064736f6c63430008160033

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

00000000000000000000000000000000000000447e69651d841bd8d104bed493

-----Decoded View---------------
Arg [0] : delegateRegistry_ (address): 0x00000000000000447e69651d841bD8D104Bed493

-----Encoded View---------------
1 Constructor Arguments found :
Arg [0] : 00000000000000000000000000000000000000447e69651d841bd8d104bed493


Block Uncle Number Difficulty Gas Used Reward
View All Uncles
Loading...
Loading
Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]
[ Download: CSV Export  ]

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.