ETH Price: $2,399.95 (-0.23%)

Contract

0x8a520BD5e3a78D207617B9BB2bE1204ae291F314
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
0x60806040145472852022-04-08 20:32:53881 days ago1649449973IN
 Create: PublicLock
0 ETH0.338461863.14398686

View more zero value Internal Transactions in Advanced View mode

Advanced mode:
Loading...
Loading

Contract Source Code Verified (Exact Match)

Contract Name:
PublicLock

Compiler Version
v0.8.7+commit.e28d00a7

Optimization Enabled:
Yes with 80 runs

Other Settings:
default evmVersion
File 1 of 32 : PublicLock.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.7;

import './interfaces/IPublicLock.sol';
import '@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol';
import '@openzeppelin/contracts-upgradeable/utils/introspection/ERC165StorageUpgradeable.sol';
import './mixins/MixinDisable.sol';
import './mixins/MixinERC721Enumerable.sol';
import './mixins/MixinFunds.sol';
import './mixins/MixinGrantKeys.sol';
import './mixins/MixinKeys.sol';
import './mixins/MixinLockCore.sol';
import './mixins/MixinLockMetadata.sol';
import './mixins/MixinPurchase.sol';
import './mixins/MixinRefunds.sol';
import './mixins/MixinTransfer.sol';
import './mixins/MixinRoles.sol';
import './mixins/MixinConvenienceOwnable.sol';

/**
 * @title The Lock contract
 * @author Julien Genestoux (unlock-protocol.com)
 * @dev ERC165 allows our contract to be queried to determine whether it implements a given interface.
 * Every ERC-721 compliant contract must implement the ERC165 interface.
 * https://eips.ethereum.org/EIPS/eip-721
 */
contract PublicLock is
  Initializable,
  ERC165StorageUpgradeable,
  MixinRoles,
  MixinFunds,
  MixinDisable,
  MixinLockCore,
  MixinKeys,
  MixinLockMetadata,
  MixinERC721Enumerable,
  MixinGrantKeys,
  MixinPurchase,
  MixinTransfer,
  MixinRefunds,
  MixinConvenienceOwnable
{
  function initialize(
    address payable _lockCreator,
    uint _expirationDuration,
    address _tokenAddress,
    uint _keyPrice,
    uint _maxNumberOfKeys,
    string calldata _lockName
  ) public
    initializer()
  {
    MixinFunds._initializeMixinFunds(_tokenAddress);
    MixinLockCore._initializeMixinLockCore(_lockCreator, _expirationDuration, _keyPrice, _maxNumberOfKeys);
    MixinLockMetadata._initializeMixinLockMetadata(_lockName);
    MixinERC721Enumerable._initializeMixinERC721Enumerable();
    MixinRefunds._initializeMixinRefunds();
    MixinRoles._initializeMixinRoles(_lockCreator);
    MixinConvenienceOwnable._initializeMixinConvenienceOwnable(_lockCreator);
    // registering the interface for erc721 with ERC165.sol using
    // the ID specified in the standard: https://eips.ethereum.org/EIPS/eip-721
    _registerInterface(0x80ac58cd);
  }

  /**
   * @notice Allow the contract to accept tips in ETH sent directly to the contract.
   * @dev This is okay to use even if the lock is priced in ERC-20 tokens
   */
  receive() external payable {}
  
  /**
   Overrides
  */
  function supportsInterface(bytes4 interfaceId) 
    public 
    view 
    virtual 
    override(
      MixinERC721Enumerable,
      MixinLockMetadata,
      AccessControlUpgradeable, 
      ERC165StorageUpgradeable
    ) 
    returns (bool) 
    {
    return super.supportsInterface(interfaceId);
  }

}

File 2 of 32 : IPublicLock.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.17 <0.9.0;
pragma experimental ABIEncoderV2;

/**
* @title The PublicLock Interface
* @author Nick Furfaro (unlock-protocol.com)
 */


interface IPublicLock
{

// See indentationissue description here:
// https://github.com/duaraghav8/Ethlint/issues/268
// solium-disable indentation

  /// Functions
  function initialize(
    address _lockCreator,
    uint _expirationDuration,
    address _tokenAddress,
    uint _keyPrice,
    uint _maxNumberOfKeys,
    string calldata _lockName
  ) external;

  /**
   * @notice Allow the contract to accept tips in ETH sent directly to the contract.
   * @dev This is okay to use even if the lock is priced in ERC-20 tokens
   */
  // receive() external payable;

  // roles
  function DEFAULT_ADMIN_ROLE() external pure returns (bytes32);
  function KEY_GRANTER_ROLE() external pure returns (bytes32);
  function LOCK_MANAGER_ROLE() external pure returns (bytes32);

  /**
  * @notice The version number of the current implementation on this network.
  * @return The current version number.
  */
  function publicLockVersion() external pure returns (uint16);

  /**
   * @dev Called by a lock manager or beneficiary to withdraw all funds from the lock and send them to the `beneficiary`.
   * @dev Throws if called by other than a lock manager or beneficiary
   * @param _tokenAddress specifies the token address to withdraw or 0 for ETH. This is usually
   * the same as `tokenAddress` in MixinFunds.
   * @param _amount specifies the max amount to withdraw, which may be reduced when
   * considering the available balance. Set to 0 or MAX_UINT to withdraw everything.
   *  -- however be wary of draining funds as it breaks the `cancelAndRefund` and `expireAndRefundFor`
   * use cases.
   */
  function withdraw(
    address _tokenAddress,
    uint _amount
  ) external;

  /**
   * @notice An ERC-20 style approval, allowing the spender to transfer funds directly from this lock.
   */
  function approveBeneficiary(
    address _spender,
    uint _amount
  ) external
    returns (bool);

  /**
   * A function which lets a Lock manager of the lock to change the price for future purchases.
   * @dev Throws if called by other than a Lock manager
   * @dev Throws if lock has been disabled
   * @dev Throws if _tokenAddress is not a valid token
   * @param _keyPrice The new price to set for keys
   * @param _tokenAddress The address of the erc20 token to use for pricing the keys,
   * or 0 to use ETH
   */
  function updateKeyPricing( uint _keyPrice, address _tokenAddress ) external;

  /**
   * A function to change the default duration of each key in the lock
   * @notice keys previously bought are unaffected by this change (i.e.
   * existing keys timestamps are not recalculated/updated)
   * @param _newExpirationDuration the new amount of time for each key purchased 
   * or type(uint).max for a non-expiring key
   */
  function setExpirationDuration(uint _newExpirationDuration) external;

  /**
   * A function which lets a Lock manager update the beneficiary account,
   * which receives funds on withdrawal.
   * @dev Throws if called by other than a Lock manager or beneficiary
   * @dev Throws if _beneficiary is address(0)
   * @param _beneficiary The new address to set as the beneficiary
   */
  function updateBeneficiary( address _beneficiary ) external;

  /**
   * Checks if the user has a non-expired key.
   * @param _user The address of the key owner
   */
  function getHasValidKey(
    address _user
  ) external view returns (bool);

  /**
  * @dev Returns the key's ExpirationTimestamp field for a given owner.
  * @param _tokenId the id of the key
  * @dev Returns 0 if the owner has never owned a key for this lock
  */
  function keyExpirationTimestampFor(
    uint _tokenId
  ) external view returns (uint timestamp);
  
  /**
   * Public function which returns the total number of unique owners (both expired
   * and valid).  This may be larger than totalSupply.
   */
  function numberOfOwners() external view returns (uint);

  /**
   * Allows a Lock manager to assign a descriptive name for this Lock.
   * @param _lockName The new name for the lock
   * @dev Throws if called by other than a Lock manager
   */
  function updateLockName(
    string calldata _lockName
  ) external;

  /**
   * Allows a Lock manager to assign a Symbol for this Lock.
   * @param _lockSymbol The new Symbol for the lock
   * @dev Throws if called by other than a Lock manager
   */
  function updateLockSymbol(
    string calldata _lockSymbol
  ) external;

  /**
    * @dev Gets the token symbol
    * @return string representing the token symbol
    */
  function symbol()
    external view
    returns(string memory);

    /**
   * Allows a Lock manager to update the baseTokenURI for this Lock.
   * @dev Throws if called by other than a Lock manager
   * @param _baseTokenURI String representing the base of the URI for this lock.
   */
  function setBaseTokenURI(
    string calldata _baseTokenURI
  ) external;

  /**  @notice A distinct Uniform Resource Identifier (URI) for a given asset.
   * @dev Throws if `_tokenId` is not a valid NFT. URIs are defined in RFC
   *  3986. The URI may point to a JSON file that conforms to the "ERC721
   *  Metadata JSON Schema".
   * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
   * @param _tokenId The tokenID we're inquiring about
   * @return String representing the URI for the requested token
   */
  function tokenURI(
    uint256 _tokenId
  ) external view returns(string memory);

  /**
   * @notice Allows a Lock manager to add or remove an event hook
   */
  function setEventHooks(
    address _onKeyPurchaseHook,
    address _onKeyCancelHook,
    address _onValidKeyHook,
    address _onTokenURIHook
  ) external;

  /**
   * Allows a Lock manager to give a collection of users a key with no charge.
   * Each key may be assigned a different expiration date.
   * @dev Throws if called by other than a Lock manager
   * @param _recipients An array of receiving addresses
   * @param _expirationTimestamps An array of expiration Timestamps for the keys being granted
   */
  function grantKeys(
    address[] calldata _recipients,
    uint[] calldata _expirationTimestamps,
    address[] calldata _keyManagers
  ) external;

  /**
  * @dev Purchase function
  * @param _values array of tokens amount to pay for this purchase >= the current keyPrice - any applicable discount
  * (_values is ignored when using ETH)
  * @param _recipients array of addresses of the recipients of the purchased key
  * @param _referrers array of addresses of the users making the referral
  * @param _keyManagers optional array of addresses to grant managing rights to a specific address on creation
  * @param _data array of arbitrary data populated by the front-end which initiated the sale
  * @notice when called for an existing and non-expired key, the `_keyManager` param will be ignored 
  * @dev Setting _value to keyPrice exactly doubles as a security feature. That way if the lock owner increases the
  * price while my transaction is pending I can't be charged more than I expected (only applicable to ERC-20 when more
  * than keyPrice is approved for spending).
  */
  function purchase(
    uint256[] calldata _values,
    address[] calldata _recipients,
    address[] calldata _referrers,
    address[] calldata _keyManagers,
    bytes[] calldata _data
  ) external payable;
  
  /**
  * @dev Extend function
  * @param _value the number of tokens to pay for this purchase >= the current keyPrice - any applicable discount
  * (_value is ignored when using ETH)
  * @param _tokenId the id of the key to extend
  * @param _referrer address of the user making the referral
  * @param _data arbitrary data populated by the front-end which initiated the sale
  * @dev Throws if lock is disabled or key does not exist for _recipient. Throws if _recipient == address(0).
  */
  function extend(
    uint _value,
    uint _tokenId,
    address _referrer,
    bytes calldata _data
  ) external payable;

  /**
   * Merge existing keys
   * @param _tokenIdFrom the id of the token to substract time from
   * @param _tokenIdTo the id of the destination token  to add time
   * @param _amount the amount of time to transfer (in seconds)
   */
  function mergeKeys(uint _tokenIdFrom, uint _tokenIdTo, uint _amount) external;

  /**
   * Deactivate an existing key
   * @param _tokenId the id of token to burn
   * @notice the key will be expired and ownership records will be destroyed
   */
  function burn(uint _tokenId) external;

  /**
  * @param _gasRefundValue price in wei or token in smallest price unit
  * @dev Set the value to be refunded to the sender on purchase
  */
  function setGasRefundValue(uint256 _gasRefundValue) external;
  
  /**
  * _gasRefundValue price in wei or token in smallest price unit
  * @dev Returns the value/rpice to be refunded to the sender on purchase
  */
  function gasRefundValue() external view returns (uint256 _gasRefundValue);

  /**
   * @notice returns the minimum price paid for a purchase with these params.
   * @dev this considers any discount from Unlock or the OnKeyPurchase hook.
   */
  function purchasePriceFor(
    address _recipient,
    address _referrer,
    bytes calldata _data
  ) external view
    returns (uint);

  /**
   * Allow a Lock manager to change the transfer fee.
   * @dev Throws if called by other than a Lock manager
   * @param _transferFeeBasisPoints The new transfer fee in basis-points(bps).
   * Ex: 200 bps = 2%
   */
  function updateTransferFee(
    uint _transferFeeBasisPoints
  ) external;

  /**
   * Determines how much of a fee would need to be paid in order to
   * transfer to another account.  This is pro-rated so the fee goes 
   * down overtime.
   * @dev Throws if _tokenId does not have a valid key
   * @param _tokenId The id of the key check the transfer fee for.
   * @param _time The amount of time to calculate the fee for.
   * @return The transfer fee in seconds.
   */
  function getTransferFee(
    uint _tokenId,
    uint _time
  ) external view returns (uint);

  /**
   * @dev Invoked by a Lock manager to expire the user's key 
   * and perform a refund and cancellation of the key
   * @param _tokenId The key id we wish to refund to
   * @param _amount The amount to refund to the key-owner
   * @dev Throws if called by other than a Lock manager
   * @dev Throws if _keyOwner does not have a valid key
   */
  function expireAndRefundFor(
    uint _tokenId,
    uint _amount
  ) external;

   /**
   * @dev allows the key manager to expire a given tokenId
   * and send a refund to the keyOwner based on the amount of time remaining.
   * @param _tokenId The id of the key to cancel.
   */
  function cancelAndRefund(uint _tokenId) external;

  /**
   * Allow a Lock manager to change the refund penalty.
   * @dev Throws if called by other than a Lock manager
   * @param _freeTrialLength The new duration of free trials for this lock
   * @param _refundPenaltyBasisPoints The new refund penaly in basis-points(bps)
   */
  function updateRefundPenalty(
    uint _freeTrialLength,
    uint _refundPenaltyBasisPoints
  ) external;

  /**
   * @dev Determines how much of a refund a key owner would receive if they issued
   * @param _keyOwner The key owner to get the refund value for.
   * a cancelAndRefund block.timestamp.
   * Note that due to the time required to mine a tx, the actual refund amount will be lower
   * than what the user reads from this call.
   */
  function getCancelAndRefundValue(
    address _keyOwner
  ) external view returns (uint refund);

  function addKeyGranter(address account) external;

  function addLockManager(address account) external;

  function isKeyGranter(address account) external view returns (bool);

  function isLockManager(address account) external view returns (bool);

  function onKeyPurchaseHook() external view returns(address);

  function onKeyCancelHook() external view returns(address);
  
  function onValidKeyHook() external view returns(bool);

  function onTokenURIHook() external view returns(string memory);

  function revokeKeyGranter(address _granter) external;

  function renounceLockManager() external;

  /**
   * @dev Change the maximum number of keys the lock can edit
   * @param _maxNumberOfKeys uint the maximum number of keys
   */
  function setMaxNumberOfKeys (uint _maxNumberOfKeys) external;

   /**
   * Set the maximum number of keys a specific address can use
   * @param _maxKeysPerAddress the maximum amount of key a user can own
   */
  function setMaxKeysPerAddress (uint _maxKeysPerAddress) external;

  /**
   * @return the maximum number of key allowed for a single address
   */
  function maxKeysPerAddress() external view returns (uint);


  ///===================================================================
  /// Auto-generated getter functions from public state variables

  function beneficiary() external view returns (address );

  function expirationDuration() external view returns (uint256 );

  function freeTrialLength() external view returns (uint256 );

  function keyPrice() external view returns (uint256 );

  function maxNumberOfKeys() external view returns (uint256 );

  function refundPenaltyBasisPoints() external view returns (uint256 );

  function tokenAddress() external view returns (address );

  function transferFeeBasisPoints() external view returns (uint256 );

  function unlockProtocol() external view returns (address );

  function keyManagerOf(uint) external view returns (address );

  ///===================================================================

  /**
  * @notice Allows the key owner to safely share their key (parent key) by
  * transferring a portion of the remaining time to a new key (child key).
  * @dev Throws if key is not valid.
  * @dev Throws if `_to` is the zero address
  * @param _to The recipient of the shared key
  * @param _tokenId the key to share
  * @param _timeShared The amount of time shared
  * checks if `_to` is a smart contract (code size > 0). If so, it calls
  * `onERC721Received` on `_to` and throws if the return value is not
  * `bytes4(keccak256('onERC721Received(address,address,uint,bytes)'))`.
  * @dev Emit Transfer event
  */
  function shareKey(
    address _to,
    uint _tokenId,
    uint _timeShared
  ) external;

  /**
  * @notice Update transfer and cancel rights for a given key
  * @param _tokenId The id of the key to assign rights for
  * @param _keyManager The address to assign the rights to for the given key
  */
  function setKeyManagerOf(
    uint _tokenId,
    address _keyManager
  ) external;
  
  /**
  * Check if a certain key is valid
  * @param _tokenId the id of the key to check validity
  * @notice this makes use of the onValidKeyHook if it is set
  */
  function isValidKey(
    uint _tokenId
  )
    external
    view
    returns (bool);
  
  /// @notice A descriptive name for a collection of NFTs in this contract
  function name() external view returns (string memory _name);
  ///===================================================================

  /// From ERC165.sol
  function supportsInterface(bytes4 interfaceId) external view returns (bool);
  ///===================================================================

  /// From ERC-721
  /**
    * @dev Returns the number of NFTs in `owner`'s account.
    */
  function balanceOf(address _owner) external view returns (uint256 balance);

  /**
    * @dev Returns the owner of the NFT specified by `tokenId`.
    */
  function ownerOf(uint256 tokenId) external view returns (address _owner);

  /**
    * @dev Transfers a specific NFT (`tokenId`) from one account (`from`) to
    * another (`to`).
    *
    * Requirements:
    * - `from`, `to` cannot be zero.
    * - `tokenId` must be owned by `from`.
    * - If the caller is not `from`, it must be have been allowed to move this
    * NFT by either {approve} or {setApprovalForAll}.
    */
  function safeTransferFrom(address from, address to, uint256 tokenId) external;
  
  /**
    * @dev Transfers a specific NFT (`tokenId`) from one account (`from`) to
    * another (`to`).
    *
    * Requirements:
    * - If the caller is not `from`, it must be approved to move this NFT by
    * either {approve} or {setApprovalForAll}.
    */
  function transferFrom(address from, address to, uint256 tokenId) external;
  function approve(address to, uint256 tokenId) external;

  /**
    * @notice Get the approved address for a single NFT
    * @dev Throws if `_tokenId` is not a valid NFT.
    * @param _tokenId The NFT to find the approved address for
    * @return operator The approved address for this NFT, or the zero address if there is none
    */
  function getApproved(uint256 _tokenId) external view returns (address operator);

   /**
   * @dev Sets or unsets the approval of a given operator
   * An operator is allowed to transfer all tokens of the sender on their behalf
   * @param _operator operator address to set the approval
   * @param _approved representing the status of the approval to be set
   * @notice disabled when transfers are disabled
   */
  function setApprovalForAll(address _operator, bool _approved) external;

   /**
   * @dev Tells whether an operator is approved by a given keyManager
   * @param _owner owner address which you want to query the approval of
   * @param _operator operator address which you want to query the approval of
   * @return bool whether the given operator is approved by the given owner
   */
  function isApprovedForAll(address _owner, address _operator) external view returns (bool);

  function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

  function totalSupply() external view returns (uint256);
  function tokenOfOwnerByIndex(address _owner, uint256 index) external view returns (uint256 tokenId);

  function tokenByIndex(uint256 index) external view returns (uint256);

  /**
    * Innherited from Open Zeppelin AccessControl.sol
    */
  function getRoleAdmin(bytes32 role) external view returns (bytes32);
  function grantRole(bytes32 role, address account) external;
  function revokeRole(bytes32 role, address account) external;
  function renounceRole(bytes32 role, address account) external;
  function hasRole(bytes32 role, address account) external view returns (bool);

  /**
    * @notice An ERC-20 style transfer.
    * @param _value sends a token with _value * expirationDuration (the amount of time remaining on a standard purchase).
    * @dev The typical use case would be to call this with _value 1, which is on par with calling `transferFrom`. If the user
    * has more than `expirationDuration` time remaining this may use the `shareKey` function to send some but not all of the token.
    */
  function transfer(
    address _to,
    uint _value
  ) external
    returns (bool success);

  /** `owner()` is provided as an helper to mimick the `Ownable` contract ABI.
    * The `Ownable` logic is used by many 3rd party services to determine
    * contract ownership - e.g. who is allowed to edit metadata on Opensea.
    * 
    * @notice This logic is NOT used internally by the Unlock Protocol and is made 
    * available only as a convenience helper.
    */
  function owner() external view returns (address);
  function setOwner(address account) external;
  function isOwner(address account) external returns (bool);

  /**
  * Migrate data from the previous single owner => key mapping to 
  * the new data structure w multiple tokens.
  * @param _calldata an ABI-encoded representation of the params (v10: the number of records to migrate as `uint`)
  * @dev when all record schemas are sucessfully upgraded, this function will update the `schemaVersion`
  * variable to the latest/current lock version
  */
  function migrate(bytes calldata _calldata) external;

  /**
  * Returns the version number of the data schema currently used by the lock
  * @notice if this is different from `publicLockVersion`, then the ability to purchase, grant
  * or extend keys is disabled.
  * @dev will return 0 if no ;igration has ever been run
  */
  function schemaVersion() external view returns (uint);

  /**
   * Set the schema version to the latest
   * @notice only lock manager call call this
   */
  function updateSchemaVersion() external;

    /**
  * Renew a given token
  * @notice only works for non-free, expiring, ERC20 locks
  * @param _tokenId the ID fo the token to renew
  * @param _referrer the address of the person to be granted UDT
  */
  function renewMembershipFor(
    uint _tokenId,
    address _referrer
  ) external;
}

File 3 of 32 : Initializable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (proxy/utils/Initializable.sol)

pragma solidity ^0.8.0;

import "../../utils/AddressUpgradeable.sol";

/**
 * @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
 * behind a proxy. Since a proxied contract can't have a constructor, it's common to move constructor logic to an
 * external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
 * function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
 *
 * TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
 * possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
 *
 * CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
 * that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
 *
 * [CAUTION]
 * ====
 * Avoid leaving a contract uninitialized.
 *
 * An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
 * contract, which may impact the proxy. To initialize the implementation contract, you can either invoke the
 * initializer manually, or you can include a constructor to automatically mark it as initialized when it is deployed:
 *
 * [.hljs-theme-light.nopadding]
 * ```
 * /// @custom:oz-upgrades-unsafe-allow constructor
 * constructor() initializer {}
 * ```
 * ====
 */
abstract contract Initializable {
    /**
     * @dev Indicates that the contract has been initialized.
     */
    bool private _initialized;

    /**
     * @dev Indicates that the contract is in the process of being initialized.
     */
    bool private _initializing;

    /**
     * @dev Modifier to protect an initializer function from being invoked twice.
     */
    modifier initializer() {
        // If the contract is initializing we ignore whether _initialized is set in order to support multiple
        // inheritance patterns, but we only do this in the context of a constructor, because in other contexts the
        // contract may have been reentered.
        require(_initializing ? _isConstructor() : !_initialized, "Initializable: contract is already initialized");

        bool isTopLevelCall = !_initializing;
        if (isTopLevelCall) {
            _initializing = true;
            _initialized = true;
        }

        _;

        if (isTopLevelCall) {
            _initializing = false;
        }
    }

    /**
     * @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
     * {initializer} modifier, directly or indirectly.
     */
    modifier onlyInitializing() {
        require(_initializing, "Initializable: contract is not initializing");
        _;
    }

    function _isConstructor() private view returns (bool) {
        return !AddressUpgradeable.isContract(address(this));
    }
}

File 4 of 32 : ERC165StorageUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165Storage.sol)

pragma solidity ^0.8.0;

import "./ERC165Upgradeable.sol";
import "../../proxy/utils/Initializable.sol";

/**
 * @dev Storage based implementation of the {IERC165} interface.
 *
 * Contracts may inherit from this and call {_registerInterface} to declare
 * their support of an interface.
 */
abstract contract ERC165StorageUpgradeable is Initializable, ERC165Upgradeable {
    function __ERC165Storage_init() internal onlyInitializing {
        __ERC165_init_unchained();
        __ERC165Storage_init_unchained();
    }

    function __ERC165Storage_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev Mapping of interface ids to whether or not it's supported.
     */
    mapping(bytes4 => bool) private _supportedInterfaces;

    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return super.supportsInterface(interfaceId) || _supportedInterfaces[interfaceId];
    }

    /**
     * @dev Registers the contract as an implementer of the interface defined by
     * `interfaceId`. Support of the actual ERC165 interface is automatic and
     * registering its interface id is not required.
     *
     * See {IERC165-supportsInterface}.
     *
     * Requirements:
     *
     * - `interfaceId` cannot be the ERC165 invalid interface (`0xffffffff`).
     */
    function _registerInterface(bytes4 interfaceId) internal virtual {
        require(interfaceId != 0xffffffff, "ERC165: invalid interface id");
        _supportedInterfaces[interfaceId] = true;
    }
    uint256[49] private __gap;
}

File 5 of 32 : MixinDisable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
 * The ability to disable locks has been removed on v10 to decrease contract code size.
 * Disabling locks can be achieved by setting `setMaxNumberOfKeys` to `totalSupply`
 * and expire all existing keys.
 * @dev the variables are kept to prevent conflicts in storage layout during upgrades
 */
contract MixinDisable {
  bool isAlive;
  uint256[1000] private __safe_upgrade_gap;
}

File 6 of 32 : MixinERC721Enumerable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import './MixinKeys.sol';
import './MixinLockCore.sol';
// import '@openzeppelin/contracts-upgradeable/token/ERC721/extensions/IERC721EnumerableUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/utils/introspection/ERC165StorageUpgradeable.sol';


/**
 * @title Implements the ERC-721 Enumerable extension.
 */
contract MixinERC721Enumerable is
  ERC165StorageUpgradeable,
  MixinLockCore, // Implements totalSupply
  MixinKeys
{
  function _initializeMixinERC721Enumerable() internal
  {
    /**
     * register the supported interface to conform to ERC721Enumerable via ERC165
     * 0x780e9d63 ===
     *     bytes4(keccak256('totalSupply()')) ^
     *     bytes4(keccak256('tokenOfOwnerByIndex(address,uint256)')) ^
     *     bytes4(keccak256('tokenByIndex(uint256)'))
     */
    _registerInterface(0x780e9d63);
  }

  /// @notice Enumerate valid NFTs
  /// @dev Throws if `_index` >= `totalSupply()`.
  /// @param _index A counter less than `totalSupply()`
  /// @return The token identifier for the `_index`th NFT,
  ///  (sort order not specified)
  function tokenByIndex(
    uint256 _index
  ) public view
    returns (uint256)
  {
    require(_index < _totalSupply, 'OUT_OF_RANGE');
    return _index;
  }

  function supportsInterface(bytes4 interfaceId) 
    public 
    view 
    virtual 
    override(
      AccessControlUpgradeable,
      ERC165StorageUpgradeable
    ) 
    returns (bool) 
    {
    return super.supportsInterface(interfaceId);
  }
  
  uint256[1000] private __safe_upgrade_gap;
}

File 7 of 32 : MixinFunds.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import '@openzeppelin/contracts-upgradeable/token/ERC20/IERC20Upgradeable.sol';
import '@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol';

/**
 * @title An implementation of the money related functions.
 * @author HardlyDifficult (unlock-protocol.com)
 */
contract MixinFunds
{
  using AddressUpgradeable for address payable;
  using SafeERC20Upgradeable for IERC20Upgradeable;

  /**
   * The token-type that this Lock is priced in.  If 0, then use ETH, else this is
   * a ERC20 token address.
   */
  address public tokenAddress;

  function _initializeMixinFunds(
    address _tokenAddress
  ) internal
  {
    _isValidToken(_tokenAddress);
    tokenAddress = _tokenAddress;
  }

  function _isValidToken(
    address _tokenAddress
  ) 
  internal 
  view
  {
    require(
      _tokenAddress == address(0) || IERC20Upgradeable(_tokenAddress).totalSupply() > 0,
      'INVALID_TOKEN'
    );
  }

  /**
   * Transfers funds from the contract to the account provided.
   *
   * Security: be wary of re-entrancy when calling this function.
   */
  function _transfer(
    address _tokenAddress,
    address payable _to,
    uint _amount
  ) internal
  {
    if(_amount > 0) {
      if(_tokenAddress == address(0)) {
        // https://diligence.consensys.net/blog/2019/09/stop-using-soliditys-transfer-now/
        _to.sendValue(_amount);
      } else {
        IERC20Upgradeable token = IERC20Upgradeable(_tokenAddress);
        token.safeTransfer(_to, _amount);
      }
    }
  }

  uint256[1000] private __safe_upgrade_gap;
}

File 8 of 32 : MixinGrantKeys.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import './MixinKeys.sol';
import './MixinRoles.sol';


/**
 * @title Mixin allowing the Lock owner to grant / gift keys to users.
 * @author HardlyDifficult
 * @dev `Mixins` are a design pattern seen in the 0x contracts.  It simply
 * separates logically groupings of code to ease readability.
 */
contract MixinGrantKeys is
  MixinRoles,
  MixinKeys
{
  /**
   * Allows the Lock owner to give a collection of users a key with no charge.
   * Each key may be assigned a different expiration date.
   */
  function grantKeys(
    address[] calldata _recipients,
    uint[] calldata _expirationTimestamps,
    address[] calldata _keyManagers
  ) external {
    _lockIsUpToDate();
    require(isKeyGranter(msg.sender) || isLockManager(msg.sender), 'ONLY_LOCK_MANAGER_OR_KEY_GRANTER');

    for(uint i = 0; i < _recipients.length; i++) {
      require(_recipients[i] != address(0), 'INVALID_ADDRESS');

      // an event is triggered
      _createNewKey(
        _recipients[i],
        _keyManagers[i],  
        _expirationTimestamps[i]
      ); 
    }
  }

  uint256[1000] private __safe_upgrade_gap;
}

File 9 of 32 : MixinKeys.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import './MixinLockCore.sol';

/**
 * @title Mixin for managing `Key` data, as well as the * Approval related functions needed to meet the ERC721
 * standard.
 * @author HardlyDifficult
 * @dev `Mixins` are a design pattern seen in the 0x contracts.  It simply
 * separates logically groupings of code to ease readability.
 */
contract MixinKeys is
  MixinLockCore
{
  // The struct for a key
  struct Key {
    uint tokenId;
    uint expirationTimestamp;
  }

  // Emitted when the Lock owner expires a user's Key
  event ExpireKey(uint indexed tokenId);

  // Emitted when the expiration of a key is modified
  event ExpirationChanged(
    uint indexed _tokenId,
    uint _amount,
    bool _timeAdded
  );

  // fire when a key is extended
  event KeyExtended(
    uint indexed tokenId,
    uint newTimestamp
  );

  
  event KeyManagerChanged(uint indexed _tokenId, address indexed _newManager);

  event KeysMigrated(
    uint updatedRecordsCount
  );

  // Deprecated: don't use this anymore as we know enable multiple keys per owner.
  mapping (address => Key) internal keyByOwner;

  // Each tokenId can have at most exactly one owner at a time.
  // Returns address(0) if the token does not exist
  mapping (uint => address) internal _ownerOf;

  // Keep track of the total number of unique owners for this lock (both expired and valid).
  // This may be larger than totalSupply
  uint public numberOfOwners;

  // A given key has both an owner and a manager.
  // If keyManager == address(0) then the key owner is also the manager
  // Each key can have at most 1 keyManager.
  mapping (uint => address) public keyManagerOf;

  // Keeping track of approved transfers
  // This is a mapping of addresses which have approved
  // the transfer of a key to another address where their key can be transferred
  // Note: the approver may actually NOT have a key... and there can only
  // be a single approved address
  mapping (uint => address) private approved;

  // Keeping track of approved operators for a given Key manager.
  // This approves a given operator for all keys managed by the calling "keyManager"
  // The caller may not currently be the keyManager for ANY keys.
  // These approvals are never reset/revoked automatically, unlike "approved",
  // which is reset on transfer.
  mapping (address => mapping (address => bool)) internal managerToOperatorApproved;

  // store all keys: tokenId => token
  mapping(uint256 => Key) internal _keys;
  
  // store ownership: owner => array of tokens owned by that owner
  mapping(address => mapping(uint256 => uint256)) private _ownedKeyIds;
  
  // store indexes: owner => list of tokenIds
  mapping(uint256 => uint256) private _ownedKeysIndex;

  // Mapping owner address to token count
  mapping(address => uint256) private _balances;
  
  /** 
   * Ensure that the caller is the keyManager of the key
   * or that the caller has been approved
   * for ownership of that key
   * @dev This is a modifier
   */ 
  function _onlyKeyManagerOrApproved(
    uint _tokenId
  )
  internal
  view
  {
    require(
      _isKeyManager(_tokenId, msg.sender) ||
      approved[_tokenId] == msg.sender ||
      isApprovedForAll(_ownerOf[_tokenId], msg.sender),
      'ONLY_KEY_MANAGER_OR_APPROVED'
    );
  }

  /**
   * Ensures that an owner has a valid key
   * @param _user the account to check
   * @dev This is a modifier
   */ 
  function _hasValidKey(
    address _user
  ) 
  internal 
  view 
  {
    require(
      getHasValidKey(_user), 'KEY_NOT_VALID'
    );
  }

  /**
   * Check if a key is expired or not
   * @dev This is a modifier
   */
  function _isValidKey(
    uint _tokenId
  ) 
  internal
  view
  {
    require(
      isValidKey(_tokenId),
      'KEY_NOT_VALID'
    );
  }

  /**
   * Check if a key actually exists
   * @dev This is a modifier
   */
  function _isKey(
    uint _tokenId
  ) 
  internal
  view 
  {
    require(
      _keys[_tokenId].expirationTimestamp != 0, 'NO_SUCH_KEY'
    );
  }

  /**
   * Deactivate an existing key
   * @param _tokenId the id of token to burn
   * @notice the key will be expired and ownership records will be destroyed
   */
  function burn(uint _tokenId) public {
    _isKey(_tokenId);
    _onlyKeyManagerOrApproved(_tokenId);

    emit Transfer(_ownerOf[_tokenId], address(0), _tokenId);

    // delete ownership and expire key
    _cancelKey(_tokenId);
  }

  /**
    * Migrate data from the previous single owner => key mapping to 
    * the new data structure w multiple tokens.
    * @param _calldata an ABI-encoded representation of the params 
    * for v10: `(uint _startIndex, uint nbRecordsToUpdate)`
    * -  `_startIndex` : the index of the first record to migrate
    * -  `_nbRecordsToUpdate` : number of records to migrate
    * @dev if all records can be processed at once, the `schemaVersion` will be updated
    * if not, you will have to call `updateSchemaVersion`
    * variable to the latest/current lock version
    */
  function migrate(
    bytes calldata _calldata
  ) virtual public {
    
    // make sure we have correct data version before migrating
    require(
      (
        (schemaVersion == publicLockVersion() - 1)
        ||
        schemaVersion == 0
      ),
      'SCHEMA_VERSION_NOT_CORRECT'
    );

    // set default value to 1
    if(_maxKeysPerAddress == 0) {
      _maxKeysPerAddress = 1;
    }

    // count the records that are actually migrated
    uint startIndex = 0;
    
    // count the records that are actually migrated
    uint updatedRecordsCount;

    // the index of the last record to migrate in this call
    uint nbRecordsToUpdate;

    // the total number of records to migrate
    uint totalSupply = totalSupply();
    
    // default to 100 when sent from Unlock, as this is called by default in the upgrade script.
    // If there are more than 100 keys, the migrate function will need to be called again until all keys have been migrated.
    if( msg.sender == address(unlockProtocol) ) {
      nbRecordsToUpdate = 100;
    } else {
      // decode param
      (startIndex, nbRecordsToUpdate) = abi.decode(_calldata, (uint, uint));
    }

    // cap the number of records to migrate to totalSupply
    if(nbRecordsToUpdate > totalSupply) nbRecordsToUpdate = totalSupply;

    for (uint256 i = startIndex; i < startIndex + nbRecordsToUpdate; i++) {
      // tokenId starts at 1
      uint tokenId = i + 1;
      address keyOwner = _ownerOf[tokenId];
      Key memory k = keyByOwner[keyOwner];

      // make sure key exists
      if(k.tokenId != 0 && k.expirationTimestamp != 0) {

        // copy key in new mapping
        _keys[i + 1] = Key(k.tokenId, k.expirationTimestamp);
        
        // delete token from previous owner
        delete keyByOwner[keyOwner];

        // record new owner
        _createOwnershipRecord(
          tokenId,
          keyOwner
        );

        // keep track of updated records
        updatedRecordsCount++;
      }
    }
    
    // enable lock if all keys has been migrated in a single run
    if(nbRecordsToUpdate >= totalSupply) {
      schemaVersion = publicLockVersion();
    }

    emit KeysMigrated(
      updatedRecordsCount // records that have been migrated
    );
  }

  /**
   * Set the schema version to the latest
   * @notice only lock manager call call this
   */
  function updateSchemaVersion() public {
    _onlyLockManager();
    schemaVersion = publicLockVersion();
  }

  /**
    * Returns the id of a key for a specific owner at a specific index
    * @notice Enumerate keys assigned to an owner
    * @dev Throws if `_index` >= `balanceOf(_keyOwner)` or if
    *  `_keyOwner` is the zero address, representing invalid keys.
    * @param _keyOwner address of the owner
    * @param _index position index in the array of all keys - less than `balanceOf(_keyOwner)`
    * @return The token identifier for the `_index`th key assigned to `_keyOwner`,
    *   (sort order not specified)
    * NB: name kept to be ERC721 compatible
    */
  function tokenOfOwnerByIndex(
    address _keyOwner,
    uint256 _index
  ) 
    public 
    view
    returns (uint256)
  {
      require(_index < balanceOf(_keyOwner), "OWNER_INDEX_OUT_OF_BOUNDS");
      return _ownedKeyIds[_keyOwner][_index];
  }

  /**
   * Create a new key with a new tokenId and store it 
   * 
   */
  function _createNewKey(
    address _recipient,
    address _keyManager,
    uint expirationTimestamp
  ) 
  internal 
  returns (uint tokenId) {
    
    // We increment the tokenId counter
    _totalSupply++;
    tokenId = _totalSupply;

    // create the key
    _keys[tokenId] = Key(tokenId, expirationTimestamp);
    
    // increase total number of unique owners
    if(balanceOf(_recipient) == 0 ) {
      numberOfOwners++;
    }

    // store ownership
    _createOwnershipRecord(tokenId, _recipient);

    // set key manager
    _setKeyManagerOf(tokenId, _keyManager);

    // trigger event
    emit Transfer(
      address(0), // This is a creation.
      _recipient,
      tokenId
    );
  }

  function _extendKey(
    uint _tokenId
  ) internal 
    returns (
      uint newTimestamp
    )
  {
    uint expirationTimestamp = _keys[_tokenId].expirationTimestamp;

    // prevent extending a valid non-expiring key
    require(expirationTimestamp != type(uint).max, 'CANT_EXTEND_NON_EXPIRING_KEY');
    
    // if non-expiring but not valid then extend
    if(expirationDuration == type(uint).max) {
      newTimestamp = type(uint).max;
    } else {
      if (expirationTimestamp > block.timestamp) {
        // extends a valid key  
        newTimestamp = expirationTimestamp + expirationDuration;
      } else {
        // renew an expired or cancelled key
        newTimestamp = block.timestamp + expirationDuration;
      }
    }

    _keys[_tokenId].expirationTimestamp = newTimestamp;

    emit KeyExtended(_tokenId, newTimestamp);
  } 

  /**
   * Record ownership info and udpate balance for new owner
   * @param _tokenId the id of the token to cancel
   * @param _recipient the address of the new owner
   */
  function _createOwnershipRecord(
   uint _tokenId,
   address _recipient
  ) internal { 
    uint length = balanceOf(_recipient);
    
    // make sure address does not have more keys than allowed
    require(length < _maxKeysPerAddress, 'MAX_KEYS');

    // record new owner
    _ownedKeysIndex[_tokenId] = length;
    _ownedKeyIds[_recipient][length] = _tokenId;

    // update ownership mapping
    _ownerOf[_tokenId] = _recipient;
    _balances[_recipient] += 1;
  }

  /**
   * Merge existing keys
   * @param _tokenIdFrom the id of the token to substract time from
   * @param _tokenIdTo the id of the destination token  to add time
   * @param _amount the amount of time to transfer (in seconds)
   */
  function mergeKeys(
    uint _tokenIdFrom, 
    uint _tokenIdTo, 
    uint _amount
    ) public {

    // checks
    _isKey(_tokenIdFrom);
    _isValidKey(_tokenIdFrom);
    _onlyKeyManagerOrApproved(_tokenIdFrom);
    _isKey(_tokenIdTo);
    
    // make sure there is enough time remaining
    require(keyExpirationTimestampFor(_tokenIdFrom) - block.timestamp >= _amount, 'NOT_ENOUGH_TIME');

    // deduct time from parent key
    _timeMachine(_tokenIdFrom, _amount, false);

    // add time to destination key
    _timeMachine(_tokenIdTo, _amount, true);

  }

  /**
   * Delete ownership info and udpate balance for previous owner
   * @param _tokenId the id of the token to cancel
   */
  function _deleteOwnershipRecord(
    uint _tokenId
  ) internal {
    // get owner
    address previousOwner = _ownerOf[_tokenId];

    // delete previous ownership
    uint lastTokenIndex = balanceOf(previousOwner) - 1;
    uint index = _ownedKeysIndex[_tokenId];

    // When the token to delete is the last token, the swap operation is unnecessary
    if (index != lastTokenIndex) {
        uint256 lastTokenId = _ownedKeyIds[previousOwner][lastTokenIndex];
        _ownedKeyIds[previousOwner][index] = lastTokenId; // Move the last token to the slot of the to-delete token
        _ownedKeysIndex[lastTokenId] = index; // Update the moved token's index
    }

    // Deletes the contents at the last position of the array
    delete _ownedKeyIds[previousOwner][lastTokenIndex];

    // remove from owner count if thats the only key 
    if(balanceOf(previousOwner) == 1 ) {
      numberOfOwners--;
    }
    // update balance
    _balances[previousOwner] -= 1;
  }

  /**
   * Delete ownership info about a key and expire the key
   * @param _tokenId the id of the token to cancel
   * @notice this won't 'burn' the token, as it would still exist in the record
   */
  function _cancelKey(
    uint _tokenId
  ) internal {
    
    // Deletes the contents at the last position of the array
    _deleteOwnershipRecord(_tokenId);

    // expire the key
    _keys[_tokenId].expirationTimestamp = block.timestamp;

    // delete previous owner
    _ownerOf[_tokenId] = address(0);
  }

  /**
   * In the specific case of a Lock, each owner can own only at most 1 key.
   * @return The number of NFTs owned by `_keyOwner`, either 0 or 1.
  */
  function balanceOf(
    address _keyOwner
  )
    public
    view
    returns (uint)
  {
    require(_keyOwner != address(0), 'INVALID_ADDRESS');
    return _balances[_keyOwner];
  }

  /**
   * Check if a certain key is valid
   * @param _tokenId the id of the key to check validity
   * @notice this makes use of the onValidKeyHook if it is set
   */
  function isValidKey(
    uint _tokenId
  )
    public
    view
    returns (bool)
  { 
    bool isValid = _keys[_tokenId].expirationTimestamp > block.timestamp;
    return isValid;
  }   

  /**
   * Checks if the user has at least one non-expired key.
   * @param _keyOwner the 
   */
  function getHasValidKey(
    address _keyOwner
  )
    public
    view
    returns (bool isValid)
  { 
    uint length = balanceOf(_keyOwner);
    if(length > 0) {
      for (uint i = 0; i < length; i++) {
        if(isValidKey(tokenOfOwnerByIndex(_keyOwner, i))) {
          return true; // stop looping at the first valid key
        }
      }
    }

    // use hook if it exists
    if(address(onValidKeyHook) != address(0)) {
      isValid = onValidKeyHook.hasValidKey(
        address(this),
        _keyOwner,
        0, // no timestamp needed (we use tokenId)
        isValid
      );
    }
    return isValid;   
  }

  /**
    * Returns the key's ExpirationTimestamp field for a given token.
    * @param _tokenId the tokenId of the key
    * @dev Returns 0 if the owner has never owned a key for this lock
    */
  function keyExpirationTimestampFor(
    uint _tokenId
  ) public view
    returns (uint)
  {
    return _keys[_tokenId].expirationTimestamp;
  }
 
  /** 
   *  Returns the owner of a given tokenId
   * @param _tokenId the id of the token
   * @return the address of the owner
   */ 
  function ownerOf(
    uint _tokenId
  ) public view
    returns(address)
  {
    return _ownerOf[_tokenId];
  }

  /**
   * @notice Public function for updating transfer and cancel rights for a given key
   * @param _tokenId The id of the key to assign rights for
   * @param _keyManager The address with the manager's rights for the given key.
   * Setting _keyManager to address(0) means the keyOwner is also the keyManager
   */
  function setKeyManagerOf(
    uint _tokenId,
    address _keyManager
  ) public
  {
    _isKey(_tokenId);
    require(
      _isKeyManager(_tokenId, msg.sender) ||
      isLockManager(msg.sender),
      'UNAUTHORIZED_KEY_MANAGER_UPDATE'
    );
    _setKeyManagerOf(_tokenId, _keyManager);
  }

  function _setKeyManagerOf(
    uint _tokenId,
    address _keyManager
  ) internal
  {
    if(keyManagerOf[_tokenId] != _keyManager) {
      keyManagerOf[_tokenId] = _keyManager;
      _clearApproval(_tokenId);
      emit KeyManagerChanged(_tokenId, _keyManager);
    }
  }

    /**
   * This approves _approved to get ownership of _tokenId.
   * Note: that since this is used for both purchase and transfer approvals
   * the approved token may not exist.
   */
  function approve(
    address _approved,
    uint _tokenId
  )
    public
  {
    _onlyKeyManagerOrApproved(_tokenId);
    require(msg.sender != _approved, 'APPROVE_SELF');

    approved[_tokenId] = _approved;
    emit Approval(_ownerOf[_tokenId], _approved, _tokenId);
  }

    /**
   * @notice Get the approved address for a single NFT
   * @dev Throws if `_tokenId` is not a valid NFT.
   * @param _tokenId The NFT to find the approved address for
   * @return The approved address for this NFT, or the zero address if there is none
   */
  function getApproved(
    uint _tokenId
  ) public view
    returns (address)
  {
    _isKey(_tokenId);
    address approvedRecipient = approved[_tokenId];
    return approvedRecipient;
  }

    /**
   * @dev Tells whether an operator is approved by a given keyManager
   * @param _owner owner address which you want to query the approval of
   * @param _operator operator address which you want to query the approval of
   * @return bool whether the given operator is approved by the given owner
   */
  function isApprovedForAll(
    address _owner,
    address _operator
  ) public view
    returns (bool)
  {
    return managerToOperatorApproved[_owner][_operator];
  }

  /**
   * Returns true if _keyManager is the manager of the key
   * identified by _tokenId
   */
  function _isKeyManager(
    uint _tokenId,
    address _keyManager
  ) internal view
    returns (bool)
  {
    if(keyManagerOf[_tokenId] == _keyManager ||
      (keyManagerOf[_tokenId] == address(0) && ownerOf(_tokenId) == _keyManager)) {
      return true;
    } else {
      return false;
    }
  }

  /**
    * @notice Modify the expirationTimestamp of a key
    * by a given amount.
    * @param _tokenId The ID of the key to modify.
    * @param _deltaT The amount of time in seconds by which
    * to modify the keys expirationTimestamp
    * @param _addTime Choose whether to increase or decrease
    * expirationTimestamp (false == decrease, true == increase)
    * @dev Throws if owner does not have a valid key.
    */
  function _timeMachine(
    uint _tokenId,
    uint256 _deltaT,
    bool _addTime
  ) internal
  {
    _isKey(_tokenId);

    uint formerTimestamp = _keys[_tokenId].expirationTimestamp;

    if(_addTime) {
      if(formerTimestamp > block.timestamp) {
        // append to valid key
        _keys[_tokenId].expirationTimestamp = formerTimestamp + _deltaT;
      } else {
        // add from now if key is expired
        _keys[_tokenId].expirationTimestamp = block.timestamp + _deltaT;
      }
    } else {
        _keys[_tokenId].expirationTimestamp = formerTimestamp - _deltaT;
    }

    emit ExpirationChanged(_tokenId, _deltaT, _addTime);
  }

  /**
   * @dev Function to clear current approval of a given token ID
   * @param _tokenId uint256 ID of the token to be transferred
   */
  function _clearApproval(
    uint256 _tokenId
  ) internal
  {
    if (approved[_tokenId] != address(0)) {
      approved[_tokenId] = address(0);
    }
  }

  /**
   * @notice Change the maximum number of keys the lock can edit
   * @param _maxNumberOfKeys uint the maximum number of keys
   * @dev Can't be smaller than the existing supply
   */
  function setMaxNumberOfKeys (uint _maxNumberOfKeys) external {
     _onlyLockManager();
     require (_maxNumberOfKeys >= _totalSupply, "SMALLER_THAN_SUPPLY");
     maxNumberOfKeys = _maxNumberOfKeys;
  }

  /**
   * A function to change the default duration of each key in the lock
   * @notice keys previously bought are unaffected by this change (i.e.
   * existing keys timestamps are not recalculated/updated)
   * @param _newExpirationDuration the new amount of time for each key purchased 
   * or type(uint).max for a non-expiring key
   */
  function setExpirationDuration(uint _newExpirationDuration) external {
     _onlyLockManager();
     expirationDuration = _newExpirationDuration;
  }
  
  /**
   * Set the maximum number of keys a specific address can use
   * @param _maxKeys the maximum amount of key a user can own
   */
  function setMaxKeysPerAddress(uint _maxKeys) external {
     _onlyLockManager();
     require(_maxKeys != 0, 'NULL_VALUE');
     _maxKeysPerAddress = _maxKeys;
  }

  /**
   * @return the maximum number of key allowed for a single address
   */
  function maxKeysPerAddress() external view returns (uint) {
    return _maxKeysPerAddress;
  }
  
  // decrease 1000 to 996 when adding new tokens/owners mappings in v10
  uint256[996] private __safe_upgrade_gap;
}

File 10 of 32 : MixinLockCore.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// import '@openzeppelin/contracts-upgradeable/token/ERC721/extensions/IERC721EnumerableUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol';
import './MixinDisable.sol';
import './MixinRoles.sol';
import '../interfaces/IUnlock.sol';
import './MixinFunds.sol';
import '../interfaces/hooks/ILockKeyCancelHook.sol';
import '../interfaces/hooks/ILockKeyPurchaseHook.sol';
import '../interfaces/hooks/ILockValidKeyHook.sol';
import '../interfaces/hooks/ILockTokenURIHook.sol';

/**
 * @title Mixin for core lock data and functions.
 * @author HardlyDifficult
 * @dev `Mixins` are a design pattern seen in the 0x contracts.  It simply
 * separates logically groupings of code to ease readability.
 */
contract MixinLockCore is
  MixinRoles,
  MixinFunds,
  MixinDisable
{
  using AddressUpgradeable for address;

  event Withdrawal(
    address indexed sender,
    address indexed tokenAddress,
    address indexed beneficiary,
    uint amount
  );

  event PricingChanged(
    uint oldKeyPrice,
    uint keyPrice,
    address oldTokenAddress,
    address tokenAddress
  );

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

  // Unlock Protocol address
  // TODO: should we make that private/internal?
  IUnlock public unlockProtocol;

  // Duration in seconds for which the keys are valid, after creation
  // should we take a smaller type use less gas?
  uint public expirationDuration;

  // price in wei of the next key
  // TODO: allow support for a keyPriceCalculator which could set prices dynamically
  uint public keyPrice;

  // Max number of keys sold if the keyReleaseMechanism is public
  uint public maxNumberOfKeys;

  // A count of how many new key purchases there have been
  uint internal _totalSupply;

  // The account which will receive funds on withdrawal
  address payable public beneficiary;

  // The denominator component for values specified in basis points.
  uint internal constant BASIS_POINTS_DEN = 10000;

  ILockKeyPurchaseHook public onKeyPurchaseHook;
  ILockKeyCancelHook public onKeyCancelHook;
  ILockValidKeyHook public onValidKeyHook;
  ILockTokenURIHook public onTokenURIHook;

  // use to check data version
  uint public schemaVersion;

  // keep track of how many key a single address can use
  uint internal _maxKeysPerAddress;

  // modifier to check if data has been upgraded
  function _lockIsUpToDate() internal view {
    require(
      schemaVersion == publicLockVersion(),
      'MIGRATION_REQUIRED'
    );
  }

  // modifier
  function _onlyLockManagerOrBeneficiary() 
  internal 
  view
  {
    require(
      isLockManager(msg.sender) || msg.sender == beneficiary,
      'ONLY_LOCK_MANAGER_OR_BENEFICIARY'
    );
  }
  
  function _initializeMixinLockCore(
    address payable _beneficiary,
    uint _expirationDuration,
    uint _keyPrice,
    uint _maxNumberOfKeys
  ) internal
  {
    unlockProtocol = IUnlock(msg.sender); // Make sure we link back to Unlock's smart contract.
    beneficiary = _beneficiary;
    expirationDuration = _expirationDuration;
    keyPrice = _keyPrice;
    maxNumberOfKeys = _maxNumberOfKeys;

    // update only when initialized
    schemaVersion = publicLockVersion();

    // only a single key per address is allowed by default
    _maxKeysPerAddress = 1;
  }

  // The version number of the current implementation on this network
  function publicLockVersion(
  ) public pure
    returns (uint16)
  {
    return 10;
  }

  /**
   * @dev Called by owner to withdraw all funds from the lock and send them to the `beneficiary`.
   * @param _tokenAddress specifies the token address to withdraw or 0 for ETH. This is usually
   * the same as `tokenAddress` in MixinFunds.
   * @param _amount specifies the max amount to withdraw, which may be reduced when
   * considering the available balance. Set to 0 or MAX_UINT to withdraw everything.
   */
  function withdraw(
    address _tokenAddress,
    uint _amount
  ) external
  {
    _onlyLockManagerOrBeneficiary();

    // get balance
    uint balance;
    if(_tokenAddress == address(0)) {
      balance = address(this).balance;
    } else {
      balance = IERC20Upgradeable(_tokenAddress).balanceOf(address(this));
    }

    uint amount;
    if(_amount == 0 || _amount > balance)
    {
      require(balance > 0, 'NOT_ENOUGH_FUNDS');
      amount = balance;
    }
    else
    {
      amount = _amount;
    }

    emit Withdrawal(msg.sender, _tokenAddress, beneficiary, amount);
    // Security: re-entrancy not a risk as this is the last line of an external function
    _transfer(_tokenAddress, beneficiary, amount);
  }

  /**
   * A function which lets the owner of the lock change the pricing for future purchases.
   * This consists of 2 parts: The token address and the price in the given token.
   * In order to set the token to ETH, use 0 for the token Address.
   */
  function updateKeyPricing(
    uint _keyPrice,
    address _tokenAddress
  )
    external
  {
    _onlyLockManager();
    _isValidToken(_tokenAddress);
    uint oldKeyPrice = keyPrice;
    address oldTokenAddress = tokenAddress;
    keyPrice = _keyPrice;
    tokenAddress = _tokenAddress;
    emit PricingChanged(oldKeyPrice, keyPrice, oldTokenAddress, tokenAddress);
  }

  /**
   * A function which lets the owner of the lock update the beneficiary account,
   * which receives funds on withdrawal.
   */
  function updateBeneficiary(
    address payable _beneficiary
  ) external {
    _onlyLockManagerOrBeneficiary();
    require(_beneficiary != address(0), 'INVALID_ADDRESS');
    beneficiary = _beneficiary;
  }

  /**
   * @notice Allows a lock manager to add or remove an event hook
   */
  function setEventHooks(
    address _onKeyPurchaseHook,
    address _onKeyCancelHook,
    address _onValidKeyHook,
    address _onTokenURIHook
  ) external
  {
    _onlyLockManager();
    require(_onKeyPurchaseHook == address(0) || _onKeyPurchaseHook.isContract(), 'INVALID_ON_KEY_SOLD_HOOK');
    require(_onKeyCancelHook == address(0) || _onKeyCancelHook.isContract(), 'INVALID_ON_KEY_CANCEL_HOOK');
    require(_onValidKeyHook == address(0) || _onValidKeyHook.isContract(), 'INVALID_ON_VALID_KEY_HOOK');
    require(_onTokenURIHook == address(0) || _onTokenURIHook.isContract(), 'INVALID_ON_TOKEN_URI_HOOK');
    onKeyPurchaseHook = ILockKeyPurchaseHook(_onKeyPurchaseHook);
    onKeyCancelHook = ILockKeyCancelHook(_onKeyCancelHook);
    onTokenURIHook = ILockTokenURIHook(_onTokenURIHook);
    onValidKeyHook = ILockValidKeyHook(_onValidKeyHook);
  }

  function totalSupply()
    public
    view returns(uint256)
  {
    return _totalSupply;
  }

  /**
   * @notice An ERC-20 style approval, allowing the spender to transfer funds directly from this lock.
   */
  function approveBeneficiary(
    address _spender,
    uint _amount
  ) public
    returns (bool)
  {
    _onlyLockManagerOrBeneficiary();
    return IERC20Upgradeable(tokenAddress).approve(_spender, _amount);
  }


  // decreased from 1000 to 998 when adding `schemaVersion` and `maxKeysPerAddress` in v10 
  uint256[998] private __safe_upgrade_gap;
}

File 11 of 32 : MixinLockMetadata.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import '@openzeppelin/contracts-upgradeable/utils/introspection/ERC165StorageUpgradeable.sol';
// import '@openzeppelin/contracts-upgradeable/token/ERC721/extensions/IERC721EnumerableUpgradeable.sol';
import '../UnlockUtils.sol';
import './MixinKeys.sol';
import './MixinLockCore.sol';
import './MixinRoles.sol';

/**
 * @title Mixin for metadata about the Lock.
 * @author HardlyDifficult
 * @dev `Mixins` are a design pattern seen in the 0x contracts.  It simply
 * separates logically groupings of code to ease readability.
 */
contract MixinLockMetadata is
  ERC165StorageUpgradeable,
  MixinRoles,
  MixinLockCore,
  MixinKeys
{
  using UnlockUtils for uint;
  using UnlockUtils for address;
  using UnlockUtils for string;

  /// A descriptive name for a collection of NFTs in this contract.Defaults to "Unlock-Protocol" but is settable by lock owner
  string public name;

  /// An abbreviated name for NFTs in this contract. Defaults to "KEY" but is settable by lock owner
  string private lockSymbol;

  // the base Token URI for this Lock. If not set by lock owner, the global URI stored in Unlock is used.
  string private baseTokenURI;

  event NewLockSymbol(
    string symbol
  );

  function _initializeMixinLockMetadata(
    string calldata _lockName
  ) internal
  {
    ERC165StorageUpgradeable.__ERC165Storage_init();
    name = _lockName;
    // registering the optional erc721 metadata interface with ERC165.sol using
    // the ID specified in the standard: https://eips.ethereum.org/EIPS/eip-721
    _registerInterface(0x5b5e139f);
  }

  /**
   * Allows the Lock owner to assign a descriptive name for this Lock.
   */
  function updateLockName(
    string calldata _lockName
  ) external
  {
    _onlyLockManager();
    name = _lockName;
  }

  /**
   * Allows the Lock owner to assign a Symbol for this Lock.
   */
  function updateLockSymbol(
    string calldata _lockSymbol
  ) external
  {
    _onlyLockManager();
    lockSymbol = _lockSymbol;
    emit NewLockSymbol(_lockSymbol);
  }

  /**
    * @dev Gets the token symbol
    * @return string representing the token name
    */
  function symbol()
    external view
    returns(string memory)
  {
    if(bytes(lockSymbol).length == 0) {
      return unlockProtocol.globalTokenSymbol();
    } else {
      return lockSymbol;
    }
  }

  /**
   * Allows the Lock owner to update the baseTokenURI for this Lock.
   */
  function setBaseTokenURI(
    string calldata _baseTokenURI
  ) external
  {
    _onlyLockManager();
    baseTokenURI = _baseTokenURI;
  }

  /**  @notice A distinct Uniform Resource Identifier (URI) for a given asset.
   * @param _tokenId The iD of the token  for which we want to retrieve the URI.
   * If 0 is passed here, we just return the appropriate baseTokenURI.
   * If a custom URI has been set we don't return the lock address.
   * It may be included in the custom baseTokenURI if needed.
   * @dev  URIs are defined in RFC 3986. The URI may point to a JSON file
   * that conforms to the "ERC721 Metadata JSON Schema".
   * https://github.com/ethereum/EIPs/blob/master/EIPS/eip-721.md
   */
  function tokenURI(
    uint256 _tokenId
  ) external
    view
    returns(string memory)
  {
    string memory URI;
    string memory tokenId;
    string memory lockAddress = address(this).address2Str();
    string memory seperator;

    if(_tokenId != 0) {
      tokenId = _tokenId.uint2Str();
    } else {
      tokenId = '';
    }

    if(address(onTokenURIHook) != address(0))
    {
      uint expirationTimestamp = keyExpirationTimestampFor(_tokenId);
      return onTokenURIHook.tokenURI(
        address(this),
        msg.sender,
        ownerOf(_tokenId),
        _tokenId,
        expirationTimestamp
        );
    }

    if(bytes(baseTokenURI).length == 0) {
      URI = unlockProtocol.globalBaseTokenURI();
      seperator = '/';
    } else {
      URI = baseTokenURI;
      seperator = '';
      lockAddress = '';
    }

    return URI.strConcat(
        lockAddress,
        seperator,
        tokenId
      );
  }

  function supportsInterface(bytes4 interfaceId) 
    public 
    view 
    virtual 
    override(
      AccessControlUpgradeable,
      ERC165StorageUpgradeable
    ) 
    returns (bool) 
    {
    return super.supportsInterface(interfaceId);
  }

  uint256[1000] private __safe_upgrade_gap;
}

File 12 of 32 : MixinPurchase.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import './MixinDisable.sol';
import './MixinKeys.sol';
import './MixinLockCore.sol';
import './MixinFunds.sol';

/**
 * @title Mixin for the purchase-related functions.
 * @author HardlyDifficult
 * @dev `Mixins` are a design pattern seen in the 0x contracts.  It simply
 * separates logically groupings of code to ease readability.
 */
contract MixinPurchase is
  MixinFunds,
  MixinDisable,
  MixinLockCore,
  MixinKeys
{
  event RenewKeyPurchase(address indexed owner, uint newExpiration);

  event GasRefunded(address indexed receiver, uint refundedAmount, address tokenAddress);
  
  event UnlockCallFailed(address indexed lockAddress, address unlockAddress);

  // default to 0 
  uint256 internal _gasRefundValue;

  // Keep track of ERC20 price when purchased
  mapping(uint256 => uint256) private _originalPrices;
  
  // Keep track of duration when purchased
  mapping(uint256 => uint256) internal _originalDurations;
  
  // keep track of token pricing when purchased
  mapping(uint256 => address) private _originalTokens;

  /**
  * @dev Set the value/price to be refunded to the sender on purchase
  */

  function setGasRefundValue(uint256 _refundValue) external {
    _onlyLockManager();
    _gasRefundValue = _refundValue;
  }
  
  /**
  * @dev Returns value/price to be refunded to the sender on purchase
  */
  function gasRefundValue() external view returns (uint256 _refundValue) {
    return _gasRefundValue;
  }

  /**
  * @dev Helper to communicate with Unlock (record GNP and mint UDT tokens)
  */
  function _recordKeyPurchase (uint _keyPrice, address _referrer) internal  {
    // make sure unlock is a contract, and we catch possible reverts
      if (address(unlockProtocol).code.length > 0) {
        // call Unlock contract to record GNP
        // the function is capped by gas to prevent running out of gas
        try unlockProtocol.recordKeyPurchase{gas: 300000}(_keyPrice, _referrer) 
        {} 
        catch {
          // emit missing unlock
          emit UnlockCallFailed(address(this), address(unlockProtocol));
        }
      } else {
        // emit missing unlock
        emit UnlockCallFailed(address(this), address(unlockProtocol));
      }
  }

  /**
  * @dev Purchase function
  * @param _values array of tokens amount to pay for this purchase >= the current keyPrice - any applicable discount
  * (_values is ignored when using ETH)
  * @param _recipients array of addresses of the recipients of the purchased key
  * @param _referrers array of addresses of the users making the referral
  * @param _keyManagers optional array of addresses to grant managing rights to a specific address on creation
  * @param _data arbitrary data populated by the front-end which initiated the sale
  * @notice when called for an existing and non-expired key, the `_keyManager` param will be ignored 
  * @dev Setting _value to keyPrice exactly doubles as a security feature. That way if the lock owner increases the
  * price while my transaction is pending I can't be charged more than I expected (only applicable to ERC-20 when more
  * than keyPrice is approved for spending).
  */
  function purchase(
    uint256[] memory _values,
    address[] memory _recipients,
    address[] memory _referrers,
    address[] memory _keyManagers,
    bytes[] calldata _data
  ) external payable
  {
    _lockIsUpToDate();
    require(maxNumberOfKeys > _totalSupply, 'LOCK_SOLD_OUT');
    require(_recipients.length == _referrers.length, 'INVALID_REFERRERS_LENGTH');
    require(_recipients.length == _keyManagers.length, 'INVALID_KEY_MANAGERS_LENGTH');

    uint totalPriceToPay;
    uint tokenId;

    for (uint256 i = 0; i < _recipients.length; i++) {
      // check recipient address
      address _recipient = _recipients[i];
      require(_recipient != address(0), 'INVALID_ADDRESS');
      
      // check for a non-expiring key
      if (expirationDuration == type(uint).max) {
        // create a new key
        tokenId = _createNewKey(
          _recipient,
          _keyManagers[i],
          type(uint).max
        );
      } else {
        tokenId = _createNewKey(
          _recipient,
          _keyManagers[i],
          block.timestamp + expirationDuration
        );
      }

      // price      

      uint inMemoryKeyPrice = purchasePriceFor(_recipient, _referrers[i], _data[i]);
      totalPriceToPay = totalPriceToPay + inMemoryKeyPrice;

      // store values at purchase time
      _originalPrices[tokenId] = inMemoryKeyPrice;
      _originalDurations[tokenId] = expirationDuration;
      _originalTokens[tokenId] = tokenAddress;
      
      if(tokenAddress != address(0)) {
        require(inMemoryKeyPrice <= _values[i], 'INSUFFICIENT_ERC20_VALUE');
      }

      // store in unlock
      _recordKeyPurchase(inMemoryKeyPrice, _referrers[i]);

      // fire hook
      uint pricePaid = tokenAddress == address(0) ? msg.value : _values[i];
      if(address(onKeyPurchaseHook) != address(0)) {
        onKeyPurchaseHook.onKeyPurchase(
          msg.sender, 
          _recipient, 
          _referrers[i], 
          _data[i], 
          inMemoryKeyPrice, 
          pricePaid
        );
      }
    }

    // transfer the ERC20 tokens
    if(tokenAddress != address(0)) {
      IERC20Upgradeable token = IERC20Upgradeable(tokenAddress);
      token.transferFrom(msg.sender, address(this), totalPriceToPay);
    } else {
      // We explicitly allow for greater amounts of ETH or tokens to allow 'donations'
      require(totalPriceToPay <= msg.value, 'INSUFFICIENT_VALUE');
    }

    // refund gas
    _refundGas();
  }

  /**
  * @dev Extend function
  * @param _value the number of tokens to pay for this purchase >= the current keyPrice - any applicable discount
  * (_value is ignored when using ETH)
  * @param _tokenId id of the key to extend
  * @param _referrer address of the user making the referral
  * @param _data arbitrary data populated by the front-end which initiated the sale
  * @dev Throws if lock is disabled or key does not exist for _recipient. Throws if _recipient == address(0).
  */
  function extend(
    uint _value,
    uint _tokenId,
    address _referrer,
    bytes calldata _data
  ) 
    public 
    payable
  {
    _lockIsUpToDate();
    _isKey(_tokenId);

    // extend key duration
    _extendKey(_tokenId);

    // transfer the tokens
    uint inMemoryKeyPrice = purchasePriceFor(ownerOf(_tokenId), _referrer, _data);

    if(tokenAddress != address(0)) {
      require(inMemoryKeyPrice <= _value, 'INSUFFICIENT_ERC20_VALUE');
      IERC20Upgradeable token = IERC20Upgradeable(tokenAddress);
      token.transferFrom(msg.sender, address(this), inMemoryKeyPrice);
    } else {
      // We explicitly allow for greater amounts of ETH or tokens to allow 'donations'
      require(inMemoryKeyPrice <= msg.value, 'INSUFFICIENT_VALUE');
    }

    // refund gas (if applicable)
    _refundGas();
  }

  /**
  * Renew a given token
  * @notice only works for non-free, expiring, ERC20 locks
  * @param _tokenId the ID fo the token to renew
  * @param _referrer the address of the person to be granted UDT
  */
  function renewMembershipFor(
    uint _tokenId,
    address _referrer
  ) public {
    _lockIsUpToDate();
    _isKey(_tokenId);

    // check the lock
    require(_originalDurations[_tokenId] != type(uint).max, 'NON_EXPIRING_LOCK');
    require(tokenAddress != address(0), 'NON_ERC20_LOCK');

    // make sure duration and pricing havent changed  
    uint keyPrice = purchasePriceFor(ownerOf(_tokenId), _referrer, '');
    require(_originalPrices[_tokenId] == keyPrice, 'PRICE_CHANGED');
    require(_originalDurations[_tokenId] == expirationDuration, 'DURATION_CHANGED');
    require(_originalTokens[_tokenId] == tokenAddress, 'TOKEN_CHANGED');

    // make sure key is ready for renewal
    require(isValidKey(_tokenId) == false, 'NOT_READY');

    // extend key duration
    _extendKey(_tokenId);

    // store in unlock
    _recordKeyPurchase(keyPrice, _referrer);

    // transfer the tokens
    IERC20Upgradeable token = IERC20Upgradeable(tokenAddress);
    token.transferFrom(ownerOf(_tokenId), address(this), keyPrice);

    // refund gas if applicable
    _refundGas();
  }

  /**
   * @notice returns the minimum price paid for a purchase with these params.
   * @dev minKeyPrice considers any discount from Unlock or the OnKeyPurchase hook
   */
  function purchasePriceFor(
    address _recipient,
    address _referrer,
    bytes memory _data
  ) public view
    returns (uint minKeyPrice)
  {
    if(address(onKeyPurchaseHook) != address(0))
    {
      minKeyPrice = onKeyPurchaseHook.keyPurchasePrice(msg.sender, _recipient, _referrer, _data);
    }
    else
    {
      minKeyPrice = keyPrice;
    }
  }

  /**
   * Refund the specified gas amount and emit an event
   * @notice this does sth only if `_gasRefundValue` is non-null
   */
  function _refundGas() internal {
    if (_gasRefundValue != 0) { 
      if(tokenAddress != address(0)) {
        IERC20Upgradeable token = IERC20Upgradeable(tokenAddress);
        token.transferFrom(address(this), msg.sender, _gasRefundValue);
      } else {
        (bool success, ) = msg.sender.call{value: _gasRefundValue}("");
        require(success, "REFUND_FAILED");
      }
      emit GasRefunded(msg.sender, _gasRefundValue, tokenAddress);
    }
  }

  // decreased from 1000 to 997 when added mappings for initial purchases pricing and duration on v10 
  uint256[997] private __safe_upgrade_gap;
}

File 13 of 32 : MixinRefunds.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import './MixinKeys.sol';
import './MixinLockCore.sol';
import './MixinRoles.sol';
import './MixinFunds.sol';
import './MixinPurchase.sol';

contract MixinRefunds is
  MixinRoles,
  MixinFunds,
  MixinLockCore,
  MixinKeys,
  MixinPurchase
{
  // CancelAndRefund will return funds based on time remaining minus this penalty.
  // This is calculated as `proRatedRefund * refundPenaltyBasisPoints / BASIS_POINTS_DEN`.
  uint public refundPenaltyBasisPoints;

  uint public freeTrialLength;

  event CancelKey(
    uint indexed tokenId,
    address indexed owner,
    address indexed sendTo,
    uint refund
  );

  event RefundPenaltyChanged(
    uint freeTrialLength,
    uint refundPenaltyBasisPoints
  );

  function _initializeMixinRefunds() internal
  {
    // default to 10%
    refundPenaltyBasisPoints = 1000;
  }

  /**
   * @dev Invoked by the lock owner to destroy the user's key and perform a refund and cancellation
   * of the key
   * @param _tokenId The id of the key to expire
   * @param _amount The amount to refund
   */
  function expireAndRefundFor(
    uint _tokenId,
    uint _amount
  ) external {
    _isKey(_tokenId);
    _isValidKey(_tokenId);
    _onlyLockManager();
    _cancelAndRefund(_tokenId, _amount);
  }

  /**
   * @dev Destroys the key and sends a refund based on the amount of time remaining.
   * @param _tokenId The id of the key to cancel.
   */
  function cancelAndRefund(uint _tokenId)
    external
  {
    _isKey(_tokenId);
    _isValidKey(_tokenId);
    _onlyKeyManagerOrApproved(_tokenId);
    uint refund = getCancelAndRefundValue(_tokenId);
    _cancelAndRefund(_tokenId, refund);
  }

  /**
   * Allow the owner to change the refund penalty.
   */
  function updateRefundPenalty(
    uint _freeTrialLength,
    uint _refundPenaltyBasisPoints
  ) external {
    _onlyLockManager();
    emit RefundPenaltyChanged(
      _freeTrialLength,
      _refundPenaltyBasisPoints
    );

    freeTrialLength = _freeTrialLength;
    refundPenaltyBasisPoints = _refundPenaltyBasisPoints;
  }

  /**
   * @dev cancels the key for the given keyOwner and sends the refund to the msg.sender.
   * @notice this deletes ownership info and expire the key, but doesnt 'burn' it
   */
  function _cancelAndRefund(
    uint _tokenId,
    uint refund
  ) internal
  {
    address payable keyOwner = payable(ownerOf(_tokenId));
    
    // delete ownership info and expire the key
    _cancelKey(_tokenId);
    
    // emit event
    emit CancelKey(_tokenId, keyOwner, msg.sender, refund);
    
    if (refund > 0) {
      _transfer(tokenAddress, keyOwner, refund);
    }

    // make future reccuring transactions impossible
    _originalDurations[_tokenId] = 0;
    
    // inform the hook if there is one registered
    if(address(onKeyCancelHook) != address(0))
    {
      onKeyCancelHook.onKeyCancel(msg.sender, keyOwner, refund);
    }
  }

  /**
   * @dev Determines how much of a refund a key would be worth if a cancelAndRefund
   * is issued now.
   * @param _tokenId the key to check the refund value for.
   * @notice due to the time required to mine a tx, the actual refund amount will be lower
   * than what the user reads from this call.
   */
  function getCancelAndRefundValue(
    uint _tokenId
  )
    public view
    returns (uint refund)
  {
    _isValidKey(_tokenId);

    // return entire purchased price if key is non-expiring
    if(expirationDuration == type(uint).max) {
      return keyPrice;
    }

    // substract free trial value
    uint timeRemaining = keyExpirationTimestampFor(_tokenId) - block.timestamp;
    if(timeRemaining + freeTrialLength >= expirationDuration) {
      refund = keyPrice;
    } else {
      refund = keyPrice * timeRemaining / expirationDuration;
    }

    // Apply the penalty if this is not a free trial
    if(freeTrialLength == 0 || timeRemaining + freeTrialLength < expirationDuration)
    {
      uint penalty = keyPrice * refundPenaltyBasisPoints / BASIS_POINTS_DEN;
      if (refund > penalty) {
        refund -= penalty;
      } else {
        refund = 0;
      }
    }
  }

  uint256[1000] private __safe_upgrade_gap;
}

File 14 of 32 : MixinTransfer.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import './MixinRoles.sol';
import './MixinDisable.sol';
import './MixinKeys.sol';
import './MixinFunds.sol';
import './MixinLockCore.sol';
import './MixinPurchase.sol';
import '@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol';
import '@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol';

/**
 * @title Mixin for the transfer-related functions needed to meet the ERC721
 * standard.
 * @author Nick Furfaro
 * @dev `Mixins` are a design pattern seen in the 0x contracts.  It simply
 * separates logically groupings of code to ease readability.
 */

contract MixinTransfer is
  MixinRoles,
  MixinFunds,
  MixinLockCore,
  MixinKeys,
  MixinPurchase
{
  using AddressUpgradeable for address;

  event TransferFeeChanged(
    uint transferFeeBasisPoints
  );

  // 0x150b7a02 == bytes4(keccak256('onERC721Received(address,address,uint256,bytes)'))
  bytes4 private constant _ERC721_RECEIVED = 0x150b7a02;

  // The fee relative to keyPrice to charge when transfering a Key to another account
  // (potentially on a 0x marketplace).
  // This is calculated as `keyPrice * transferFeeBasisPoints / BASIS_POINTS_DEN`.
  uint public transferFeeBasisPoints;

  /**
  * @notice Allows the key owner to safely transfer a portion of the remaining time 
  * from their key to a new key
  * @param _tokenIdFrom the key to share
  * @param _to The recipient of the shared time
  * @param _timeShared The amount of time shared
  */
  function shareKey(
    address _to,
    uint _tokenIdFrom,
    uint _timeShared
  ) public
  {
    _lockIsUpToDate();
    require(maxNumberOfKeys > _totalSupply, 'LOCK_SOLD_OUT');
    _onlyKeyManagerOrApproved(_tokenIdFrom);
    _isValidKey(_tokenIdFrom);
    require(transferFeeBasisPoints < BASIS_POINTS_DEN, 'KEY_TRANSFERS_DISABLED');
    require(_to != address(0), 'INVALID_ADDRESS');
    address keyOwner = _ownerOf[_tokenIdFrom];
    require(keyOwner != _to, 'TRANSFER_TO_SELF');

    // store time to be added
    uint time;

    // get the remaining time for the origin key
    uint timeRemaining = keyExpirationTimestampFor(_tokenIdFrom) - block.timestamp;

    // get the transfer fee based on amount of time wanted share
    uint fee = getTransferFee(_tokenIdFrom, _timeShared);
    uint timePlusFee = _timeShared + fee;

    // ensure that we don't try to share too much
    if(timePlusFee < timeRemaining) {
      // now we can safely set the time
      time = _timeShared;
      // deduct time from parent key, including transfer fee
      _timeMachine(_tokenIdFrom, timePlusFee, false);
    } else {
      // we have to recalculate the fee here
      fee = getTransferFee(_tokenIdFrom, timeRemaining);
      time = timeRemaining - fee;
      _keys[_tokenIdFrom].expirationTimestamp = block.timestamp; // Effectively expiring the key
      emit ExpireKey(_tokenIdFrom);
    }

    // create new key
    uint tokenIdTo = _createNewKey(
      _to,
      address(0),
      block.timestamp + time
    );
    
    // trigger event
    emit Transfer(
      keyOwner,
      _to,
      tokenIdTo
    );

    require(_checkOnERC721Received(keyOwner, _to, tokenIdTo, ''), 'NON_COMPLIANT_ERC721_RECEIVER');
  }


  function transferFrom(
    address _from,
    address _recipient,
    uint _tokenId
  )
    public
  {
    _isValidKey(_tokenId);
    _onlyKeyManagerOrApproved(_tokenId);
    require(ownerOf(_tokenId) == _from, 'TRANSFER_FROM: NOT_KEY_OWNER');
    require(transferFeeBasisPoints < BASIS_POINTS_DEN, 'KEY_TRANSFERS_DISABLED');
    require(_recipient != address(0), 'INVALID_ADDRESS');
    require(_from != _recipient, 'TRANSFER_TO_SELF');

    // subtract the fee from the senders key before the transfer
    _timeMachine(_tokenId, getTransferFee(_tokenId, 0), false);  

    // transfer a token
    Key storage key = _keys[_tokenId];

    // update expiration
    key.expirationTimestamp = keyExpirationTimestampFor(_tokenId);

    // increase total number of unique owners
    if(balanceOf(_recipient) == 0 ) {
      numberOfOwners++;
    }

    // delete token from previous owner
    _deleteOwnershipRecord(_tokenId);
    
    // record new owner
    _createOwnershipRecord(_tokenId, _recipient);

    // clear any previous approvals
    _setKeyManagerOf(_tokenId, address(0));
    _clearApproval(_tokenId);

    // make future reccuring transactions impossible
    _originalDurations[_tokenId] = 0;

    // trigger event
    emit Transfer(
      _from,
      _recipient,
      _tokenId
    );
  }

  /**
   * @notice An ERC-20 style transfer.
   * @param _tokenId the Id of the token to send
   * @param _to the destination address
   * @param _valueBasisPoint a percentage (expressed as basis points) of the time to be transferred
   * @return success bool success/failure of the transfer
   */
  function transfer(
    uint _tokenId,
    address _to,
    uint _valueBasisPoint
  ) public
    returns (bool success)
  {
    _isValidKey(_tokenId);
    uint timeShared = ( keyExpirationTimestampFor(_tokenId) - block.timestamp ) * _valueBasisPoint / BASIS_POINTS_DEN;
    shareKey( _to, _tokenId, timeShared);
    // Errors will cause a revert
    return true;
  }

  /**
  * @notice Transfers the ownership of an NFT from one address to another address
  * @dev This works identically to the other function with an extra data parameter,
  *  except this function just sets data to ''
  * @param _from The current owner of the NFT
  * @param _to The new owner
  * @param _tokenId The NFT to transfer
  */
  function safeTransferFrom(
    address _from,
    address _to,
    uint _tokenId
  )
    public
  {
    safeTransferFrom(_from, _to, _tokenId, '');
  }

   /**
   * @dev Sets or unsets the approval of a given operator
   * An operator is allowed to transfer all tokens of the sender on their behalf
   * @param _to operator address to set the approval
   * @param _approved representing the status of the approval to be set
   * @notice disabled when transfers are disabled
   */
  function setApprovalForAll(
    address _to,
    bool _approved
  ) public
  {
    require(_to != msg.sender, 'APPROVE_SELF');
    require(transferFeeBasisPoints < BASIS_POINTS_DEN, 'KEY_TRANSFERS_DISABLED');
    managerToOperatorApproved[msg.sender][_to] = _approved;
    emit ApprovalForAll(msg.sender, _to, _approved);
  }

  /**
  * @notice Transfers the ownership of an NFT from one address to another address.
  * When transfer is complete, this functions
  *  checks if `_to` is a smart contract (code size > 0). If so, it calls
  *  `onERC721Received` on `_to` and throws if the return value is not
  *  `bytes4(keccak256('onERC721Received(address,address,uint,bytes)'))`.
  * @param _from The current owner of the NFT
  * @param _to The new owner
  * @param _tokenId The NFT to transfer
  * @param _data Additional data with no specified format, sent in call to `_to`
  */
  function safeTransferFrom(
    address _from,
    address _to,
    uint _tokenId,
    bytes memory _data
  )
    public
  {
    transferFrom(_from, _to, _tokenId);
    require(_checkOnERC721Received(_from, _to, _tokenId, _data), 'NON_COMPLIANT_ERC721_RECEIVER');

  }

  /**
   * Allow the Lock owner to change the transfer fee.
   */
  function updateTransferFee(
    uint _transferFeeBasisPoints
  ) external {
    _onlyLockManager();
    emit TransferFeeChanged(
      _transferFeeBasisPoints
    );
    transferFeeBasisPoints = _transferFeeBasisPoints;
  }

  /**
   * Determines how much of a fee would need to be paid in order to
   * transfer to another account.  This is pro-rated so the fee goes 
   * down overtime.
   * @dev Throws if _tokenId is not have a valid key
   * @param _tokenId The id of the key check the transfer fee for.
   * @param _time The amount of time to calculate the fee for.
   * @return The transfer fee in seconds.
   */
  function getTransferFee(
    uint _tokenId,
    uint _time
  )
    public view
    returns (uint)
  {
    _isKey(_tokenId);
    uint expirationTimestamp = keyExpirationTimestampFor(_tokenId);
    if(expirationTimestamp < block.timestamp) {
      return 0;
    } else {
      uint timeToTransfer;
      if(_time == 0) {
        timeToTransfer = expirationTimestamp - block.timestamp;
      } else {
        timeToTransfer = _time;
      }
      return timeToTransfer * transferFeeBasisPoints / BASIS_POINTS_DEN;
    }
  }

  /**
   * @dev Internal function to invoke `onERC721Received` on a target address
   * The call is not executed if the target address is not a contract
   * @param from address representing the previous owner of the given token ID
   * @param to target address that will receive the tokens
   * @param tokenId uint256 ID of the token to be transferred
   * @param _data bytes optional data to send along with the call
   * @return whether the call correctly returned the expected magic value
   */
  function _checkOnERC721Received(
    address from,
    address to,
    uint256 tokenId,
    bytes memory _data
  )
    internal
    returns (bool)
  {
    if (!to.isContract()) {
      return true;
    }
    bytes4 retval = IERC721ReceiverUpgradeable(to).onERC721Received(
      msg.sender, from, tokenId, _data);
    return (retval == _ERC721_RECEIVED);
  }

  uint256[1000] private __safe_upgrade_gap;
}

File 15 of 32 : MixinRoles.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// This contract mostly follows the pattern established by openzeppelin in
// openzeppelin/contracts-ethereum-package/contracts/access/roles

import '@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol';

contract MixinRoles is AccessControlUpgradeable {

  // roles
  bytes32 public constant LOCK_MANAGER_ROLE = keccak256("LOCK_MANAGER");
  bytes32 public constant KEY_GRANTER_ROLE = keccak256("KEY_GRANTER");

  // events
  event LockManagerAdded(address indexed account);
  event LockManagerRemoved(address indexed account);
  event KeyGranterAdded(address indexed account);
  event KeyGranterRemoved(address indexed account);

  // initializer
  function _initializeMixinRoles(address sender) internal {

    // for admin mamangers to add other lock admins
    _setRoleAdmin(LOCK_MANAGER_ROLE, LOCK_MANAGER_ROLE);

    // for lock managers to add/remove key granters
    _setRoleAdmin(KEY_GRANTER_ROLE, LOCK_MANAGER_ROLE);

    if (!isLockManager(sender)) {
      _setupRole(LOCK_MANAGER_ROLE, sender);  
    }
    if (!isKeyGranter(sender)) {
      _setupRole(KEY_GRANTER_ROLE, sender);
    }

  }

  // modifiers
  function _onlyLockManager() 
  internal 
  view
  {
    require( hasRole(LOCK_MANAGER_ROLE, msg.sender), 'ONLY_LOCK_MANAGER');
  }

  // lock manager functions
  function isLockManager(address account) public view returns (bool) {
    return hasRole(LOCK_MANAGER_ROLE, account);
  }

  function addLockManager(address account) public {
    _onlyLockManager();
    grantRole(LOCK_MANAGER_ROLE, account);
    emit LockManagerAdded(account);
  }

  function renounceLockManager() public {
    renounceRole(LOCK_MANAGER_ROLE, msg.sender);
    emit LockManagerRemoved(msg.sender);
  }


  // key granter functions
  function isKeyGranter(address account) public view returns (bool) {
    return hasRole(KEY_GRANTER_ROLE, account);
  }

  function addKeyGranter(address account) public {
    _onlyLockManager();
    grantRole(KEY_GRANTER_ROLE, account);
    emit KeyGranterAdded(account);
  }

  function revokeKeyGranter(address _granter) public {
    _onlyLockManager();
    revokeRole(KEY_GRANTER_ROLE, _granter);
    emit KeyGranterRemoved(_granter);
  }

  uint256[1000] private __safe_upgrade_gap;
}

File 16 of 32 : MixinConvenienceOwnable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import './MixinLockCore.sol';

/**
 * @title Mixin to add support for `ownable()`
 * @dev `Mixins` are a design pattern seen in the 0x contracts.  It simply
 * separates logically groupings of code to ease readability.
 */
contract MixinConvenienceOwnable is MixinLockCore {

  // used for `owner()`convenience helper
  address private _convenienceOwner;

  // events
  event OwnershipTransferred(address previousOwner, address newOwner);

  function _initializeMixinConvenienceOwnable(address _sender) internal {
    _convenienceOwner = _sender;
  }

  /** `owner()` is provided as an helper to mimick the `Ownable` contract ABI.
    * The `Ownable` logic is used by many 3rd party services to determine
    * contract ownership - e.g. who is allowed to edit metadata on Opensea.
    * 
    * @notice This logic is NOT used internally by the Unlock Protocol and is made 
    * available only as a convenience helper.
   */
  function owner() public view returns (address) {
    return _convenienceOwner;
  }

  /** Setter for the `owner` convenience helper (see `owner()` docstring for more).
    * @notice This logic is NOT used internally by the Unlock Protocol ans is made 
    * available only as a convenience helper.
    * @param account address returned by the `owner()` helper
   */ 
  function setOwner(address account) public {
    _onlyLockManager();
    require(account != address(0), 'OWNER_CANT_BE_ADDRESS_ZERO');
    address _previousOwner = _convenienceOwner;
    _convenienceOwner = account;
    emit OwnershipTransferred(_previousOwner, account);
  }

  function isOwner(address account) public view returns (bool) {
    return _convenienceOwner == account;
  }

  uint256[1000] private __safe_upgrade_gap;

}

File 17 of 32 : AddressUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Address.sol)

pragma solidity ^0.8.0;

/**
 * @dev Collection of functions related to the address type
 */
library AddressUpgradeable {
    /**
     * @dev Returns true if `account` is a contract.
     *
     * [IMPORTANT]
     * ====
     * It is unsafe to assume that an address for which this function returns
     * false is an externally-owned account (EOA) and not a contract.
     *
     * Among others, `isContract` will return false for the following
     * types of addresses:
     *
     *  - an externally-owned account
     *  - a contract in construction
     *  - an address where a contract will be created
     *  - an address where a contract lived, but was destroyed
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize, which returns 0 for contracts in
        // construction, since the code is only stored at the end of the
        // constructor execution.

        uint256 size;
        assembly {
            size := extcodesize(account)
        }
        return size > 0;
    }

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

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

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

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

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

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

        (bool success, bytes memory returndata) = target.call{value: value}(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

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

    /**
     * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
     * but performing a static call.
     *
     * _Available since v3.3._
     */
    function functionStaticCall(
        address target,
        bytes memory data,
        string memory errorMessage
    ) internal view returns (bytes memory) {
        require(isContract(target), "Address: static call to non-contract");

        (bool success, bytes memory returndata) = target.staticcall(data);
        return verifyCallResult(success, returndata, errorMessage);
    }

    /**
     * @dev Tool to verifies that a low level call was successful, and revert if it wasn't, either by bubbling the
     * revert reason using the provided one.
     *
     * _Available since v4.3._
     */
    function verifyCallResult(
        bool success,
        bytes memory returndata,
        string memory errorMessage
    ) internal pure returns (bytes memory) {
        if (success) {
            return returndata;
        } else {
            // Look for revert reason and bubble it up if present
            if (returndata.length > 0) {
                // The easiest way to bubble the revert reason is using memory via assembly

                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

File 18 of 32 : ERC165Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/ERC165.sol)

pragma solidity ^0.8.0;

import "./IERC165Upgradeable.sol";
import "../../proxy/utils/Initializable.sol";

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

    function __ERC165_init_unchained() internal onlyInitializing {
    }
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165Upgradeable).interfaceId;
    }
    uint256[50] private __gap;
}

File 19 of 32 : IERC165Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[EIP].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165Upgradeable {
    /**
     * @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);
}

File 20 of 32 : IUnlock.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.17 <0.9.0;


/**
 * @title The Unlock Interface
 * @author Nick Furfaro (unlock-protocol.com)
**/

interface IUnlock
{
  // Use initialize instead of a constructor to support proxies(for upgradeability via zos).
  function initialize(address _unlockOwner) external;

  /**
  * @dev deploy a ProxyAdmin contract used to upgrade locks
  */
  function initializeProxyAdmin() external;

  // store contract proxy admin address
  function proxyAdminAddress() external view;

  /**
  * @notice Create lock (legacy)
  * This deploys a lock for a creator. It also keeps track of the deployed lock.
  * @param _expirationDuration the duration of the lock (pass 0 for unlimited duration)
  * @param _tokenAddress set to the ERC20 token address, or 0 for ETH.
  * @param _keyPrice the price of each key
  * @param _maxNumberOfKeys the maximum nimbers of keys to be edited
  * @param _lockName the name of the lock
  * param _salt [deprec] -- kept only for backwards copatibility
  * This may be implemented as a sequence ID or with RNG. It's used with `create2`
  * to know the lock's address before the transaction is mined.
  * @dev internally call `createUpgradeableLock`
  */
  function createLock(
    uint _expirationDuration,
    address _tokenAddress,
    uint _keyPrice,
    uint _maxNumberOfKeys,
    string calldata _lockName,
    bytes12 // _salt
  ) external returns(address);

  /**
  * @notice Create lock (default)
  * This deploys a lock for a creator. It also keeps track of the deployed lock.
  * @param data bytes containing the call to initialize the lock template
  * @dev this call is passed as encoded function - for instance:
  *  bytes memory data = abi.encodeWithSignature(
  *    'initialize(address,uint256,address,uint256,uint256,string)',
  *    msg.sender,
  *    _expirationDuration,
  *    _tokenAddress,
  *    _keyPrice,
  *    _maxNumberOfKeys,
  *    _lockName
  *  );
  * @return address of the create lock
  */
  function createUpgradeableLock(
    bytes memory data
  ) external returns(address);

  /**
   * Create an upgradeable lock using a specific PublicLock version
   * @param data bytes containing the call to initialize the lock template
   * (refer to createUpgradeableLock for more details)
   * @param _lockVersion the version of the lock to use
  */
  function createUpgradeableLockAtVersion(
    bytes memory data,
    uint16 _lockVersion
  ) external returns (address);

  /**
  * @notice Upgrade a lock to a specific version
  * @dev only available for publicLockVersion > 10 (proxyAdmin /required)
  * @param lockAddress the existing lock address
  * @param version the version number you are targeting
  * Likely implemented with OpenZeppelin TransparentProxy contract
  */
  function upgradeLock(
    address payable lockAddress, 
    uint16 version
  ) external returns(address);

    /**
   * This function keeps track of the added GDP, as well as grants of discount tokens
   * to the referrer, if applicable.
   * The number of discount tokens granted is based on the value of the referal,
   * the current growth rate and the lock's discount token distribution rate
   * This function is invoked by a previously deployed lock only.
   */
  function recordKeyPurchase(
    uint _value,
    address _referrer // solhint-disable-line no-unused-vars
  )
    external;

    /**
   * @notice [DEPRECATED] Call to this function has been removed from PublicLock > v9.
   * @dev [DEPRECATED] Kept for backwards compatibility
   * This function will keep track of consumed discounts by a given user.
   * It will also grant discount tokens to the creator who is granting the discount based on the
   * amount of discount and compensation rate.
   * This function is invoked by a previously deployed lock only.
   */
  function recordConsumedDiscount(
    uint _discount,
    uint _tokens // solhint-disable-line no-unused-vars
  )
    external;

    /**
   * @notice [DEPRECATED] Call to this function has been removed from PublicLock > v9.
   * @dev [DEPRECATED] Kept for backwards compatibility
   * This function returns the discount available for a user, when purchasing a
   * a key from a lock.
   * This does not modify the state. It returns both the discount and the number of tokens
   * consumed to grant that discount.
   */
  function computeAvailableDiscountFor(
    address _purchaser, // solhint-disable-line no-unused-vars
    uint _keyPrice // solhint-disable-line no-unused-vars
  )
    external
    view
    returns(uint discount, uint tokens);

  // Function to read the globalTokenURI field.
  function globalBaseTokenURI()
    external
    view
    returns(string memory);

  /**
   * @dev Redundant with globalBaseTokenURI() for backwards compatibility with v3 & v4 locks.
   */
  function getGlobalBaseTokenURI()
    external
    view
    returns (string memory);

  // Function to read the globalTokenSymbol field.
  function globalTokenSymbol()
    external
    view
    returns(string memory);

  // Function to read the chainId field.
  function chainId()
    external
    view
    returns(uint);

  /**
   * @dev Redundant with globalTokenSymbol() for backwards compatibility with v3 & v4 locks.
   */
  function getGlobalTokenSymbol()
    external
    view
    returns (string memory);

  /**
   * @notice Allows the owner to update configuration variables
   */
  function configUnlock(
    address _udt,
    address _weth,
    uint _estimatedGasForPurchase,
    string calldata _symbol,
    string calldata _URI,
    uint _chainId
  )
    external;

  /**
   * @notice Add a PublicLock template to be used for future calls to `createLock`.
   * @dev This is used to upgrade conytract per version number
   */
  function addLockTemplate(address impl, uint16 version) external;

  // match lock templates addresses with version numbers
  function publicLockImpls(uint16 _version) external view;
  
  // match version numbers with lock templates addresses 
  function publicLockVersions(address _impl) external view;

  // the latest existing lock template version
  function publicLockLatestVersion() external view;

  /**
   * @notice Upgrade the PublicLock template used for future calls to `createLock`.
   * @dev This will initialize the template and revokeOwnership.
   */
  function setLockTemplate(
    address payable _publicLockAddress
  ) external;

  // Allows the owner to change the value tracking variables as needed.
  function resetTrackedValue(
    uint _grossNetworkProduct,
    uint _totalDiscountGranted
  ) external;

  function grossNetworkProduct() external view returns(uint);

  function totalDiscountGranted() external view returns(uint);

  function locks(address) external view returns(bool deployed, uint totalSales, uint yieldedDiscountTokens);

  // The address of the public lock template, used when `createLock` is called
  function publicLockAddress() external view returns(address);

  // Map token address to exchange contract address if the token is supported
  // Used for GDP calculations
  function uniswapOracles(address) external view returns(address);

  // The WETH token address, used for value calculations
  function weth() external view returns(address);

  // The UDT token address, used to mint tokens on referral
  function udt() external view returns(address);

  // The approx amount of gas required to purchase a key
  function estimatedGasForPurchase() external view returns(uint);

  /**
   * Helper to get the network mining basefee as introduced in EIP-1559
   * @dev this helper can be wrapped in try/catch statement to avoid 
   * revert in networks where EIP-1559 is not implemented
   */
  function networkBaseFee() external view returns (uint);

  // The version number of the current Unlock implementation on this network
  function unlockVersion() external pure returns(uint16);

  /**
   * @notice allows the owner to set the oracle address to use for value conversions
   * setting the _oracleAddress to address(0) removes support for the token
   * @dev This will also call update to ensure at least one datapoint has been recorded.
   */
  function setOracle(
    address _tokenAddress,
    address _oracleAddress
  ) external;

  // Initialize the Ownable contract, granting contract ownership to the specified sender
  function __initializeOwnable(address sender) external;

  /**
   * @dev Returns true if the caller is the current owner.
   */
  function isOwner() external view returns(bool);

  /**
   * @dev Returns the address of the current owner.
   */
  function owner() external view returns(address);

  /**
   * @dev Leaves the contract without owner. It will not be possible to call
   * `onlyOwner` functions anymore. Can only be called by the current owner.
   *
   * NOTE: Renouncing ownership will leave the contract without an owner,
   * thereby removing any functionality that is only available to the owner.
   */
  function renounceOwnership() external;

  /**
   * @dev Transfers ownership of the contract to a new account (`newOwner`).
   * Can only be called by the current owner.
   */
  function transferOwnership(address newOwner) external;
}

File 21 of 32 : ILockKeyCancelHook.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.17 <0.9.0;


/**
 * @notice Functions to be implemented by a keyCancelHook.
 * @dev Lock hooks are configured by calling `setEventHooks` on the lock.
 */
interface ILockKeyCancelHook
{
  /**
   * @notice If the lock owner has registered an implementer
   * then this hook is called with every key cancel.
   * @param operator the msg.sender issuing the cancel
   * @param to the account which had the key canceled
   * @param refund the amount sent to the `to` account (ETH or a ERC-20 token)
   */
  function onKeyCancel(
    address operator,
    address to,
    uint256 refund
  ) external;
}

File 22 of 32 : ILockKeyPurchaseHook.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.17 <0.9.0;


/**
 * @notice Functions to be implemented by a keyPurchaseHook.
 * @dev Lock hooks are configured by calling `setEventHooks` on the lock.
 */
interface ILockKeyPurchaseHook
{
  /**
   * @notice Used to determine the purchase price before issueing a transaction.
   * This allows the hook to offer a discount on purchases.
   * This may revert to prevent a purchase.
   * @param from the msg.sender making the purchase
   * @param recipient the account which will be granted a key
   * @param referrer the account which referred this key sale
   * @param data arbitrary data populated by the front-end which initiated the sale
   * @return minKeyPrice the minimum value/price required to purchase a key with these settings
   * @dev the lock's address is the `msg.sender` when this function is called via
   * the lock's `purchasePriceFor` function
   */
  function keyPurchasePrice(
    address from,
    address recipient,
    address referrer,
    bytes calldata data
  ) external view
    returns (uint minKeyPrice);

  /**
   * @notice If the lock owner has registered an implementer then this hook
   * is called with every key sold.
   * @param from the msg.sender making the purchase
   * @param recipient the account which will be granted a key
   * @param referrer the account which referred this key sale
   * @param data arbitrary data populated by the front-end which initiated the sale
   * @param minKeyPrice the price including any discount granted from calling this
   * hook's `keyPurchasePrice` function
   * @param pricePaid the value/pricePaid included with the purchase transaction
   * @dev the lock's address is the `msg.sender` when this function is called
   */
  function onKeyPurchase(
    address from,
    address recipient,
    address referrer,
    bytes calldata data,
    uint minKeyPrice,
    uint pricePaid
  ) external;
}

File 23 of 32 : ILockValidKeyHook.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.17 <0.9.0;


/**
 * @notice Functions to be implemented by a hasValidKey Hook.
 * @dev Lock hooks are configured by calling `setEventHooks` on the lock.
 */
interface ILockValidKeyHook
{

  /**
   * @notice If the lock owner has registered an implementer then this hook
   * is called every time balanceOf is called
   * @param lockAddress the address of the current lock
   * @param keyOwner the potential owner of the key for which we are retrieving the `balanceof`
   * @param expirationTimestamp the key expiration timestamp
   */
  function hasValidKey(
    address lockAddress,
    address keyOwner,
    uint256 expirationTimestamp,
    bool isValidKey
  ) 
  external view
  returns (bool);
}

File 24 of 32 : ILockTokenURIHook.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.17 <0.9.0;

/**
 * @notice Functions to be implemented by a tokenURIHook.
 * @dev Lock hooks are configured by calling `setEventHooks` on the lock.
 */
interface ILockTokenURIHook
{
  /**
   * @notice If the lock owner has registered an implementer
   * then this hook is called every time `tokenURI()` is called
   * @param lockAddress the address of the lock
   * @param operator the msg.sender issuing the call
   * @param owner the owner of the key for which we are retrieving the `tokenUri`
   * @param keyId the id (tokenId) of the key (if applicable)
   * @param expirationTimestamp the key expiration timestamp
   * @return the tokenURI
   */
  function tokenURI(
    address lockAddress,
    address operator,
    address owner,
    uint256 keyId,
    uint expirationTimestamp
  ) external view returns(string memory);
}

File 25 of 32 : AccessControlUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/AccessControl.sol)

pragma solidity ^0.8.0;

import "./IAccessControlUpgradeable.sol";
import "../utils/ContextUpgradeable.sol";
import "../utils/StringsUpgradeable.sol";
import "../utils/introspection/ERC165Upgradeable.sol";
import "../proxy/utils/Initializable.sol";

/**
 * @dev Contract module that allows children to implement role-based access
 * control mechanisms. This is a lightweight version that doesn't allow enumerating role
 * members except through off-chain means by accessing the contract event logs. Some
 * applications may benefit from on-chain enumerability, for those cases see
 * {AccessControlEnumerable}.
 *
 * Roles are referred to by their `bytes32` identifier. These should be exposed
 * in the external API and be unique. The best way to achieve this is by
 * using `public constant` hash digests:
 *
 * ```
 * bytes32 public constant MY_ROLE = keccak256("MY_ROLE");
 * ```
 *
 * Roles can be used to represent a set of permissions. To restrict access to a
 * function call, use {hasRole}:
 *
 * ```
 * function foo() public {
 *     require(hasRole(MY_ROLE, msg.sender));
 *     ...
 * }
 * ```
 *
 * Roles can be granted and revoked dynamically via the {grantRole} and
 * {revokeRole} functions. Each role has an associated admin role, and only
 * accounts that have a role's admin role can call {grantRole} and {revokeRole}.
 *
 * By default, the admin role for all roles is `DEFAULT_ADMIN_ROLE`, which means
 * that only accounts with this role will be able to grant or revoke other
 * roles. More complex role relationships can be created by using
 * {_setRoleAdmin}.
 *
 * WARNING: The `DEFAULT_ADMIN_ROLE` is also its own admin: it has permission to
 * grant and revoke this role. Extra precautions should be taken to secure
 * accounts that have been granted it.
 */
abstract contract AccessControlUpgradeable is Initializable, ContextUpgradeable, IAccessControlUpgradeable, ERC165Upgradeable {
    function __AccessControl_init() internal onlyInitializing {
        __Context_init_unchained();
        __ERC165_init_unchained();
        __AccessControl_init_unchained();
    }

    function __AccessControl_init_unchained() internal onlyInitializing {
    }
    struct RoleData {
        mapping(address => bool) members;
        bytes32 adminRole;
    }

    mapping(bytes32 => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with a standardized message including the required role.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     *
     * _Available since v4.1._
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role, _msgSender());
        _;
    }

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

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) public view override returns (bool) {
        return _roles[role].members[account];
    }

    /**
     * @dev Revert with a standard message if `account` is missing `role`.
     *
     * The format of the revert reason is given by the following regular expression:
     *
     *  /^AccessControl: account (0x[0-9a-f]{40}) is missing role (0x[0-9a-f]{64})$/
     */
    function _checkRole(bytes32 role, address account) internal view {
        if (!hasRole(role, account)) {
            revert(
                string(
                    abi.encodePacked(
                        "AccessControl: account ",
                        StringsUpgradeable.toHexString(uint160(account), 20),
                        " is missing role ",
                        StringsUpgradeable.toHexString(uint256(role), 32)
                    )
                )
            );
        }
    }

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) public view override returns (bytes32) {
        return _roles[role].adminRole;
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _grantRole(role, account);
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) public virtual override onlyRole(getRoleAdmin(role)) {
        _revokeRole(role, account);
    }

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been revoked `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     */
    function renounceRole(bytes32 role, address account) public virtual override {
        require(account == _msgSender(), "AccessControl: can only renounce roles for self");

        _revokeRole(role, account);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event. Note that unlike {grantRole}, this function doesn't perform any
     * checks on the calling account.
     *
     * [WARNING]
     * ====
     * This function should only be called from the constructor when setting
     * up the initial roles for the system.
     *
     * Using this function in any other way is effectively circumventing the admin
     * system imposed by {AccessControl}.
     * ====
     *
     * NOTE: This function is deprecated in favor of {_grantRole}.
     */
    function _setupRole(bytes32 role, address account) internal virtual {
        _grantRole(role, account);
    }

    /**
     * @dev Sets `adminRole` as ``role``'s admin role.
     *
     * Emits a {RoleAdminChanged} event.
     */
    function _setRoleAdmin(bytes32 role, bytes32 adminRole) internal virtual {
        bytes32 previousAdminRole = getRoleAdmin(role);
        _roles[role].adminRole = adminRole;
        emit RoleAdminChanged(role, previousAdminRole, adminRole);
    }

    /**
     * @dev Grants `role` to `account`.
     *
     * Internal function without access restriction.
     */
    function _grantRole(bytes32 role, address account) internal virtual {
        if (!hasRole(role, account)) {
            _roles[role].members[account] = true;
            emit RoleGranted(role, account, _msgSender());
        }
    }

    /**
     * @dev Revokes `role` from `account`.
     *
     * Internal function without access restriction.
     */
    function _revokeRole(bytes32 role, address account) internal virtual {
        if (hasRole(role, account)) {
            _roles[role].members[account] = false;
            emit RoleRevoked(role, account, _msgSender());
        }
    }
    uint256[49] private __gap;
}

File 26 of 32 : IAccessControlUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (access/IAccessControl.sol)

pragma solidity ^0.8.0;

/**
 * @dev External interface of AccessControl declared to support ERC165 detection.
 */
interface IAccessControlUpgradeable {
    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     *
     * _Available since v3.1._
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call, an admin role
     * bearer except when using {AccessControl-_setupRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Emitted when `account` is revoked `role`.
     *
     * `sender` is the account that originated the contract call:
     *   - if using `revokeRole`, it is the admin role bearer
     *   - if using `renounceRole`, it is the role bearer (i.e. `account`)
     */
    event RoleRevoked(bytes32 indexed role, address indexed account, address indexed sender);

    /**
     * @dev Returns `true` if `account` has been granted `role`.
     */
    function hasRole(bytes32 role, address account) external view returns (bool);

    /**
     * @dev Returns the admin role that controls `role`. See {grantRole} and
     * {revokeRole}.
     *
     * To change a role's admin, use {AccessControl-_setRoleAdmin}.
     */
    function getRoleAdmin(bytes32 role) external view returns (bytes32);

    /**
     * @dev Grants `role` to `account`.
     *
     * If `account` had not been already granted `role`, emits a {RoleGranted}
     * event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function grantRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from `account`.
     *
     * If `account` had been granted `role`, emits a {RoleRevoked} event.
     *
     * Requirements:
     *
     * - the caller must have ``role``'s admin role.
     */
    function revokeRole(bytes32 role, address account) external;

    /**
     * @dev Revokes `role` from the calling account.
     *
     * Roles are often managed via {grantRole} and {revokeRole}: this function's
     * purpose is to provide a mechanism for accounts to lose their privileges
     * if they are compromised (such as when a trusted device is misplaced).
     *
     * If the calling account had been granted `role`, emits a {RoleRevoked}
     * event.
     *
     * Requirements:
     *
     * - the caller must be `account`.
     */
    function renounceRole(bytes32 role, address account) external;
}

File 27 of 32 : ContextUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)

pragma solidity ^0.8.0;
import "../proxy/utils/Initializable.sol";

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

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

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }
    uint256[50] private __gap;
}

File 28 of 32 : StringsUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Strings.sol)

pragma solidity ^0.8.0;

/**
 * @dev String operations.
 */
library StringsUpgradeable {
    bytes16 private constant _HEX_SYMBOLS = "0123456789abcdef";

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        // Inspired by OraclizeAPI's implementation - MIT licence
        // https://github.com/oraclize/ethereum-api/blob/b42146b063c7d6ee1358846c198246239e9360e8/oraclizeAPI_0.4.25.sol

        if (value == 0) {
            return "0";
        }
        uint256 temp = value;
        uint256 digits;
        while (temp != 0) {
            digits++;
            temp /= 10;
        }
        bytes memory buffer = new bytes(digits);
        while (value != 0) {
            digits -= 1;
            buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
            value /= 10;
        }
        return string(buffer);
    }

    /**
     * @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
     */
    function toHexString(uint256 value) internal pure returns (string memory) {
        if (value == 0) {
            return "0x00";
        }
        uint256 temp = value;
        uint256 length = 0;
        while (temp != 0) {
            length++;
            temp >>= 8;
        }
        return toHexString(value, length);
    }

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

File 29 of 32 : IERC20Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20Upgradeable {
    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

    /**
     * @dev Moves `amount` tokens from the caller's account to `recipient`.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transfer(address recipient, uint256 amount) external returns (bool);

    /**
     * @dev Returns the remaining number of tokens that `spender` will be
     * allowed to spend on behalf of `owner` through {transferFrom}. This is
     * zero by default.
     *
     * This value changes when {approve} or {transferFrom} are called.
     */
    function allowance(address owner, address spender) external view returns (uint256);

    /**
     * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * IMPORTANT: Beware that changing an allowance with this method brings the risk
     * that someone may use both the old and the new allowance by unfortunate
     * transaction ordering. One possible solution to mitigate this race
     * condition is to first reduce the spender's allowance to 0 and set the
     * desired value afterwards:
     * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
     *
     * Emits an {Approval} event.
     */
    function approve(address spender, uint256 amount) external returns (bool);

    /**
     * @dev Moves `amount` tokens from `sender` to `recipient` using the
     * allowance mechanism. `amount` is then deducted from the caller's
     * allowance.
     *
     * Returns a boolean value indicating whether the operation succeeded.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(
        address sender,
        address recipient,
        uint256 amount
    ) external returns (bool);

    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

    /**
     * @dev Emitted when the allowance of a `spender` for an `owner` is set by
     * a call to {approve}. `value` is the new allowance.
     */
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

File 30 of 32 : SafeERC20Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20Upgradeable.sol";
import "../../../utils/AddressUpgradeable.sol";

/**
 * @title SafeERC20
 * @dev Wrappers around ERC20 operations that throw on failure (when the token
 * contract returns false). Tokens that return no value (and instead revert or
 * throw on failure) are also supported, non-reverting calls are assumed to be
 * successful.
 * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
 * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
 */
library SafeERC20Upgradeable {
    using AddressUpgradeable for address;

    function safeTransfer(
        IERC20Upgradeable token,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
    }

    function safeTransferFrom(
        IERC20Upgradeable token,
        address from,
        address to,
        uint256 value
    ) internal {
        _callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
    }

    /**
     * @dev Deprecated. This function has issues similar to the ones found in
     * {IERC20-approve}, and its usage is discouraged.
     *
     * Whenever possible, use {safeIncreaseAllowance} and
     * {safeDecreaseAllowance} instead.
     */
    function safeApprove(
        IERC20Upgradeable token,
        address spender,
        uint256 value
    ) internal {
        // safeApprove should only be called when setting an initial allowance,
        // or when resetting it to zero. To increase and decrease it, use
        // 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
        require(
            (value == 0) || (token.allowance(address(this), spender) == 0),
            "SafeERC20: approve from non-zero to non-zero allowance"
        );
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
    }

    function safeIncreaseAllowance(
        IERC20Upgradeable token,
        address spender,
        uint256 value
    ) internal {
        uint256 newAllowance = token.allowance(address(this), spender) + value;
        _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
    }

    function safeDecreaseAllowance(
        IERC20Upgradeable token,
        address spender,
        uint256 value
    ) internal {
        unchecked {
            uint256 oldAllowance = token.allowance(address(this), spender);
            require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
            uint256 newAllowance = oldAllowance - value;
            _callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
        }
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20Upgradeable token, bytes memory data) private {
        // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
        // we're implementing it ourselves. We use {Address.functionCall} to perform this call, which verifies that
        // the target address contains contract code and also asserts for success in the low-level call.

        bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
        if (returndata.length > 0) {
            // Return data is optional
            require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
        }
    }
}

File 31 of 32 : UnlockUtils.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.17 <=0.8.7;

// This contract provides some utility methods for use with the unlock protocol smart contracts.
// Borrowed from:
// https://github.com/oraclize/ethereum-api/blob/master/oraclizeAPI_0.5.sol#L943

library UnlockUtils {

  function strConcat(
    string memory _a,
    string memory _b,
    string memory _c,
    string memory _d
  ) internal pure
    returns (string memory _concatenatedString)
  {
    return string(abi.encodePacked(_a, _b, _c, _d));
  }

  function uint2Str(
    uint _i
  ) internal pure
    returns (string memory _uintAsString)
  {
    // make a copy of the param to avoid security/no-assign-params error
    uint c = _i;
    if (_i == 0) {
      return '0';
    }
    uint j = _i;
    uint len;
    while (j != 0) {
      len++;
      j /= 10;
    }
    bytes memory bstr = new bytes(len);
    uint k = len;
    while (c != 0) {
        k = k-1;
        uint8 temp = (48 + uint8(c - c / 10 * 10));
        bytes1 b1 = bytes1(temp);
        bstr[k] = b1;
        c /= 10;
    }
    return string(bstr);
  }

  function address2Str(
    address _addr
  ) internal pure
    returns(string memory)
  {
    bytes32 value = bytes32(uint256(uint160(_addr)));
    bytes memory alphabet = '0123456789abcdef';
    bytes memory str = new bytes(42);
    str[0] = '0';
    str[1] = 'x';
    for (uint i = 0; i < 20; i++) {
      str[2+i*2] = alphabet[uint8(value[i + 12] >> 4)];
      str[3+i*2] = alphabet[uint8(value[i + 12] & 0x0f)];
    }
    return string(str);
  }
}

File 32 of 32 : IERC721ReceiverUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC721/IERC721Receiver.sol)

pragma solidity ^0.8.0;

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

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

Contract Security Audit

Contract ABI

[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"sendTo","type":"address"},{"indexed":false,"internalType":"uint256","name":"refund","type":"uint256"}],"name":"CancelKey","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"_amount","type":"uint256"},{"indexed":false,"internalType":"bool","name":"_timeAdded","type":"bool"}],"name":"ExpirationChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ExpireKey","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"receiver","type":"address"},{"indexed":false,"internalType":"uint256","name":"refundedAmount","type":"uint256"},{"indexed":false,"internalType":"address","name":"tokenAddress","type":"address"}],"name":"GasRefunded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"newTimestamp","type":"uint256"}],"name":"KeyExtended","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"}],"name":"KeyGranterAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"}],"name":"KeyGranterRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_tokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"_newManager","type":"address"}],"name":"KeyManagerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"updatedRecordsCount","type":"uint256"}],"name":"KeysMigrated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"}],"name":"LockManagerAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"}],"name":"LockManagerRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"string","name":"symbol","type":"string"}],"name":"NewLockSymbol","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":false,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"oldKeyPrice","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"keyPrice","type":"uint256"},{"indexed":false,"internalType":"address","name":"oldTokenAddress","type":"address"},{"indexed":false,"internalType":"address","name":"tokenAddress","type":"address"}],"name":"PricingChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"freeTrialLength","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"refundPenaltyBasisPoints","type":"uint256"}],"name":"RefundPenaltyChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"uint256","name":"newExpiration","type":"uint256"}],"name":"RenewKeyPurchase","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"transferFeeBasisPoints","type":"uint256"}],"name":"TransferFeeChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"lockAddress","type":"address"},{"indexed":false,"internalType":"address","name":"unlockAddress","type":"address"}],"name":"UnlockCallFailed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"tokenAddress","type":"address"},{"indexed":true,"internalType":"address","name":"beneficiary","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Withdrawal","type":"event"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"KEY_GRANTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LOCK_MANAGER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"addKeyGranter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"addLockManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_approved","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"approveBeneficiary","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_keyOwner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"beneficiary","outputs":[{"internalType":"address payable","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"cancelAndRefund","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"expirationDuration","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"expireAndRefundFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_value","type":"uint256"},{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"address","name":"_referrer","type":"address"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"extend","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"freeTrialLength","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"gasRefundValue","outputs":[{"internalType":"uint256","name":"_refundValue","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"getCancelAndRefundValue","outputs":[{"internalType":"uint256","name":"refund","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_keyOwner","type":"address"}],"name":"getHasValidKey","outputs":[{"internalType":"bool","name":"isValid","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_time","type":"uint256"}],"name":"getTransferFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_recipients","type":"address[]"},{"internalType":"uint256[]","name":"_expirationTimestamps","type":"uint256[]"},{"internalType":"address[]","name":"_keyManagers","type":"address[]"}],"name":"grantKeys","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"_lockCreator","type":"address"},{"internalType":"uint256","name":"_expirationDuration","type":"uint256"},{"internalType":"address","name":"_tokenAddress","type":"address"},{"internalType":"uint256","name":"_keyPrice","type":"uint256"},{"internalType":"uint256","name":"_maxNumberOfKeys","type":"uint256"},{"internalType":"string","name":"_lockName","type":"string"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"isKeyGranter","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"isLockManager","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"isOwner","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"isValidKey","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"keyExpirationTimestampFor","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"keyManagerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"keyPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxKeysPerAddress","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxNumberOfKeys","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenIdFrom","type":"uint256"},{"internalType":"uint256","name":"_tokenIdTo","type":"uint256"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"mergeKeys","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_calldata","type":"bytes"}],"name":"migrate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"numberOfOwners","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"onKeyCancelHook","outputs":[{"internalType":"contract ILockKeyCancelHook","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"onKeyPurchaseHook","outputs":[{"internalType":"contract ILockKeyPurchaseHook","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"onTokenURIHook","outputs":[{"internalType":"contract ILockTokenURIHook","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"onValidKeyHook","outputs":[{"internalType":"contract ILockValidKeyHook","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"publicLockVersion","outputs":[{"internalType":"uint16","name":"","type":"uint16"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"_values","type":"uint256[]"},{"internalType":"address[]","name":"_recipients","type":"address[]"},{"internalType":"address[]","name":"_referrers","type":"address[]"},{"internalType":"address[]","name":"_keyManagers","type":"address[]"},{"internalType":"bytes[]","name":"_data","type":"bytes[]"}],"name":"purchase","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"address","name":"_referrer","type":"address"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"purchasePriceFor","outputs":[{"internalType":"uint256","name":"minKeyPrice","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"refundPenaltyBasisPoints","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"address","name":"_referrer","type":"address"}],"name":"renewMembershipFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceLockManager","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_granter","type":"address"}],"name":"revokeKeyGranter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"schemaVersion","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"bool","name":"_approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_baseTokenURI","type":"string"}],"name":"setBaseTokenURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_onKeyPurchaseHook","type":"address"},{"internalType":"address","name":"_onKeyCancelHook","type":"address"},{"internalType":"address","name":"_onValidKeyHook","type":"address"},{"internalType":"address","name":"_onTokenURIHook","type":"address"}],"name":"setEventHooks","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newExpirationDuration","type":"uint256"}],"name":"setExpirationDuration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_refundValue","type":"uint256"}],"name":"setGasRefundValue","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"address","name":"_keyManager","type":"address"}],"name":"setKeyManagerOf","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxKeys","type":"uint256"}],"name":"setMaxKeysPerAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxNumberOfKeys","type":"uint256"}],"name":"setMaxNumberOfKeys","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"setOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_tokenIdFrom","type":"uint256"},{"internalType":"uint256","name":"_timeShared","type":"uint256"}],"name":"shareKey","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokenAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_index","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_keyOwner","type":"address"},{"internalType":"uint256","name":"_index","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_valueBasisPoint","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"success","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"transferFeeBasisPoints","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unlockProtocol","outputs":[{"internalType":"contract IUnlock","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address payable","name":"_beneficiary","type":"address"}],"name":"updateBeneficiary","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_keyPrice","type":"uint256"},{"internalType":"address","name":"_tokenAddress","type":"address"}],"name":"updateKeyPricing","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_lockName","type":"string"}],"name":"updateLockName","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_lockSymbol","type":"string"}],"name":"updateLockSymbol","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_freeTrialLength","type":"uint256"},{"internalType":"uint256","name":"_refundPenaltyBasisPoints","type":"uint256"}],"name":"updateRefundPenalty","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"updateSchemaVersion","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_transferFeeBasisPoints","type":"uint256"}],"name":"updateTransferFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_tokenAddress","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

608060405234801561001057600080fd5b50615ffd80620000216000396000f3fe6080604052600436106104395760003560e01c80636d8ea5b41161022b578063a98d36231161012f578063d32bfb6c116100b1578063d32bfb6c14610dbb578063d52e4a1014610ddb578063d547741f14610df1578063d813cc1914610e11578063e985e9c514610e24578063f0ba604014610e44578063f12c6b6e14610e59578063f32e8b2414610e79578063f3fef3a314610e8e578063f5766b3914610eae578063f8548e3614610ece57600080fd5b8063a98d362314610c4c578063aae4b8f714610c7f578063b11d7ec114610c9f578063b1a3b25d14610cbf578063b585a6d514610cdf578063b88d4fde14610cff578063bf4a927014610d1f578063c87b56dd14610d3f578063cb0703c614610d5f578063d1bbd49c14610d7f578063d250348514610d9b57600080fd5b80638ca2fbad116101b85780638ca2fbad14610b125780638da5cb5b14610b3457806391d1485414610b5357806392ac98a514610b7357806393fd184414610b9357806395d89b4114610baa5780639d76ea5814610bbf578063a217fddf14610be0578063a22cb46514610bf5578063a2e4cd2e14610c15578063a375cb0514610c3557600080fd5b80636d8ea5b4146109da5780636eadde43146109fa57806370a0823114610a1a57806374b6c10614610a3a578063782a4ade14610a515780637ec2a72414610a7157806381a3c94314610a925780638505fe9514610ab25780638577a6d514610ad25780638932a90d14610af257600080fd5b80632f54bf6e1161033d5780634e2ce6d3116102bf5780634e2ce6d3146108855780634f6ccce71461089c57806352b0f638146108bc57806354b249fb146108dc578063550ef3a81461090d578063558b71e91461092d578063564aa99d1461094d57806356e0d51f1461096d5780636207a8da14610984578063626485a31461099a5780636352211e146109ba57600080fd5b80632f54bf6e1461070a5780632f745c591461073a57806330176e131461075a57806331159a171461077a578063338189971461079a57806336568abe146107ad57806338af3eed146107cd57806339f46986146107ee57806342842e0e1461080e57806342966c681461082e5780634d025fed1461084e57600080fd5b806313af4035116103c657806313af4035146105a857806318160ddd146105c8578063183767da146105de578063217751bc146105f5578063231005091461061657806323b872dd14610638578063248a9ca31461065857806326e9ca07146106885780632af9162a146106a95780632d33dd5b146106c95780632f2ff15d146106ea57600080fd5b806301ffc9a714610445578063068208cd1461047a57806306fdde031461049c578063081812fc146104be578063095ea7b3146104eb578063097ba3331461050b5780630aaffd2a146105395780630f15023b1461055957806310e569731461057a57806311a4c03a1461059157600080fd5b3661044057005b600080fd5b34801561045157600080fd5b50610465610460366004615701565b610eee565b60405190151581526020015b60405180910390f35b34801561048657600080fd5b5061049a6104953660046158bd565b610eff565b005b3480156104a857600080fd5b506104b1610fab565b6040516104719190615b1e565b3480156104ca57600080fd5b506104de6104d93660046156c3565b61103a565b6040516104719190615a20565b3480156104f757600080fd5b5061049a6105063660046154f4565b611062565b34801561051757600080fd5b5061052b6105263660046153b9565b6110fd565b604051908152602001610471565b34801561054557600080fd5b5061049a610554366004615282565b6111ad565b34801561056557600080fd5b50610c83546104de906001600160a01b031681565b34801561058657600080fd5b5061052b610c855481565b34801561059d57600080fd5b5061052b610c845481565b3480156105b457600080fd5b5061049a6105c3366004615282565b6111fe565b3480156105d457600080fd5b50610c875461052b565b3480156105ea57600080fd5b5061052b6124075481565b34801561060157600080fd5b50610c8a546104de906001600160a01b031681565b34801561062257600080fd5b5061052b600080516020615f6883398151915281565b34801561064457600080fd5b5061049a61065336600461541a565b6112bc565b34801561066457600080fd5b5061052b6106733660046156c3565b60009081526097602052604090206001015490565b34801561069457600080fd5b50610c8b546104de906001600160a01b031681565b3480156106b557600080fd5b5061049a6106c4366004615282565b611460565b3480156106d557600080fd5b50610c89546104de906001600160a01b031681565b3480156106f657600080fd5b5061049a6107053660046156dc565b6114b7565b34801561071657600080fd5b50610465610725366004615282565b612bda546001600160a01b0390811691161490565b34801561074657600080fd5b5061052b6107553660046154f4565b6114dd565b34801561076657600080fd5b5061049a61077536600461573b565b61155c565b34801561078657600080fd5b5061049a6107953660046156c3565b611571565b61049a6107a83660046155ee565b6115b9565b3480156107b957600080fd5b5061049a6107c83660046156dc565b611a73565b3480156107d957600080fd5b50610c88546104de906001600160a01b031681565b3480156107fa57600080fd5b5061049a610809366004615832565b611af1565b34801561081a57600080fd5b5061049a61082936600461541a565b611b3f565b34801561083a57600080fd5b5061049a6108493660046156c3565b611b5a565b34801561085a57600080fd5b506104de6108693660046156c3565b611078602052600090815260409020546001600160a01b031681565b34801561089157600080fd5b5061052b610c8d5481565b3480156108a857600080fd5b5061052b6108b73660046156c3565b611bab565b3480156108c857600080fd5b506104656108d7366004615282565b611bf2565b3480156108e857600080fd5b5061052b6108f73660046156c3565b600090815261107b602052604090206001015490565b34801561091957600080fd5b5061049a61092836600461573b565b611c0c565b34801561093957600080fd5b5061049a610948366004615832565b611c21565b34801561095957600080fd5b5061049a610968366004615282565b611c45565b34801561097957600080fd5b5061052b6127f05481565b34801561099057600080fd5b5061201e5461052b565b3480156109a657600080fd5b5061049a6109b53660046156c3565b611c9c565b3480156109c657600080fd5b506104de6109d53660046156c3565b611caa565b3480156109e657600080fd5b506104656109f5366004615282565b611cc6565b348015610a0657600080fd5b5061049a610a1536600461529f565b611dc3565b348015610a2657600080fd5b5061052b610a35366004615282565b611f27565b348015610a4657600080fd5b5061052b610c865481565b348015610a5d57600080fd5b5061049a610a6c36600461573b565b611f6c565b348015610a7d57600080fd5b50610c8c546104de906001600160a01b031681565b348015610a9e57600080fd5b5061049a610aad366004615555565b611fb3565b348015610abe57600080fd5b5061049a610acd3660046156dc565b612105565b348015610ade57600080fd5b5061049a610aed3660046156c3565b6123db565b348015610afe57600080fd5b5061049a610b0d36600461573b565b61241c565b348015610b1e57600080fd5b5061052b600080516020615fa883398151915281565b348015610b4057600080fd5b50612bda546001600160a01b03166104de565b348015610b5f57600080fd5b50610465610b6e3660046156dc565b61263f565b348015610b7f57600080fd5b5061052b610b8e3660046156c3565b61266a565b348015610b9f57600080fd5b5061052b6110775481565b348015610bb657600080fd5b506104b161275a565b348015610bcb57600080fd5b506104b1546104de906001600160a01b031681565b348015610bec57600080fd5b5061052b600081565b348015610c0157600080fd5b5061049a610c103660046154c6565b612893565b348015610c2157600080fd5b5061049a610c303660046156dc565b61294d565b348015610c4157600080fd5b5061052b6127f15481565b348015610c5857600080fd5b50610465610c673660046156c3565b600090815261107b6020526040902060010154421090565b348015610c8b57600080fd5b50610465610c9a366004615282565b6129db565b348015610cab57600080fd5b5061049a610cba3660046156dc565b6129f5565b348015610ccb57600080fd5b5061052b610cda366004615832565b612a6d565b348015610ceb57600080fd5b50610465610cfa3660046154f4565b612add565b348015610d0b57600080fd5b5061049a610d1a36600461545b565b612b6e565b348015610d2b57600080fd5b5061049a610d3a3660046156c3565b612ba1565b348015610d4b57600080fd5b506104b1610d5a3660046156c3565b612bf8565b348015610d6b57600080fd5b5061049a610d7a36600461535d565b612ea7565b348015610d8b57600080fd5b50604051600a8152602001610471565b348015610da757600080fd5b5061049a610db6366004615282565b61309e565b348015610dc757600080fd5b5061049a610dd63660046156c3565b6130f5565b348015610de757600080fd5b50610c8e5461052b565b348015610dfd57600080fd5b5061049a610e0c3660046156dc565b613127565b61049a610e1f366004615854565b61314d565b348015610e3057600080fd5b50610465610e3f366004615324565b6132a8565b348015610e5057600080fd5b5061049a6132d7565b348015610e6557600080fd5b5061049a610e74366004615520565b61331c565b348015610e8557600080fd5b5061049a613518565b348015610e9a57600080fd5b5061049a610ea93660046154f4565b613528565b348015610eba57600080fd5b5061049a610ec93660046156c3565b613687565b348015610eda57600080fd5b50610465610ee936600461580b565b613695565b6000610ef9826136f7565b92915050565b610f0883613702565b610f118361374f565b610f1a836137a0565b610f2382613702565b8042610f3f85600090815261107b602052604090206001015490565b610f499190615e42565b1015610f8e5760405162461bcd60e51b815260206004820152600f60248201526e4e4f545f454e4f5547485f54494d4560881b60448201526064015b60405180910390fd5b610f9a83826000613841565b610fa682826001613841565b505050565b6114638054610fb990615e9c565b80601f0160208091040260200160405190810160405280929190818152602001828054610fe590615e9c565b80156110325780601f1061100757610100808354040283529160200191611032565b820191906000526020600020905b81548152906001019060200180831161101557829003601f168201915b505050505081565b600061104582613702565b50600090815261107960205260409020546001600160a01b031690565b61106b816137a0565b336001600160a01b03831614156110945760405162461bcd60e51b8152600401610f8590615c39565b60008181526110796020908152604080832080546001600160a01b0319166001600160a01b03878116918217909255611076909352818420549151859492909116917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a45050565b610c89546000906001600160a01b0316156111a057610c895460405163221c1fd160e01b81526001600160a01b039091169063221c1fd190611149903390889088908890600401615aa3565b60206040518083038186803b15801561116157600080fd5b505afa158015611175573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061119991906157f2565b90506111a6565b50610c85545b9392505050565b6111b56138f7565b6001600160a01b0381166111db5760405162461bcd60e51b8152600401610f8590615b61565b610c8880546001600160a01b0319166001600160a01b0392909216919091179055565b611206613964565b6001600160a01b0381166112595760405162461bcd60e51b815260206004820152601a6024820152794f574e45525f43414e545f42455f414444524553535f5a45524f60301b6044820152606401610f85565b612bda80546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091015b60405180910390a15050565b6112c58161374f565b6112ce816137a0565b826001600160a01b03166112e182611caa565b6001600160a01b0316146113375760405162461bcd60e51b815260206004820152601c60248201527f5452414e534645525f46524f4d3a204e4f545f4b45595f4f574e4552000000006044820152606401610f85565b612710612407541061135b5760405162461bcd60e51b8152600401610f8590615b31565b6001600160a01b0382166113815760405162461bcd60e51b8152600401610f8590615b61565b816001600160a01b0316836001600160a01b031614156113b35760405162461bcd60e51b8152600401610f8590615be8565b6113c9816113c2836000612a6d565b6000613841565b600081815261107b602052604090206113e183611f27565b6113fc5761107780549060006113f683615ed1565b91905055505b611405826139bc565b61140f8284613ac3565b61141a826000613b82565b61142382613c0f565b600082815261202060205260408082208290555183916001600160a01b038087169290881691600080516020615f8883398151915291a450505050565b611468613964565b611480600080516020615f6883398151915282613127565b6040516001600160a01b038216907f766f6199fea59554b9ff688e413302b9200f85d74811c053c12d945ac6d8dd7a90600090a250565b6000828152609760205260409020600101546114d38133613c4c565b610fa68383613cb0565b60006114e883611f27565b82106115325760405162461bcd60e51b81526020600482015260196024820152784f574e45525f494e4445585f4f55545f4f465f424f554e445360381b6044820152606401610f85565b506001600160a01b0391909116600090815261107c60209081526040808320938352929052205490565b611564613964565b610fa6611465838361503a565b611579613964565b806115b35760405162461bcd60e51b815260206004820152600a6024820152694e554c4c5f56414c554560b01b6044820152606401610f85565b610c8e55565b6115c1613d36565b610c8754610c8654116115e65760405162461bcd60e51b8152600401610f8590615c12565b83518551146116325760405162461bcd60e51b81526020600482015260186024820152770929cac82989288bea48a8c8aa4a48aa4a6be988a9c8ea8960431b6044820152606401610f85565b82518551146116835760405162461bcd60e51b815260206004820152601b60248201527f494e56414c49445f4b45595f4d414e41474552535f4c454e47544800000000006044820152606401610f85565b60008060005b875181101561199e5760008882815181106116a6576116a6615f02565b6020026020010151905060006001600160a01b0316816001600160a01b031614156116e35760405162461bcd60e51b8152600401610f8590615b61565b600019610c8454141561171d576117168188848151811061170657611706615f02565b6020026020010151600019613d7e565b9250611752565b61174f8188848151811061173357611733615f02565b6020026020010151610c84544261174a9190615da1565b613d7e565b92505b60006117d0828a858151811061176a5761176a615f02565b602002602001015189898781811061178457611784615f02565b90506020028101906117969190615ce1565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506110fd92505050565b90506117dc8186615da1565b600085815261201f60209081526040808320859055610c84546120208352818420556104b18054612021909352922080546001600160a01b0319166001600160a01b0392831617905590549196501615611869578a838151811061184257611842615f02565b60200260200101518111156118695760405162461bcd60e51b8152600401610f8590615b8a565b61188c818a858151811061187f5761187f615f02565b6020026020010151613e2e565b6104b1546000906001600160a01b0316156118c0578b84815181106118b3576118b3615f02565b60200260200101516118c2565b345b610c89549091506001600160a01b03161561198857610c89548a516001600160a01b0390911690639849965790339086908e908990811061190557611905615f02565b60200260200101518c8c8a81811061191f5761191f615f02565b90506020028101906119319190615ce1565b88886040518863ffffffff1660e01b81526004016119559796959493929190615a58565b600060405180830381600087803b15801561196f57600080fd5b505af1158015611983573d6000803e3d6000fd5b505050505b505050808061199690615ed1565b915050611689565b506104b1546001600160a01b031615611a41576104b1546040516323b872dd60e01b81526001600160a01b039091169081906323b872dd906119e890339030908890600401615a34565b602060405180830381600087803b158015611a0257600080fd5b505af1158015611a16573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a3a91906156a6565b5050611a61565b34821115611a615760405162461bcd60e51b8152600401610f8590615bbc565b611a69613f33565b5050505050505050565b6001600160a01b0381163314611ae35760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b6064820152608401610f85565b611aed82826140bb565b5050565b611af9613964565b60408051838152602081018390527fd6867bc538320e67d7bdc35860c27c08486eb490b4fd9b820fff18fb28381d3c910160405180910390a16127f1919091556127f055565b610fa683838360405180602001604052806000815250612b6e565b611b6381613702565b611b6c816137a0565b600081815261107660205260408082205490518392916001600160a01b031690600080516020615f88833981519152908390a4611ba881614122565b50565b6000610c87548210611bee5760405162461bcd60e51b815260206004820152600c60248201526b4f55545f4f465f52414e474560a01b6044820152606401610f85565b5090565b6000610ef9600080516020615f688339815191528361263f565b611c14613964565b610fa6611463838361503a565b611c2a82613702565b611c338261374f565b611c3b613964565b611aed828261415c565b611c4d613964565b611c65600080516020615f68833981519152826114b7565b6040516001600160a01b038216907f684f8a71407db0c334454ebe9c9b288549317893a20b10dc779ec5c118dcd12190600090a250565b611ca4613964565b610c8455565b600090815261107660205260409020546001600160a01b031690565b600080611cd283611f27565b90508015611d155760005b81811015611d1357611cf2610c6785836114dd565b15611d01575060019392505050565b80611d0b81615ed1565b915050611cdd565b505b610c8b546001600160a01b031615611dbd57610c8b546040516370b6638f60e11b81523060048201526001600160a01b0385811660248301526000604483015284151560648301529091169063e16cc71e9060840160206040518083038186803b158015611d8257600080fd5b505afa158015611d96573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611dba91906156a6565b91505b50919050565b600054610100900460ff16611dde5760005460ff1615611de2565b303b155b611e455760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610f85565b600054610100900460ff16158015611e67576000805461ffff19166101011790555b611e7086614263565b610c838054336001600160a01b031991821617909155610c8880549091166001600160a01b038a16179055610c84879055610c85859055610c86849055600a610c8d556001610c8e55611ec3838361428f565b611ecb6142b5565b611ed76103e86127f055565b611ee0886142c5565b612bda80546001600160a01b0319166001600160a01b038a16179055611f0c6380ac58cd60e01b61434d565b8015611a69576000805461ff00191690555050505050505050565b60006001600160a01b038216611f4f5760405162461bcd60e51b8152600401610f8590615b61565b506001600160a01b0316600090815261107e602052604090205490565b611f74613964565b611f81611464838361503a565b507f8868e22e84ebf32da89b2ebcb0ac642816304ea3863b257f240df9098719cb9782826040516112b0929190615b0a565b611fbb613d36565b611fc433611bf2565b80611fd35750611fd3336129db565b61201f5760405162461bcd60e51b815260206004820181905260248201527f4f4e4c595f4c4f434b5f4d414e414745525f4f525f4b45595f4752414e5445526044820152606401610f85565b60005b858110156120fc57600087878381811061203e5761203e615f02565b90506020020160208101906120539190615282565b6001600160a01b0316141561207a5760405162461bcd60e51b8152600401610f8590615b61565b6120e987878381811061208f5761208f615f02565b90506020020160208101906120a49190615282565b8484848181106120b6576120b6615f02565b90506020020160208101906120cb9190615282565b8787858181106120dd576120dd615f02565b90506020020135613d7e565b50806120f481615ed1565b915050612022565b50505050505050565b61210d613d36565b61211682613702565b60008281526120206020526040902054600019141561216b5760405162461bcd60e51b81526020600482015260116024820152704e4f4e5f4558504952494e475f4c4f434b60781b6044820152606401610f85565b6104b1546001600160a01b03166121b55760405162461bcd60e51b815260206004820152600e60248201526d4e4f4e5f45524332305f4c4f434b60901b6044820152606401610f85565b60006121d96121c384611caa565b83604051806020016040528060008152506110fd565b600084815261201f6020526040902054909150811461222a5760405162461bcd60e51b815260206004820152600d60248201526c14149250d157d0d2105391d151609a1b6044820152606401610f85565b610c8454600084815261202060205260409020541461227e5760405162461bcd60e51b815260206004820152601060248201526f11155490551253d397d0d2105391d15160821b6044820152606401610f85565b6104b154600084815261202160205260409020546001600160a01b039081169116146122dc5760405162461bcd60e51b815260206004820152600d60248201526c1513d2d15397d0d2105391d151609a1b6044820152606401610f85565b600083815261107b602052604090206001015442101561232a5760405162461bcd60e51b81526020600482015260096024820152684e4f545f524541445960b81b6044820152606401610f85565b612333836143cc565b5061233e8183613e2e565b6104b1546001600160a01b0316806323b872dd61235a86611caa565b30856040518463ffffffff1660e01b815260040161237a93929190615a34565b602060405180830381600087803b15801561239457600080fd5b505af11580156123a8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123cc91906156a6565b506123d5613f33565b50505050565b6123e3613964565b6040518181527f0496ed1e61eb69727f9659a8e859288db4758ffb1f744d1c1424634f90a257f49060200160405180910390a161240755565b6124286001600a615e1f565b61ffff16610c8d54148061243d5750610c8d54155b6124865760405162461bcd60e51b815260206004820152601a60248201527914d0d211535057d5915494d253d397d393d517d0d3d4949150d560321b6044820152606401610f85565b610c8e54612495576001610c8e555b6000808060006124a5610c875490565b610c83549091506001600160a01b03163314156124c557606491506124d7565b6124d185870187615832565b90945091505b808211156124e3578091505b835b6124ef8386615da1565b8110156125f5576000612503826001615da1565b600081815261107660209081526040808320546001600160a01b031680845261107583529281902081518083019092528054808352600190910154928201929092529293509091901580159061255c5750602081015115155b156125df5760408051808201909152815181526020808301519082015261107b6000612589876001615da1565b81526020808201929092526040908101600090812084518155938301516001948501556001600160a01b03861681526110759092528120818155909101556125d18383613ac3565b866125db81615ed1565b9750505b50505080806125ed90615ed1565b9150506124e5565b5080821061260457600a610c8d555b6040518381527f4e075b7dcd463fe1b504dad3ccd1c4f87011a70c1e50f1805183df5d559834cd9060200160405180910390a1505050505050565b60009182526097602090815260408084206001600160a01b0393909316845291905290205460ff1690565b60006126758261374f565b600019610c8454141561268b575050610c855490565b600082815261107b60205260408120600101546126a9904290615e42565b9050610c84546127f154826126be9190615da1565b106126ce57610c855491506126ee565b610c845481610c85546126e19190615e00565b6126eb9190615dde565b91505b6127f154158061270d5750610c84546127f15461270b9083615da1565b105b15611dbd5760006127106127f054610c85546127299190615e00565b6127339190615dde565b90508083111561274e576127478184615e42565b9250612753565b600092505b5050919050565b6060611464805461276a90615e9c565b1515905061280257610c8360009054906101000a90046001600160a01b03166001600160a01b031663cec410526040518163ffffffff1660e01b815260040160006040518083038186803b1580156127c157600080fd5b505afa1580156127d5573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526127fd919081019061577c565b905090565b611464805461281090615e9c565b80601f016020809104026020016040519081016040528092919081815260200182805461283c90615e9c565b80156128895780601f1061285e57610100808354040283529160200191612889565b820191906000526020600020905b81548152906001019060200180831161286c57829003601f168201915b5050505050905090565b6001600160a01b0382163314156128bc5760405162461bcd60e51b8152600401610f8590615c39565b61271061240754106128e05760405162461bcd60e51b8152600401610f8590615b31565b33600081815261107a602090815260408083206001600160a01b03871680855290835292819020805460ff191686151590811790915590519081529192917f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a35050565b612955613964565b61295e816144cd565b610c8580546104b18054928590556001600160a01b031983166001600160a01b0385811691821790925560408051848152602081018890529290941693820184905260608201529091907f3615065ccf48367ac483ac86701248e2e5ff55bdd9be845007d34a3b68d719d49060800160405180910390a150505050565b6000610ef9600080516020615fa88339815191528361263f565b6129fe82613702565b612a08823361458f565b80612a175750612a17336129db565b612a635760405162461bcd60e51b815260206004820152601f60248201527f554e415554484f52495a45445f4b45595f4d414e414745525f555044415445006044820152606401610f85565b611aed8282613b82565b6000612a7883613702565b600083815261107b602052604090206001015442811015612a9d576000915050610ef9565b600083612ab557612aae4283615e42565b9050612ab8565b50825b6127106124075482612aca9190615e00565b612ad49190615dde565b92505050610ef9565b6000612ae76138f7565b6104b15460405163095ea7b360e01b81526001600160a01b038581166004830152602482018590529091169063095ea7b390604401602060405180830381600087803b158015612b3657600080fd5b505af1158015612b4a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111a691906156a6565b612b798484846112bc565b612b8584848484614607565b6123d55760405162461bcd60e51b8152600401610f8590615caa565b612ba9613964565b610c8754811015612bf25760405162461bcd60e51b8152602060048201526013602482015272534d414c4c45525f5448414e5f535550504c5960681b6044820152606401610f85565b610c8655565b606080806000612c07306146c6565b905060608515612c2157612c1a866148b1565b9250612c34565b6040518060200160405280600081525092505b610c8c546001600160a01b031615612d1a57600086815261107b6020526040902060010154610c8c546001600160a01b031663988b93ad3033612c768b611caa565b60405160e085901b6001600160e01b03191681526001600160a01b03938416600482015291831660248301529091166044820152606481018a90526084810184905260a40160006040518083038186803b158015612cd357600080fd5b505afa158015612ce7573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612d0f919081019061577c565b979650505050505050565b6114658054612d2890615e9c565b15159050612dde57610c8360009054906101000a90046001600160a01b03166001600160a01b031663a998e9fb6040518163ffffffff1660e01b815260040160006040518083038186803b158015612d7f57600080fd5b505afa158015612d93573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612dbb919081019061577c565b9350604051806040016040528060018152602001602f60f81b8152509050612e91565b6114658054612dec90615e9c565b80601f0160208091040260200160405190810160405280929190818152602001828054612e1890615e9c565b8015612e655780601f10612e3a57610100808354040283529160200191612e65565b820191906000526020600020905b815481529060010190602001808311612e4857829003601f168201915b505050505093506040518060200160405280600081525090506040518060200160405280600081525091505b612e9d848383866149dd565b9695505050505050565b612eaf613964565b6001600160a01b0384161580612ece57506001600160a01b0384163b15155b612f155760405162461bcd60e51b8152602060048201526018602482015277494e56414c49445f4f4e5f4b45595f534f4c445f484f4f4b60401b6044820152606401610f85565b6001600160a01b0383161580612f3457506001600160a01b0383163b15155b612f7d5760405162461bcd60e51b815260206004820152601a602482015279494e56414c49445f4f4e5f4b45595f43414e43454c5f484f4f4b60301b6044820152606401610f85565b6001600160a01b0382161580612f9c57506001600160a01b0382163b15155b612fe45760405162461bcd60e51b8152602060048201526019602482015278494e56414c49445f4f4e5f56414c49445f4b45595f484f4f4b60381b6044820152606401610f85565b6001600160a01b038116158061300357506001600160a01b0381163b15155b61304b5760405162461bcd60e51b8152602060048201526019602482015278494e56414c49445f4f4e5f544f4b454e5f5552495f484f4f4b60381b6044820152606401610f85565b610c8980546001600160a01b039586166001600160a01b031991821617909155610c8a805494861694821694909417909355610c8c8054918516918416919091179055610c8b8054919093169116179055565b6130a6613964565b6130be600080516020615fa8833981519152826114b7565b6040516001600160a01b038216907f91d5c045d5bd98bf59a379b259ebca05b93bf79af1845fdf87e3172385d4c7f790600090a250565b6130fe81613702565b6131078161374f565b613110816137a0565b600061311b8261266a565b9050611aed828261415c565b6000828152609760205260409020600101546131438133613c4c565b610fa683836140bb565b613155613d36565b61315e84613702565b613167846143cc565b5060006131b361317686611caa565b8585858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506110fd92505050565b6104b1549091506001600160a01b03161561327857858111156131e85760405162461bcd60e51b8152600401610f8590615b8a565b6104b1546040516323b872dd60e01b81526001600160a01b039091169081906323b872dd9061321f90339030908790600401615a34565b602060405180830381600087803b15801561323957600080fd5b505af115801561324d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061327191906156a6565b5050613298565b348111156132985760405162461bcd60e51b8152600401610f8590615bbc565b6132a0613f33565b505050505050565b6001600160a01b03918216600090815261107a6020908152604080832093909416825291909152205460ff1690565b6132ef600080516020615fa883398151915233611a73565b60405133907f42885193b8178d25fca25a38e6fcc93918501e91be06d85e0c8afb3bad95238090600090a2565b613324613d36565b610c8754610c8654116133495760405162461bcd60e51b8152600401610f8590615c12565b613352826137a0565b61335b8261374f565b612710612407541061337f5760405162461bcd60e51b8152600401610f8590615b31565b6001600160a01b0383166133a55760405162461bcd60e51b8152600401610f8590615b61565b600082815261107660205260409020546001600160a01b039081169084168114156133e25760405162461bcd60e51b8152600401610f8590615be8565b600083815261107b60205260408120600101548190613402904290615e42565b905060006134108686612a6d565b9050600061341e8287615da1565b90508281101561343c5785935061343787826000613841565b613494565b6134468784612a6d565b91506134528284615e42565b600088815261107b6020526040808220426001909101555191955088917f59f2fe866dd27a1c2d34115520888c3150365cbc931aab97fa88c4b9ab40b7959190a25b60006134a5898261174a8842615da1565b905080896001600160a01b0316876001600160a01b0316600080516020615f8883398151915260405160405180910390a46134f1868a8360405180602001604052806000815250614607565b61350d5760405162461bcd60e51b8152600401610f8590615caa565b505050505050505050565b613520613964565b600a610c8d55565b6135306138f7565b60006001600160a01b0383166135475750476135c6565b6040516370a0823160e01b81526001600160a01b038416906370a0823190613573903090600401615a20565b60206040518083038186803b15801561358b57600080fd5b505afa15801561359f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906135c391906157f2565b90505b60008215806135d457508183115b15613623576000821161361c5760405162461bcd60e51b815260206004820152601060248201526f4e4f545f454e4f5547485f46554e445360801b6044820152606401610f85565b5080613626565b50815b610c88546040518281526001600160a01b039182169186169033907f342e7ff505a8a0364cd0dc2ff195c315e43bce86b204846ecd36913e117b109e9060200160405180910390a4610c88546123d59085906001600160a01b031683614a0f565b61368f613964565b61201e55565b60006136a08461374f565b600061271083426136c188600090815261107b602052604090206001015490565b6136cb9190615e42565b6136d59190615e00565b6136df9190615dde565b90506136ec84868361331c565b506001949350505050565b6000610ef982614a4b565b600081815261107b6020526040902060010154611ba85760405162461bcd60e51b815260206004820152600b60248201526a4e4f5f535543485f4b455960a81b6044820152606401610f85565b600081815261107b60205260409020600101544210611ba85760405162461bcd60e51b815260206004820152600d60248201526c12d15657d393d517d590531251609a1b6044820152606401610f85565b6137aa813361458f565b806137cc5750600081815261107960205260409020546001600160a01b031633145b806137f55750600081815261107660205260409020546137f5906001600160a01b0316336132a8565b611ba85760405162461bcd60e51b815260206004820152601c60248201527f4f4e4c595f4b45595f4d414e414745525f4f525f415050524f564544000000006044820152606401610f85565b61384a83613702565b600083815261107b60205260409020600101548115613897574281111561388d576138758382615da1565b600085815261107b60205260409020600101556138b5565b6138758342615da1565b6138a18382615e42565b600085815261107b60205260409020600101555b60408051848152831515602082015285917fe9408df99703ae33a9d01185bcad328ea8683fb1f920da9c30959c192f21b5b3910160405180910390a250505050565b613900336129db565b806139165750610c88546001600160a01b031633145b6139625760405162461bcd60e51b815260206004820181905260248201527f4f4e4c595f4c4f434b5f4d414e414745525f4f525f42454e45464943494152596044820152606401610f85565b565b61397c600080516020615fa88339815191523361263f565b6139625760405162461bcd60e51b815260206004820152601160248201527027a7262cafa627a1a5afa6a0a720a3a2a960791b6044820152606401610f85565b600081815261107660205260408120546001600160a01b03169060016139e183611f27565b6139eb9190615e42565b600084815261107d6020526040902054909150808214613a41576001600160a01b038316600090815261107c60209081526040808320858452825280832054848452818420819055835261107d90915290208190555b6001600160a01b038316600090815261107c60209081526040808320858452909152812055613a6f83611f27565b60011415613a8e576110778054906000613a8883615e85565b91905055505b6001600160a01b038316600090815261107e60205260408120805460019290613ab8908490615e42565b909155505050505050565b6000613ace82611f27565b9050610c8e548110613b0d5760405162461bcd60e51b81526020600482015260086024820152674d41585f4b45595360c01b6044820152606401610f85565b600083815261107d602090815260408083208490556001600160a01b03851680845261107c83528184208585528352818420879055868452611076835281842080546001600160a01b03191682179055835261107e9091528120805460019290613b78908490615da1565b9091555050505050565b600082815261107860205260409020546001600160a01b03828116911614611aed5760008281526110786020526040902080546001600160a01b0319166001600160a01b038316179055613bd582613c0f565b6040516001600160a01b0382169083907f9d2895c45a420624de863a2f437b022d879f457bf7a829044055a10c5a6fd5e390600090a35050565b600081815261107960205260409020546001600160a01b031615611ba85760009081526110796020526040902080546001600160a01b0319169055565b613c56828261263f565b611aed57613c6e816001600160a01b03166014614a56565b613c79836020614a56565b604051602001613c8a9291906159b1565b60408051601f198184030181529082905262461bcd60e51b8252610f8591600401615b1e565b613cba828261263f565b611aed5760008281526097602090815260408083206001600160a01b03851684529091529020805460ff19166001179055613cf23390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b610c8d54600a146139625760405162461bcd60e51b8152602060048201526012602482015271135251d490551253d397d49154555254915160721b6044820152606401610f85565b610c87805460009182613d9083615ed1565b9091555050610c87546040805180820182528281526020808201868152600085815261107b9092529290209051815590516001909101559050613dd284611f27565b613ded576110778054906000613de783615ed1565b91905055505b613df78185613ac3565b613e018184613b82565b60405181906001600160a01b03861690600090600080516020615f88833981519152908290a49392505050565b610c83546001600160a01b03163b15613ef557610c835460405163939d9f1f60e01b8152600481018490526001600160a01b0383811660248301529091169063939d9f1f90620493e090604401600060405180830381600088803b158015613e9557600080fd5b5087f193505050508015613ea7575060015b611aed57610c835460405130917f6b18946261693dfd6c760d986b28ad2238b5b0267f8e5b6bc40a2f998e2f20ac91613ee9916001600160a01b031690615a20565b60405180910390a25050565b610c835460405130917f6b18946261693dfd6c760d986b28ad2238b5b0267f8e5b6bc40a2f998e2f20ac91613ee9916001600160a01b031690615a20565b61201e5415613962576104b1546001600160a01b031615613fe1576104b15461201e546040516323b872dd60e01b81526001600160a01b039092169182916323b872dd91613f88913091339190600401615a34565b602060405180830381600087803b158015613fa257600080fd5b505af1158015613fb6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613fda91906156a6565b505061406d565b61201e5460405160009133918381818185875af1925050503d8060008114614025576040519150601f19603f3d011682016040523d82523d6000602084013e61402a565b606091505b505090508061406b5760405162461bcd60e51b815260206004820152600d60248201526c14915195539117d19052531151609a1b6044820152606401610f85565b505b61201e546104b154604080519283526001600160a01b03909116602083015233917f522a883b471164223f18b50f326da8671372b64b4792eac0e63d447e714c3e3b910160405180910390a2565b6140c5828261263f565b15611aed5760008281526097602090815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b61412b816139bc565b600090815261107b6020908152604080832042600190910155611076909152902080546001600160a01b0319169055565b600061416783611caa565b905061417283614122565b336001600160a01b0316816001600160a01b0316847f0a7068a9989857441c039a14a42b67ed71dd1fcfe5a9b17cc87b252e47bce528856040516141b891815260200190565b60405180910390a481156141de576104b1546141de906001600160a01b03168284614a0f565b60008381526120206020526040812055610c8a546001600160a01b031615610fa657610c8a5460405163b499b6c560e01b81526001600160a01b039091169063b499b6c59061423590339085908790600401615a34565b600060405180830381600087803b15801561424f57600080fd5b505af11580156120fc573d6000803e3d6000fd5b61426c816144cd565b6104b180546001600160a01b0319166001600160a01b0392909216919091179055565b614297614bf1565b6142a4611463838361503a565b50611aed635b5e139f60e01b61434d565b61396263780e9d6360e01b61434d565b6142dd600080516020615fa883398151915280614c28565b614303600080516020615f68833981519152600080516020615fa8833981519152614c28565b61430c816129db565b61432857614328600080516020615fa883398151915282614c73565b61433181611bf2565b611ba857611ba8600080516020615f6883398151915282614c73565b6001600160e01b031980821614156143a75760405162461bcd60e51b815260206004820152601c60248201527f4552433136353a20696e76616c696420696e74657266616365206964000000006044820152606401610f85565b6001600160e01b0319166000908152606560205260409020805460ff19166001179055565b600081815261107b60205260408120600101546000198114156144315760405162461bcd60e51b815260206004820152601c60248201527f43414e545f455854454e445f4e4f4e5f4558504952494e475f4b4559000000006044820152606401610f85565b600019610c84541415614448576000199150614476565b4281111561446557610c845461445e9082615da1565b9150614476565b610c84546144739042615da1565b91505b600083815261107b6020526040908190206001018390555183907f3ca112768ff7861e008ace1c11570c52e404c043e585545b5957a1e20961dde3906144bf9085815260200190565b60405180910390a250919050565b6001600160a01b038116158061455357506000816001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b15801561451957600080fd5b505afa15801561452d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061455191906157f2565b115b611ba85760405162461bcd60e51b815260206004820152600d60248201526c24a72b20a624a22faa27a5a2a760991b6044820152606401610f85565b600082815261107860205260408120546001600160a01b03838116911614806145f25750600083815261107860205260409020546001600160a01b03161580156145f25750816001600160a01b03166145e784611caa565b6001600160a01b0316145b156145ff57506001610ef9565b506000610ef9565b60006001600160a01b0384163b614620575060016146be565b604051630a85bd0160e11b81526000906001600160a01b0386169063150b7a02906146559033908a9089908990600401615ad7565b602060405180830381600087803b15801561466f57600080fd5b505af1158015614683573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906146a7919061571e565b6001600160e01b031916630a85bd0160e11b149150505b949350505050565b604080518082018252601081526f181899199a1a9b1b9c1cb0b131b232b360811b60208201528151602a80825260608281019094526001600160a01b0385169291600091602082018180368337019050509050600360fc1b8160008151811061473157614731615f02565b60200101906001600160f81b031916908160001a905350600f60fb1b8160018151811061476057614760615f02565b60200101906001600160f81b031916908160001a90535060005b60148110156148a8578260048561479284600c615da1565b602081106147a2576147a2615f02565b1a60f81b6001600160f81b031916901c60f81c60ff16815181106147c8576147c8615f02565b01602001516001600160f81b031916826147e3836002615e00565b6147ee906002615da1565b815181106147fe576147fe615f02565b60200101906001600160f81b031916908160001a905350828461482283600c615da1565b6020811061483257614832615f02565b825191901a600f1690811061484957614849615f02565b01602001516001600160f81b03191682614864836002615e00565b61486f906003615da1565b8151811061487f5761487f615f02565b60200101906001600160f81b031916908160001a905350806148a081615ed1565b91505061477a565b50949350505050565b606081806148d85750506040805180820190915260018152600360fc1b6020820152919050565b8260005b811561490257806148ec81615ed1565b91506148fb9050600a83615dde565b91506148dc565b6000816001600160401b0381111561491c5761491c615f18565b6040519080825280601f01601f191660200182016040528015614946576020820181803683370190505b509050815b84156149d35761495c600182615e42565b9050600061496b600a87615dde565b61497690600a615e00565b6149809087615e42565b61498b906030615db9565b905060008160f81b9050808484815181106149a8576149a8615f02565b60200101906001600160f81b031916908160001a9053506149ca600a88615dde565b9650505061494b565b5095945050505050565b6060848484846040516020016149f6949392919061595a565b6040516020818303038152906040529050949350505050565b8015610fa6576001600160a01b038316614a3657610fa66001600160a01b03831682614c7d565b826123d56001600160a01b0382168484614d93565b6000610ef982614de5565b60606000614a65836002615e00565b614a70906002615da1565b6001600160401b03811115614a8757614a87615f18565b6040519080825280601f01601f191660200182016040528015614ab1576020820181803683370190505b509050600360fc1b81600081518110614acc57614acc615f02565b60200101906001600160f81b031916908160001a905350600f60fb1b81600181518110614afb57614afb615f02565b60200101906001600160f81b031916908160001a9053506000614b1f846002615e00565b614b2a906001615da1565b90505b6001811115614ba2576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110614b5e57614b5e615f02565b1a60f81b828281518110614b7457614b74615f02565b60200101906001600160f81b031916908160001a90535060049490941c93614b9b81615e85565b9050614b2d565b5083156111a65760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610f85565b600054610100900460ff16614c185760405162461bcd60e51b8152600401610f8590615c5f565b614c20614e0a565b613962614e0a565b600082815260976020526040808220600101805490849055905190918391839186917fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff9190a4505050565b611aed8282613cb0565b80471015614ccd5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e63650000006044820152606401610f85565b6000826001600160a01b03168260405160006040518083038185875af1925050503d8060008114614d1a576040519150601f19603f3d011682016040523d82523d6000602084013e614d1f565b606091505b5050905080610fa65760405162461bcd60e51b815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c20726044820152791958da5c1a595b9d081b585e481a185d99481c995d995c9d195960321b6064820152608401610f85565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b179052610fa6908490614e31565b60006001600160e01b03198216637965db0b60e01b1480610ef95750610ef982614f03565b600054610100900460ff166139625760405162461bcd60e51b8152600401610f8590615c5f565b6000614e86826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316614f3f9092919063ffffffff16565b805190915015610fa65780806020019051810190614ea491906156a6565b610fa65760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610f85565b60006301ffc9a760e01b6001600160e01b031983161480610ef95750506001600160e01b03191660009081526065602052604090205460ff1690565b60606146be848460008585843b614f985760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610f85565b600080866001600160a01b03168587604051614fb4919061593e565b60006040518083038185875af1925050503d8060008114614ff1576040519150601f19603f3d011682016040523d82523d6000602084013e614ff6565b606091505b5091509150612d0f828286606083156150105750816111a6565b8251156150205782518084602001fd5b8160405162461bcd60e51b8152600401610f859190615b1e565b82805461504690615e9c565b90600052602060002090601f01602090048101928261506857600085556150ae565b82601f106150815782800160ff198235161785556150ae565b828001600101855582156150ae579182015b828111156150ae578235825591602001919060010190615093565b50611bee9291505b80821115611bee57600081556001016150b6565b60008083601f8401126150dc57600080fd5b5081356001600160401b038111156150f357600080fd5b6020830191508360208260051b850101111561510e57600080fd5b9250929050565b600082601f83011261512657600080fd5b8135602061513b61513683615d57565b615d27565b80838252828201915082860187848660051b890101111561515b57600080fd5b60005b8581101561518357813561517181615f2e565b8452928401929084019060010161515e565b5090979650505050505050565b600082601f8301126151a157600080fd5b813560206151b161513683615d57565b80838252828201915082860187848660051b89010111156151d157600080fd5b60005b85811015615183578135845292840192908401906001016151d4565b60008083601f84011261520257600080fd5b5081356001600160401b0381111561521957600080fd5b60208301915083602082850101111561510e57600080fd5b600082601f83011261524257600080fd5b813561525061513682615d7a565b81815284602083860101111561526557600080fd5b816020850160208301376000918101602001919091529392505050565b60006020828403121561529457600080fd5b81356111a681615f2e565b600080600080600080600060c0888a0312156152ba57600080fd5b87356152c581615f2e565b96506020880135955060408801356152dc81615f2e565b9450606088013593506080880135925060a08801356001600160401b0381111561530557600080fd5b6153118a828b016151f0565b989b979a50959850939692959293505050565b6000806040838503121561533757600080fd5b823561534281615f2e565b9150602083013561535281615f2e565b809150509250929050565b6000806000806080858703121561537357600080fd5b843561537e81615f2e565b9350602085013561538e81615f2e565b9250604085013561539e81615f2e565b915060608501356153ae81615f2e565b939692955090935050565b6000806000606084860312156153ce57600080fd5b83356153d981615f2e565b925060208401356153e981615f2e565b915060408401356001600160401b0381111561540457600080fd5b61541086828701615231565b9150509250925092565b60008060006060848603121561542f57600080fd5b833561543a81615f2e565b9250602084013561544a81615f2e565b929592945050506040919091013590565b6000806000806080858703121561547157600080fd5b843561547c81615f2e565b9350602085013561548c81615f2e565b92506040850135915060608501356001600160401b038111156154ae57600080fd5b6154ba87828801615231565b91505092959194509250565b600080604083850312156154d957600080fd5b82356154e481615f2e565b9150602083013561535281615f43565b6000806040838503121561550757600080fd5b823561551281615f2e565b946020939093013593505050565b60008060006060848603121561553557600080fd5b833561554081615f2e565b95602085013595506040909401359392505050565b6000806000806000806060878903121561556e57600080fd5b86356001600160401b038082111561558557600080fd5b6155918a838b016150ca565b909850965060208901359150808211156155aa57600080fd5b6155b68a838b016150ca565b909650945060408901359150808211156155cf57600080fd5b506155dc89828a016150ca565b979a9699509497509295939492505050565b60008060008060008060a0878903121561560757600080fd5b86356001600160401b038082111561561e57600080fd5b61562a8a838b01615190565b9750602089013591508082111561564057600080fd5b61564c8a838b01615115565b9650604089013591508082111561566257600080fd5b61566e8a838b01615115565b9550606089013591508082111561568457600080fd5b6156908a838b01615115565b945060808901359150808211156155cf57600080fd5b6000602082840312156156b857600080fd5b81516111a681615f43565b6000602082840312156156d557600080fd5b5035919050565b600080604083850312156156ef57600080fd5b82359150602083013561535281615f2e565b60006020828403121561571357600080fd5b81356111a681615f51565b60006020828403121561573057600080fd5b81516111a681615f51565b6000806020838503121561574e57600080fd5b82356001600160401b0381111561576457600080fd5b615770858286016151f0565b90969095509350505050565b60006020828403121561578e57600080fd5b81516001600160401b038111156157a457600080fd5b8201601f810184136157b557600080fd5b80516157c361513682615d7a565b8181528560208385010111156157d857600080fd5b6157e9826020830160208601615e59565b95945050505050565b60006020828403121561580457600080fd5b5051919050565b60008060006060848603121561582057600080fd5b83359250602084013561544a81615f2e565b6000806040838503121561584557600080fd5b50508035926020909101359150565b60008060008060006080868803121561586c57600080fd5b8535945060208601359350604086013561588581615f2e565b925060608601356001600160401b038111156158a057600080fd5b6158ac888289016151f0565b969995985093965092949392505050565b6000806000606084860312156158d257600080fd5b505081359360208301359350604090920135919050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6000815180845261592a816020860160208601615e59565b601f01601f19169290920160200192915050565b60008251615950818460208701615e59565b9190910192915050565b6000855161596c818460208a01615e59565b855190830190615980818360208a01615e59565b8551910190615993818360208901615e59565b84519101906159a6818360208801615e59565b019695505050505050565b76020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b8152600083516159e3816017850160208801615e59565b7001034b99036b4b9b9b4b733903937b6329607d1b6017918401918201528351615a14816028840160208801615e59565b01602801949350505050565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b03888116825287811660208301528616604082015260c060608201819052600090615a8d90830186886158e9565b60808301949094525060a0015295945050505050565b6001600160a01b038581168252848116602083015283166040820152608060608201819052600090612e9d90830184615912565b6001600160a01b0385811682528416602082015260408101839052608060608201819052600090612e9d90830184615912565b6020815260006146be6020830184866158e9565b6020815260006111a66020830184615912565b60208082526016908201527512d15657d514905394d1915494d7d11254d05093115160521b604082015260600190565b6020808252600f908201526e494e56414c49445f4144445245535360881b604082015260600190565b602080825260189082015277494e53554646494349454e545f45524332305f56414c554560401b604082015260600190565b602080825260129082015271494e53554646494349454e545f56414c554560701b604082015260600190565b60208082526010908201526f2a2920a729a322a92faa27afa9a2a62360811b604082015260600190565b6020808252600d908201526c1313d0d2d7d4d3d31117d3d555609a1b604082015260600190565b6020808252600c908201526b20a8282927ab22afa9a2a62360a11b604082015260600190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6020808252601d908201527f4e4f4e5f434f4d504c49414e545f4552433732315f5245434549564552000000604082015260600190565b6000808335601e19843603018112615cf857600080fd5b8301803591506001600160401b03821115615d1257600080fd5b60200191503681900382131561510e57600080fd5b604051601f8201601f191681016001600160401b0381118282101715615d4f57615d4f615f18565b604052919050565b60006001600160401b03821115615d7057615d70615f18565b5060051b60200190565b60006001600160401b03821115615d9357615d93615f18565b50601f01601f191660200190565b60008219821115615db457615db4615eec565b500190565b600060ff821660ff84168060ff03821115615dd657615dd6615eec565b019392505050565b600082615dfb57634e487b7160e01b600052601260045260246000fd5b500490565b6000816000190483118215151615615e1a57615e1a615eec565b500290565b600061ffff83811690831681811015615e3a57615e3a615eec565b039392505050565b600082821015615e5457615e54615eec565b500390565b60005b83811015615e74578181015183820152602001615e5c565b838111156123d55750506000910152565b600081615e9457615e94615eec565b506000190190565b600181811c90821680615eb057607f821691505b60208210811415611dbd57634e487b7160e01b600052602260045260246000fd5b6000600019821415615ee557615ee5615eec565b5060010190565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052604160045260246000fd5b6001600160a01b0381168114611ba857600080fd5b8015158114611ba857600080fd5b6001600160e01b031981168114611ba857600080fdfeb309c40027c81d382c3b58d8de24207a34b27e1db369b1434e4a11311f154b5eddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efb89cdd26cddd51301940bf2715f765b626b8a5a9e2681ac62dc83cc2db2530c0a2646970667358221220fa5eda210be05319db90c40fdffb53a7d189847a0ec560bcf8b9352f8b2407f664736f6c63430008070033

Deployed Bytecode

0x6080604052600436106104395760003560e01c80636d8ea5b41161022b578063a98d36231161012f578063d32bfb6c116100b1578063d32bfb6c14610dbb578063d52e4a1014610ddb578063d547741f14610df1578063d813cc1914610e11578063e985e9c514610e24578063f0ba604014610e44578063f12c6b6e14610e59578063f32e8b2414610e79578063f3fef3a314610e8e578063f5766b3914610eae578063f8548e3614610ece57600080fd5b8063a98d362314610c4c578063aae4b8f714610c7f578063b11d7ec114610c9f578063b1a3b25d14610cbf578063b585a6d514610cdf578063b88d4fde14610cff578063bf4a927014610d1f578063c87b56dd14610d3f578063cb0703c614610d5f578063d1bbd49c14610d7f578063d250348514610d9b57600080fd5b80638ca2fbad116101b85780638ca2fbad14610b125780638da5cb5b14610b3457806391d1485414610b5357806392ac98a514610b7357806393fd184414610b9357806395d89b4114610baa5780639d76ea5814610bbf578063a217fddf14610be0578063a22cb46514610bf5578063a2e4cd2e14610c15578063a375cb0514610c3557600080fd5b80636d8ea5b4146109da5780636eadde43146109fa57806370a0823114610a1a57806374b6c10614610a3a578063782a4ade14610a515780637ec2a72414610a7157806381a3c94314610a925780638505fe9514610ab25780638577a6d514610ad25780638932a90d14610af257600080fd5b80632f54bf6e1161033d5780634e2ce6d3116102bf5780634e2ce6d3146108855780634f6ccce71461089c57806352b0f638146108bc57806354b249fb146108dc578063550ef3a81461090d578063558b71e91461092d578063564aa99d1461094d57806356e0d51f1461096d5780636207a8da14610984578063626485a31461099a5780636352211e146109ba57600080fd5b80632f54bf6e1461070a5780632f745c591461073a57806330176e131461075a57806331159a171461077a578063338189971461079a57806336568abe146107ad57806338af3eed146107cd57806339f46986146107ee57806342842e0e1461080e57806342966c681461082e5780634d025fed1461084e57600080fd5b806313af4035116103c657806313af4035146105a857806318160ddd146105c8578063183767da146105de578063217751bc146105f5578063231005091461061657806323b872dd14610638578063248a9ca31461065857806326e9ca07146106885780632af9162a146106a95780632d33dd5b146106c95780632f2ff15d146106ea57600080fd5b806301ffc9a714610445578063068208cd1461047a57806306fdde031461049c578063081812fc146104be578063095ea7b3146104eb578063097ba3331461050b5780630aaffd2a146105395780630f15023b1461055957806310e569731461057a57806311a4c03a1461059157600080fd5b3661044057005b600080fd5b34801561045157600080fd5b50610465610460366004615701565b610eee565b60405190151581526020015b60405180910390f35b34801561048657600080fd5b5061049a6104953660046158bd565b610eff565b005b3480156104a857600080fd5b506104b1610fab565b6040516104719190615b1e565b3480156104ca57600080fd5b506104de6104d93660046156c3565b61103a565b6040516104719190615a20565b3480156104f757600080fd5b5061049a6105063660046154f4565b611062565b34801561051757600080fd5b5061052b6105263660046153b9565b6110fd565b604051908152602001610471565b34801561054557600080fd5b5061049a610554366004615282565b6111ad565b34801561056557600080fd5b50610c83546104de906001600160a01b031681565b34801561058657600080fd5b5061052b610c855481565b34801561059d57600080fd5b5061052b610c845481565b3480156105b457600080fd5b5061049a6105c3366004615282565b6111fe565b3480156105d457600080fd5b50610c875461052b565b3480156105ea57600080fd5b5061052b6124075481565b34801561060157600080fd5b50610c8a546104de906001600160a01b031681565b34801561062257600080fd5b5061052b600080516020615f6883398151915281565b34801561064457600080fd5b5061049a61065336600461541a565b6112bc565b34801561066457600080fd5b5061052b6106733660046156c3565b60009081526097602052604090206001015490565b34801561069457600080fd5b50610c8b546104de906001600160a01b031681565b3480156106b557600080fd5b5061049a6106c4366004615282565b611460565b3480156106d557600080fd5b50610c89546104de906001600160a01b031681565b3480156106f657600080fd5b5061049a6107053660046156dc565b6114b7565b34801561071657600080fd5b50610465610725366004615282565b612bda546001600160a01b0390811691161490565b34801561074657600080fd5b5061052b6107553660046154f4565b6114dd565b34801561076657600080fd5b5061049a61077536600461573b565b61155c565b34801561078657600080fd5b5061049a6107953660046156c3565b611571565b61049a6107a83660046155ee565b6115b9565b3480156107b957600080fd5b5061049a6107c83660046156dc565b611a73565b3480156107d957600080fd5b50610c88546104de906001600160a01b031681565b3480156107fa57600080fd5b5061049a610809366004615832565b611af1565b34801561081a57600080fd5b5061049a61082936600461541a565b611b3f565b34801561083a57600080fd5b5061049a6108493660046156c3565b611b5a565b34801561085a57600080fd5b506104de6108693660046156c3565b611078602052600090815260409020546001600160a01b031681565b34801561089157600080fd5b5061052b610c8d5481565b3480156108a857600080fd5b5061052b6108b73660046156c3565b611bab565b3480156108c857600080fd5b506104656108d7366004615282565b611bf2565b3480156108e857600080fd5b5061052b6108f73660046156c3565b600090815261107b602052604090206001015490565b34801561091957600080fd5b5061049a61092836600461573b565b611c0c565b34801561093957600080fd5b5061049a610948366004615832565b611c21565b34801561095957600080fd5b5061049a610968366004615282565b611c45565b34801561097957600080fd5b5061052b6127f05481565b34801561099057600080fd5b5061201e5461052b565b3480156109a657600080fd5b5061049a6109b53660046156c3565b611c9c565b3480156109c657600080fd5b506104de6109d53660046156c3565b611caa565b3480156109e657600080fd5b506104656109f5366004615282565b611cc6565b348015610a0657600080fd5b5061049a610a1536600461529f565b611dc3565b348015610a2657600080fd5b5061052b610a35366004615282565b611f27565b348015610a4657600080fd5b5061052b610c865481565b348015610a5d57600080fd5b5061049a610a6c36600461573b565b611f6c565b348015610a7d57600080fd5b50610c8c546104de906001600160a01b031681565b348015610a9e57600080fd5b5061049a610aad366004615555565b611fb3565b348015610abe57600080fd5b5061049a610acd3660046156dc565b612105565b348015610ade57600080fd5b5061049a610aed3660046156c3565b6123db565b348015610afe57600080fd5b5061049a610b0d36600461573b565b61241c565b348015610b1e57600080fd5b5061052b600080516020615fa883398151915281565b348015610b4057600080fd5b50612bda546001600160a01b03166104de565b348015610b5f57600080fd5b50610465610b6e3660046156dc565b61263f565b348015610b7f57600080fd5b5061052b610b8e3660046156c3565b61266a565b348015610b9f57600080fd5b5061052b6110775481565b348015610bb657600080fd5b506104b161275a565b348015610bcb57600080fd5b506104b1546104de906001600160a01b031681565b348015610bec57600080fd5b5061052b600081565b348015610c0157600080fd5b5061049a610c103660046154c6565b612893565b348015610c2157600080fd5b5061049a610c303660046156dc565b61294d565b348015610c4157600080fd5b5061052b6127f15481565b348015610c5857600080fd5b50610465610c673660046156c3565b600090815261107b6020526040902060010154421090565b348015610c8b57600080fd5b50610465610c9a366004615282565b6129db565b348015610cab57600080fd5b5061049a610cba3660046156dc565b6129f5565b348015610ccb57600080fd5b5061052b610cda366004615832565b612a6d565b348015610ceb57600080fd5b50610465610cfa3660046154f4565b612add565b348015610d0b57600080fd5b5061049a610d1a36600461545b565b612b6e565b348015610d2b57600080fd5b5061049a610d3a3660046156c3565b612ba1565b348015610d4b57600080fd5b506104b1610d5a3660046156c3565b612bf8565b348015610d6b57600080fd5b5061049a610d7a36600461535d565b612ea7565b348015610d8b57600080fd5b50604051600a8152602001610471565b348015610da757600080fd5b5061049a610db6366004615282565b61309e565b348015610dc757600080fd5b5061049a610dd63660046156c3565b6130f5565b348015610de757600080fd5b50610c8e5461052b565b348015610dfd57600080fd5b5061049a610e0c3660046156dc565b613127565b61049a610e1f366004615854565b61314d565b348015610e3057600080fd5b50610465610e3f366004615324565b6132a8565b348015610e5057600080fd5b5061049a6132d7565b348015610e6557600080fd5b5061049a610e74366004615520565b61331c565b348015610e8557600080fd5b5061049a613518565b348015610e9a57600080fd5b5061049a610ea93660046154f4565b613528565b348015610eba57600080fd5b5061049a610ec93660046156c3565b613687565b348015610eda57600080fd5b50610465610ee936600461580b565b613695565b6000610ef9826136f7565b92915050565b610f0883613702565b610f118361374f565b610f1a836137a0565b610f2382613702565b8042610f3f85600090815261107b602052604090206001015490565b610f499190615e42565b1015610f8e5760405162461bcd60e51b815260206004820152600f60248201526e4e4f545f454e4f5547485f54494d4560881b60448201526064015b60405180910390fd5b610f9a83826000613841565b610fa682826001613841565b505050565b6114638054610fb990615e9c565b80601f0160208091040260200160405190810160405280929190818152602001828054610fe590615e9c565b80156110325780601f1061100757610100808354040283529160200191611032565b820191906000526020600020905b81548152906001019060200180831161101557829003601f168201915b505050505081565b600061104582613702565b50600090815261107960205260409020546001600160a01b031690565b61106b816137a0565b336001600160a01b03831614156110945760405162461bcd60e51b8152600401610f8590615c39565b60008181526110796020908152604080832080546001600160a01b0319166001600160a01b03878116918217909255611076909352818420549151859492909116917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a45050565b610c89546000906001600160a01b0316156111a057610c895460405163221c1fd160e01b81526001600160a01b039091169063221c1fd190611149903390889088908890600401615aa3565b60206040518083038186803b15801561116157600080fd5b505afa158015611175573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061119991906157f2565b90506111a6565b50610c85545b9392505050565b6111b56138f7565b6001600160a01b0381166111db5760405162461bcd60e51b8152600401610f8590615b61565b610c8880546001600160a01b0319166001600160a01b0392909216919091179055565b611206613964565b6001600160a01b0381166112595760405162461bcd60e51b815260206004820152601a6024820152794f574e45525f43414e545f42455f414444524553535f5a45524f60301b6044820152606401610f85565b612bda80546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091015b60405180910390a15050565b6112c58161374f565b6112ce816137a0565b826001600160a01b03166112e182611caa565b6001600160a01b0316146113375760405162461bcd60e51b815260206004820152601c60248201527f5452414e534645525f46524f4d3a204e4f545f4b45595f4f574e4552000000006044820152606401610f85565b612710612407541061135b5760405162461bcd60e51b8152600401610f8590615b31565b6001600160a01b0382166113815760405162461bcd60e51b8152600401610f8590615b61565b816001600160a01b0316836001600160a01b031614156113b35760405162461bcd60e51b8152600401610f8590615be8565b6113c9816113c2836000612a6d565b6000613841565b600081815261107b602052604090206113e183611f27565b6113fc5761107780549060006113f683615ed1565b91905055505b611405826139bc565b61140f8284613ac3565b61141a826000613b82565b61142382613c0f565b600082815261202060205260408082208290555183916001600160a01b038087169290881691600080516020615f8883398151915291a450505050565b611468613964565b611480600080516020615f6883398151915282613127565b6040516001600160a01b038216907f766f6199fea59554b9ff688e413302b9200f85d74811c053c12d945ac6d8dd7a90600090a250565b6000828152609760205260409020600101546114d38133613c4c565b610fa68383613cb0565b60006114e883611f27565b82106115325760405162461bcd60e51b81526020600482015260196024820152784f574e45525f494e4445585f4f55545f4f465f424f554e445360381b6044820152606401610f85565b506001600160a01b0391909116600090815261107c60209081526040808320938352929052205490565b611564613964565b610fa6611465838361503a565b611579613964565b806115b35760405162461bcd60e51b815260206004820152600a6024820152694e554c4c5f56414c554560b01b6044820152606401610f85565b610c8e55565b6115c1613d36565b610c8754610c8654116115e65760405162461bcd60e51b8152600401610f8590615c12565b83518551146116325760405162461bcd60e51b81526020600482015260186024820152770929cac82989288bea48a8c8aa4a48aa4a6be988a9c8ea8960431b6044820152606401610f85565b82518551146116835760405162461bcd60e51b815260206004820152601b60248201527f494e56414c49445f4b45595f4d414e41474552535f4c454e47544800000000006044820152606401610f85565b60008060005b875181101561199e5760008882815181106116a6576116a6615f02565b6020026020010151905060006001600160a01b0316816001600160a01b031614156116e35760405162461bcd60e51b8152600401610f8590615b61565b600019610c8454141561171d576117168188848151811061170657611706615f02565b6020026020010151600019613d7e565b9250611752565b61174f8188848151811061173357611733615f02565b6020026020010151610c84544261174a9190615da1565b613d7e565b92505b60006117d0828a858151811061176a5761176a615f02565b602002602001015189898781811061178457611784615f02565b90506020028101906117969190615ce1565b8080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506110fd92505050565b90506117dc8186615da1565b600085815261201f60209081526040808320859055610c84546120208352818420556104b18054612021909352922080546001600160a01b0319166001600160a01b0392831617905590549196501615611869578a838151811061184257611842615f02565b60200260200101518111156118695760405162461bcd60e51b8152600401610f8590615b8a565b61188c818a858151811061187f5761187f615f02565b6020026020010151613e2e565b6104b1546000906001600160a01b0316156118c0578b84815181106118b3576118b3615f02565b60200260200101516118c2565b345b610c89549091506001600160a01b03161561198857610c89548a516001600160a01b0390911690639849965790339086908e908990811061190557611905615f02565b60200260200101518c8c8a81811061191f5761191f615f02565b90506020028101906119319190615ce1565b88886040518863ffffffff1660e01b81526004016119559796959493929190615a58565b600060405180830381600087803b15801561196f57600080fd5b505af1158015611983573d6000803e3d6000fd5b505050505b505050808061199690615ed1565b915050611689565b506104b1546001600160a01b031615611a41576104b1546040516323b872dd60e01b81526001600160a01b039091169081906323b872dd906119e890339030908890600401615a34565b602060405180830381600087803b158015611a0257600080fd5b505af1158015611a16573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a3a91906156a6565b5050611a61565b34821115611a615760405162461bcd60e51b8152600401610f8590615bbc565b611a69613f33565b5050505050505050565b6001600160a01b0381163314611ae35760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b6064820152608401610f85565b611aed82826140bb565b5050565b611af9613964565b60408051838152602081018390527fd6867bc538320e67d7bdc35860c27c08486eb490b4fd9b820fff18fb28381d3c910160405180910390a16127f1919091556127f055565b610fa683838360405180602001604052806000815250612b6e565b611b6381613702565b611b6c816137a0565b600081815261107660205260408082205490518392916001600160a01b031690600080516020615f88833981519152908390a4611ba881614122565b50565b6000610c87548210611bee5760405162461bcd60e51b815260206004820152600c60248201526b4f55545f4f465f52414e474560a01b6044820152606401610f85565b5090565b6000610ef9600080516020615f688339815191528361263f565b611c14613964565b610fa6611463838361503a565b611c2a82613702565b611c338261374f565b611c3b613964565b611aed828261415c565b611c4d613964565b611c65600080516020615f68833981519152826114b7565b6040516001600160a01b038216907f684f8a71407db0c334454ebe9c9b288549317893a20b10dc779ec5c118dcd12190600090a250565b611ca4613964565b610c8455565b600090815261107660205260409020546001600160a01b031690565b600080611cd283611f27565b90508015611d155760005b81811015611d1357611cf2610c6785836114dd565b15611d01575060019392505050565b80611d0b81615ed1565b915050611cdd565b505b610c8b546001600160a01b031615611dbd57610c8b546040516370b6638f60e11b81523060048201526001600160a01b0385811660248301526000604483015284151560648301529091169063e16cc71e9060840160206040518083038186803b158015611d8257600080fd5b505afa158015611d96573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611dba91906156a6565b91505b50919050565b600054610100900460ff16611dde5760005460ff1615611de2565b303b155b611e455760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401610f85565b600054610100900460ff16158015611e67576000805461ffff19166101011790555b611e7086614263565b610c838054336001600160a01b031991821617909155610c8880549091166001600160a01b038a16179055610c84879055610c85859055610c86849055600a610c8d556001610c8e55611ec3838361428f565b611ecb6142b5565b611ed76103e86127f055565b611ee0886142c5565b612bda80546001600160a01b0319166001600160a01b038a16179055611f0c6380ac58cd60e01b61434d565b8015611a69576000805461ff00191690555050505050505050565b60006001600160a01b038216611f4f5760405162461bcd60e51b8152600401610f8590615b61565b506001600160a01b0316600090815261107e602052604090205490565b611f74613964565b611f81611464838361503a565b507f8868e22e84ebf32da89b2ebcb0ac642816304ea3863b257f240df9098719cb9782826040516112b0929190615b0a565b611fbb613d36565b611fc433611bf2565b80611fd35750611fd3336129db565b61201f5760405162461bcd60e51b815260206004820181905260248201527f4f4e4c595f4c4f434b5f4d414e414745525f4f525f4b45595f4752414e5445526044820152606401610f85565b60005b858110156120fc57600087878381811061203e5761203e615f02565b90506020020160208101906120539190615282565b6001600160a01b0316141561207a5760405162461bcd60e51b8152600401610f8590615b61565b6120e987878381811061208f5761208f615f02565b90506020020160208101906120a49190615282565b8484848181106120b6576120b6615f02565b90506020020160208101906120cb9190615282565b8787858181106120dd576120dd615f02565b90506020020135613d7e565b50806120f481615ed1565b915050612022565b50505050505050565b61210d613d36565b61211682613702565b60008281526120206020526040902054600019141561216b5760405162461bcd60e51b81526020600482015260116024820152704e4f4e5f4558504952494e475f4c4f434b60781b6044820152606401610f85565b6104b1546001600160a01b03166121b55760405162461bcd60e51b815260206004820152600e60248201526d4e4f4e5f45524332305f4c4f434b60901b6044820152606401610f85565b60006121d96121c384611caa565b83604051806020016040528060008152506110fd565b600084815261201f6020526040902054909150811461222a5760405162461bcd60e51b815260206004820152600d60248201526c14149250d157d0d2105391d151609a1b6044820152606401610f85565b610c8454600084815261202060205260409020541461227e5760405162461bcd60e51b815260206004820152601060248201526f11155490551253d397d0d2105391d15160821b6044820152606401610f85565b6104b154600084815261202160205260409020546001600160a01b039081169116146122dc5760405162461bcd60e51b815260206004820152600d60248201526c1513d2d15397d0d2105391d151609a1b6044820152606401610f85565b600083815261107b602052604090206001015442101561232a5760405162461bcd60e51b81526020600482015260096024820152684e4f545f524541445960b81b6044820152606401610f85565b612333836143cc565b5061233e8183613e2e565b6104b1546001600160a01b0316806323b872dd61235a86611caa565b30856040518463ffffffff1660e01b815260040161237a93929190615a34565b602060405180830381600087803b15801561239457600080fd5b505af11580156123a8573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123cc91906156a6565b506123d5613f33565b50505050565b6123e3613964565b6040518181527f0496ed1e61eb69727f9659a8e859288db4758ffb1f744d1c1424634f90a257f49060200160405180910390a161240755565b6124286001600a615e1f565b61ffff16610c8d54148061243d5750610c8d54155b6124865760405162461bcd60e51b815260206004820152601a60248201527914d0d211535057d5915494d253d397d393d517d0d3d4949150d560321b6044820152606401610f85565b610c8e54612495576001610c8e555b6000808060006124a5610c875490565b610c83549091506001600160a01b03163314156124c557606491506124d7565b6124d185870187615832565b90945091505b808211156124e3578091505b835b6124ef8386615da1565b8110156125f5576000612503826001615da1565b600081815261107660209081526040808320546001600160a01b031680845261107583529281902081518083019092528054808352600190910154928201929092529293509091901580159061255c5750602081015115155b156125df5760408051808201909152815181526020808301519082015261107b6000612589876001615da1565b81526020808201929092526040908101600090812084518155938301516001948501556001600160a01b03861681526110759092528120818155909101556125d18383613ac3565b866125db81615ed1565b9750505b50505080806125ed90615ed1565b9150506124e5565b5080821061260457600a610c8d555b6040518381527f4e075b7dcd463fe1b504dad3ccd1c4f87011a70c1e50f1805183df5d559834cd9060200160405180910390a1505050505050565b60009182526097602090815260408084206001600160a01b0393909316845291905290205460ff1690565b60006126758261374f565b600019610c8454141561268b575050610c855490565b600082815261107b60205260408120600101546126a9904290615e42565b9050610c84546127f154826126be9190615da1565b106126ce57610c855491506126ee565b610c845481610c85546126e19190615e00565b6126eb9190615dde565b91505b6127f154158061270d5750610c84546127f15461270b9083615da1565b105b15611dbd5760006127106127f054610c85546127299190615e00565b6127339190615dde565b90508083111561274e576127478184615e42565b9250612753565b600092505b5050919050565b6060611464805461276a90615e9c565b1515905061280257610c8360009054906101000a90046001600160a01b03166001600160a01b031663cec410526040518163ffffffff1660e01b815260040160006040518083038186803b1580156127c157600080fd5b505afa1580156127d5573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526127fd919081019061577c565b905090565b611464805461281090615e9c565b80601f016020809104026020016040519081016040528092919081815260200182805461283c90615e9c565b80156128895780601f1061285e57610100808354040283529160200191612889565b820191906000526020600020905b81548152906001019060200180831161286c57829003601f168201915b5050505050905090565b6001600160a01b0382163314156128bc5760405162461bcd60e51b8152600401610f8590615c39565b61271061240754106128e05760405162461bcd60e51b8152600401610f8590615b31565b33600081815261107a602090815260408083206001600160a01b03871680855290835292819020805460ff191686151590811790915590519081529192917f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a35050565b612955613964565b61295e816144cd565b610c8580546104b18054928590556001600160a01b031983166001600160a01b0385811691821790925560408051848152602081018890529290941693820184905260608201529091907f3615065ccf48367ac483ac86701248e2e5ff55bdd9be845007d34a3b68d719d49060800160405180910390a150505050565b6000610ef9600080516020615fa88339815191528361263f565b6129fe82613702565b612a08823361458f565b80612a175750612a17336129db565b612a635760405162461bcd60e51b815260206004820152601f60248201527f554e415554484f52495a45445f4b45595f4d414e414745525f555044415445006044820152606401610f85565b611aed8282613b82565b6000612a7883613702565b600083815261107b602052604090206001015442811015612a9d576000915050610ef9565b600083612ab557612aae4283615e42565b9050612ab8565b50825b6127106124075482612aca9190615e00565b612ad49190615dde565b92505050610ef9565b6000612ae76138f7565b6104b15460405163095ea7b360e01b81526001600160a01b038581166004830152602482018590529091169063095ea7b390604401602060405180830381600087803b158015612b3657600080fd5b505af1158015612b4a573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111a691906156a6565b612b798484846112bc565b612b8584848484614607565b6123d55760405162461bcd60e51b8152600401610f8590615caa565b612ba9613964565b610c8754811015612bf25760405162461bcd60e51b8152602060048201526013602482015272534d414c4c45525f5448414e5f535550504c5960681b6044820152606401610f85565b610c8655565b606080806000612c07306146c6565b905060608515612c2157612c1a866148b1565b9250612c34565b6040518060200160405280600081525092505b610c8c546001600160a01b031615612d1a57600086815261107b6020526040902060010154610c8c546001600160a01b031663988b93ad3033612c768b611caa565b60405160e085901b6001600160e01b03191681526001600160a01b03938416600482015291831660248301529091166044820152606481018a90526084810184905260a40160006040518083038186803b158015612cd357600080fd5b505afa158015612ce7573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612d0f919081019061577c565b979650505050505050565b6114658054612d2890615e9c565b15159050612dde57610c8360009054906101000a90046001600160a01b03166001600160a01b031663a998e9fb6040518163ffffffff1660e01b815260040160006040518083038186803b158015612d7f57600080fd5b505afa158015612d93573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612dbb919081019061577c565b9350604051806040016040528060018152602001602f60f81b8152509050612e91565b6114658054612dec90615e9c565b80601f0160208091040260200160405190810160405280929190818152602001828054612e1890615e9c565b8015612e655780601f10612e3a57610100808354040283529160200191612e65565b820191906000526020600020905b815481529060010190602001808311612e4857829003601f168201915b505050505093506040518060200160405280600081525090506040518060200160405280600081525091505b612e9d848383866149dd565b9695505050505050565b612eaf613964565b6001600160a01b0384161580612ece57506001600160a01b0384163b15155b612f155760405162461bcd60e51b8152602060048201526018602482015277494e56414c49445f4f4e5f4b45595f534f4c445f484f4f4b60401b6044820152606401610f85565b6001600160a01b0383161580612f3457506001600160a01b0383163b15155b612f7d5760405162461bcd60e51b815260206004820152601a602482015279494e56414c49445f4f4e5f4b45595f43414e43454c5f484f4f4b60301b6044820152606401610f85565b6001600160a01b0382161580612f9c57506001600160a01b0382163b15155b612fe45760405162461bcd60e51b8152602060048201526019602482015278494e56414c49445f4f4e5f56414c49445f4b45595f484f4f4b60381b6044820152606401610f85565b6001600160a01b038116158061300357506001600160a01b0381163b15155b61304b5760405162461bcd60e51b8152602060048201526019602482015278494e56414c49445f4f4e5f544f4b454e5f5552495f484f4f4b60381b6044820152606401610f85565b610c8980546001600160a01b039586166001600160a01b031991821617909155610c8a805494861694821694909417909355610c8c8054918516918416919091179055610c8b8054919093169116179055565b6130a6613964565b6130be600080516020615fa8833981519152826114b7565b6040516001600160a01b038216907f91d5c045d5bd98bf59a379b259ebca05b93bf79af1845fdf87e3172385d4c7f790600090a250565b6130fe81613702565b6131078161374f565b613110816137a0565b600061311b8261266a565b9050611aed828261415c565b6000828152609760205260409020600101546131438133613c4c565b610fa683836140bb565b613155613d36565b61315e84613702565b613167846143cc565b5060006131b361317686611caa565b8585858080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152506110fd92505050565b6104b1549091506001600160a01b03161561327857858111156131e85760405162461bcd60e51b8152600401610f8590615b8a565b6104b1546040516323b872dd60e01b81526001600160a01b039091169081906323b872dd9061321f90339030908790600401615a34565b602060405180830381600087803b15801561323957600080fd5b505af115801561324d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061327191906156a6565b5050613298565b348111156132985760405162461bcd60e51b8152600401610f8590615bbc565b6132a0613f33565b505050505050565b6001600160a01b03918216600090815261107a6020908152604080832093909416825291909152205460ff1690565b6132ef600080516020615fa883398151915233611a73565b60405133907f42885193b8178d25fca25a38e6fcc93918501e91be06d85e0c8afb3bad95238090600090a2565b613324613d36565b610c8754610c8654116133495760405162461bcd60e51b8152600401610f8590615c12565b613352826137a0565b61335b8261374f565b612710612407541061337f5760405162461bcd60e51b8152600401610f8590615b31565b6001600160a01b0383166133a55760405162461bcd60e51b8152600401610f8590615b61565b600082815261107660205260409020546001600160a01b039081169084168114156133e25760405162461bcd60e51b8152600401610f8590615be8565b600083815261107b60205260408120600101548190613402904290615e42565b905060006134108686612a6d565b9050600061341e8287615da1565b90508281101561343c5785935061343787826000613841565b613494565b6134468784612a6d565b91506134528284615e42565b600088815261107b6020526040808220426001909101555191955088917f59f2fe866dd27a1c2d34115520888c3150365cbc931aab97fa88c4b9ab40b7959190a25b60006134a5898261174a8842615da1565b905080896001600160a01b0316876001600160a01b0316600080516020615f8883398151915260405160405180910390a46134f1868a8360405180602001604052806000815250614607565b61350d5760405162461bcd60e51b8152600401610f8590615caa565b505050505050505050565b613520613964565b600a610c8d55565b6135306138f7565b60006001600160a01b0383166135475750476135c6565b6040516370a0823160e01b81526001600160a01b038416906370a0823190613573903090600401615a20565b60206040518083038186803b15801561358b57600080fd5b505afa15801561359f573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906135c391906157f2565b90505b60008215806135d457508183115b15613623576000821161361c5760405162461bcd60e51b815260206004820152601060248201526f4e4f545f454e4f5547485f46554e445360801b6044820152606401610f85565b5080613626565b50815b610c88546040518281526001600160a01b039182169186169033907f342e7ff505a8a0364cd0dc2ff195c315e43bce86b204846ecd36913e117b109e9060200160405180910390a4610c88546123d59085906001600160a01b031683614a0f565b61368f613964565b61201e55565b60006136a08461374f565b600061271083426136c188600090815261107b602052604090206001015490565b6136cb9190615e42565b6136d59190615e00565b6136df9190615dde565b90506136ec84868361331c565b506001949350505050565b6000610ef982614a4b565b600081815261107b6020526040902060010154611ba85760405162461bcd60e51b815260206004820152600b60248201526a4e4f5f535543485f4b455960a81b6044820152606401610f85565b600081815261107b60205260409020600101544210611ba85760405162461bcd60e51b815260206004820152600d60248201526c12d15657d393d517d590531251609a1b6044820152606401610f85565b6137aa813361458f565b806137cc5750600081815261107960205260409020546001600160a01b031633145b806137f55750600081815261107660205260409020546137f5906001600160a01b0316336132a8565b611ba85760405162461bcd60e51b815260206004820152601c60248201527f4f4e4c595f4b45595f4d414e414745525f4f525f415050524f564544000000006044820152606401610f85565b61384a83613702565b600083815261107b60205260409020600101548115613897574281111561388d576138758382615da1565b600085815261107b60205260409020600101556138b5565b6138758342615da1565b6138a18382615e42565b600085815261107b60205260409020600101555b60408051848152831515602082015285917fe9408df99703ae33a9d01185bcad328ea8683fb1f920da9c30959c192f21b5b3910160405180910390a250505050565b613900336129db565b806139165750610c88546001600160a01b031633145b6139625760405162461bcd60e51b815260206004820181905260248201527f4f4e4c595f4c4f434b5f4d414e414745525f4f525f42454e45464943494152596044820152606401610f85565b565b61397c600080516020615fa88339815191523361263f565b6139625760405162461bcd60e51b815260206004820152601160248201527027a7262cafa627a1a5afa6a0a720a3a2a960791b6044820152606401610f85565b600081815261107660205260408120546001600160a01b03169060016139e183611f27565b6139eb9190615e42565b600084815261107d6020526040902054909150808214613a41576001600160a01b038316600090815261107c60209081526040808320858452825280832054848452818420819055835261107d90915290208190555b6001600160a01b038316600090815261107c60209081526040808320858452909152812055613a6f83611f27565b60011415613a8e576110778054906000613a8883615e85565b91905055505b6001600160a01b038316600090815261107e60205260408120805460019290613ab8908490615e42565b909155505050505050565b6000613ace82611f27565b9050610c8e548110613b0d5760405162461bcd60e51b81526020600482015260086024820152674d41585f4b45595360c01b6044820152606401610f85565b600083815261107d602090815260408083208490556001600160a01b03851680845261107c83528184208585528352818420879055868452611076835281842080546001600160a01b03191682179055835261107e9091528120805460019290613b78908490615da1565b9091555050505050565b600082815261107860205260409020546001600160a01b03828116911614611aed5760008281526110786020526040902080546001600160a01b0319166001600160a01b038316179055613bd582613c0f565b6040516001600160a01b0382169083907f9d2895c45a420624de863a2f437b022d879f457bf7a829044055a10c5a6fd5e390600090a35050565b600081815261107960205260409020546001600160a01b031615611ba85760009081526110796020526040902080546001600160a01b0319169055565b613c56828261263f565b611aed57613c6e816001600160a01b03166014614a56565b613c79836020614a56565b604051602001613c8a9291906159b1565b60408051601f198184030181529082905262461bcd60e51b8252610f8591600401615b1e565b613cba828261263f565b611aed5760008281526097602090815260408083206001600160a01b03851684529091529020805460ff19166001179055613cf23390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b610c8d54600a146139625760405162461bcd60e51b8152602060048201526012602482015271135251d490551253d397d49154555254915160721b6044820152606401610f85565b610c87805460009182613d9083615ed1565b9091555050610c87546040805180820182528281526020808201868152600085815261107b9092529290209051815590516001909101559050613dd284611f27565b613ded576110778054906000613de783615ed1565b91905055505b613df78185613ac3565b613e018184613b82565b60405181906001600160a01b03861690600090600080516020615f88833981519152908290a49392505050565b610c83546001600160a01b03163b15613ef557610c835460405163939d9f1f60e01b8152600481018490526001600160a01b0383811660248301529091169063939d9f1f90620493e090604401600060405180830381600088803b158015613e9557600080fd5b5087f193505050508015613ea7575060015b611aed57610c835460405130917f6b18946261693dfd6c760d986b28ad2238b5b0267f8e5b6bc40a2f998e2f20ac91613ee9916001600160a01b031690615a20565b60405180910390a25050565b610c835460405130917f6b18946261693dfd6c760d986b28ad2238b5b0267f8e5b6bc40a2f998e2f20ac91613ee9916001600160a01b031690615a20565b61201e5415613962576104b1546001600160a01b031615613fe1576104b15461201e546040516323b872dd60e01b81526001600160a01b039092169182916323b872dd91613f88913091339190600401615a34565b602060405180830381600087803b158015613fa257600080fd5b505af1158015613fb6573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613fda91906156a6565b505061406d565b61201e5460405160009133918381818185875af1925050503d8060008114614025576040519150601f19603f3d011682016040523d82523d6000602084013e61402a565b606091505b505090508061406b5760405162461bcd60e51b815260206004820152600d60248201526c14915195539117d19052531151609a1b6044820152606401610f85565b505b61201e546104b154604080519283526001600160a01b03909116602083015233917f522a883b471164223f18b50f326da8671372b64b4792eac0e63d447e714c3e3b910160405180910390a2565b6140c5828261263f565b15611aed5760008281526097602090815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b61412b816139bc565b600090815261107b6020908152604080832042600190910155611076909152902080546001600160a01b0319169055565b600061416783611caa565b905061417283614122565b336001600160a01b0316816001600160a01b0316847f0a7068a9989857441c039a14a42b67ed71dd1fcfe5a9b17cc87b252e47bce528856040516141b891815260200190565b60405180910390a481156141de576104b1546141de906001600160a01b03168284614a0f565b60008381526120206020526040812055610c8a546001600160a01b031615610fa657610c8a5460405163b499b6c560e01b81526001600160a01b039091169063b499b6c59061423590339085908790600401615a34565b600060405180830381600087803b15801561424f57600080fd5b505af11580156120fc573d6000803e3d6000fd5b61426c816144cd565b6104b180546001600160a01b0319166001600160a01b0392909216919091179055565b614297614bf1565b6142a4611463838361503a565b50611aed635b5e139f60e01b61434d565b61396263780e9d6360e01b61434d565b6142dd600080516020615fa883398151915280614c28565b614303600080516020615f68833981519152600080516020615fa8833981519152614c28565b61430c816129db565b61432857614328600080516020615fa883398151915282614c73565b61433181611bf2565b611ba857611ba8600080516020615f6883398151915282614c73565b6001600160e01b031980821614156143a75760405162461bcd60e51b815260206004820152601c60248201527f4552433136353a20696e76616c696420696e74657266616365206964000000006044820152606401610f85565b6001600160e01b0319166000908152606560205260409020805460ff19166001179055565b600081815261107b60205260408120600101546000198114156144315760405162461bcd60e51b815260206004820152601c60248201527f43414e545f455854454e445f4e4f4e5f4558504952494e475f4b4559000000006044820152606401610f85565b600019610c84541415614448576000199150614476565b4281111561446557610c845461445e9082615da1565b9150614476565b610c84546144739042615da1565b91505b600083815261107b6020526040908190206001018390555183907f3ca112768ff7861e008ace1c11570c52e404c043e585545b5957a1e20961dde3906144bf9085815260200190565b60405180910390a250919050565b6001600160a01b038116158061455357506000816001600160a01b03166318160ddd6040518163ffffffff1660e01b815260040160206040518083038186803b15801561451957600080fd5b505afa15801561452d573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061455191906157f2565b115b611ba85760405162461bcd60e51b815260206004820152600d60248201526c24a72b20a624a22faa27a5a2a760991b6044820152606401610f85565b600082815261107860205260408120546001600160a01b03838116911614806145f25750600083815261107860205260409020546001600160a01b03161580156145f25750816001600160a01b03166145e784611caa565b6001600160a01b0316145b156145ff57506001610ef9565b506000610ef9565b60006001600160a01b0384163b614620575060016146be565b604051630a85bd0160e11b81526000906001600160a01b0386169063150b7a02906146559033908a9089908990600401615ad7565b602060405180830381600087803b15801561466f57600080fd5b505af1158015614683573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906146a7919061571e565b6001600160e01b031916630a85bd0160e11b149150505b949350505050565b604080518082018252601081526f181899199a1a9b1b9c1cb0b131b232b360811b60208201528151602a80825260608281019094526001600160a01b0385169291600091602082018180368337019050509050600360fc1b8160008151811061473157614731615f02565b60200101906001600160f81b031916908160001a905350600f60fb1b8160018151811061476057614760615f02565b60200101906001600160f81b031916908160001a90535060005b60148110156148a8578260048561479284600c615da1565b602081106147a2576147a2615f02565b1a60f81b6001600160f81b031916901c60f81c60ff16815181106147c8576147c8615f02565b01602001516001600160f81b031916826147e3836002615e00565b6147ee906002615da1565b815181106147fe576147fe615f02565b60200101906001600160f81b031916908160001a905350828461482283600c615da1565b6020811061483257614832615f02565b825191901a600f1690811061484957614849615f02565b01602001516001600160f81b03191682614864836002615e00565b61486f906003615da1565b8151811061487f5761487f615f02565b60200101906001600160f81b031916908160001a905350806148a081615ed1565b91505061477a565b50949350505050565b606081806148d85750506040805180820190915260018152600360fc1b6020820152919050565b8260005b811561490257806148ec81615ed1565b91506148fb9050600a83615dde565b91506148dc565b6000816001600160401b0381111561491c5761491c615f18565b6040519080825280601f01601f191660200182016040528015614946576020820181803683370190505b509050815b84156149d35761495c600182615e42565b9050600061496b600a87615dde565b61497690600a615e00565b6149809087615e42565b61498b906030615db9565b905060008160f81b9050808484815181106149a8576149a8615f02565b60200101906001600160f81b031916908160001a9053506149ca600a88615dde565b9650505061494b565b5095945050505050565b6060848484846040516020016149f6949392919061595a565b6040516020818303038152906040529050949350505050565b8015610fa6576001600160a01b038316614a3657610fa66001600160a01b03831682614c7d565b826123d56001600160a01b0382168484614d93565b6000610ef982614de5565b60606000614a65836002615e00565b614a70906002615da1565b6001600160401b03811115614a8757614a87615f18565b6040519080825280601f01601f191660200182016040528015614ab1576020820181803683370190505b509050600360fc1b81600081518110614acc57614acc615f02565b60200101906001600160f81b031916908160001a905350600f60fb1b81600181518110614afb57614afb615f02565b60200101906001600160f81b031916908160001a9053506000614b1f846002615e00565b614b2a906001615da1565b90505b6001811115614ba2576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110614b5e57614b5e615f02565b1a60f81b828281518110614b7457614b74615f02565b60200101906001600160f81b031916908160001a90535060049490941c93614b9b81615e85565b9050614b2d565b5083156111a65760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401610f85565b600054610100900460ff16614c185760405162461bcd60e51b8152600401610f8590615c5f565b614c20614e0a565b613962614e0a565b600082815260976020526040808220600101805490849055905190918391839186917fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff9190a4505050565b611aed8282613cb0565b80471015614ccd5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e63650000006044820152606401610f85565b6000826001600160a01b03168260405160006040518083038185875af1925050503d8060008114614d1a576040519150601f19603f3d011682016040523d82523d6000602084013e614d1f565b606091505b5050905080610fa65760405162461bcd60e51b815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c20726044820152791958da5c1a595b9d081b585e481a185d99481c995d995c9d195960321b6064820152608401610f85565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b179052610fa6908490614e31565b60006001600160e01b03198216637965db0b60e01b1480610ef95750610ef982614f03565b600054610100900460ff166139625760405162461bcd60e51b8152600401610f8590615c5f565b6000614e86826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316614f3f9092919063ffffffff16565b805190915015610fa65780806020019051810190614ea491906156a6565b610fa65760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401610f85565b60006301ffc9a760e01b6001600160e01b031983161480610ef95750506001600160e01b03191660009081526065602052604090205460ff1690565b60606146be848460008585843b614f985760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610f85565b600080866001600160a01b03168587604051614fb4919061593e565b60006040518083038185875af1925050503d8060008114614ff1576040519150601f19603f3d011682016040523d82523d6000602084013e614ff6565b606091505b5091509150612d0f828286606083156150105750816111a6565b8251156150205782518084602001fd5b8160405162461bcd60e51b8152600401610f859190615b1e565b82805461504690615e9c565b90600052602060002090601f01602090048101928261506857600085556150ae565b82601f106150815782800160ff198235161785556150ae565b828001600101855582156150ae579182015b828111156150ae578235825591602001919060010190615093565b50611bee9291505b80821115611bee57600081556001016150b6565b60008083601f8401126150dc57600080fd5b5081356001600160401b038111156150f357600080fd5b6020830191508360208260051b850101111561510e57600080fd5b9250929050565b600082601f83011261512657600080fd5b8135602061513b61513683615d57565b615d27565b80838252828201915082860187848660051b890101111561515b57600080fd5b60005b8581101561518357813561517181615f2e565b8452928401929084019060010161515e565b5090979650505050505050565b600082601f8301126151a157600080fd5b813560206151b161513683615d57565b80838252828201915082860187848660051b89010111156151d157600080fd5b60005b85811015615183578135845292840192908401906001016151d4565b60008083601f84011261520257600080fd5b5081356001600160401b0381111561521957600080fd5b60208301915083602082850101111561510e57600080fd5b600082601f83011261524257600080fd5b813561525061513682615d7a565b81815284602083860101111561526557600080fd5b816020850160208301376000918101602001919091529392505050565b60006020828403121561529457600080fd5b81356111a681615f2e565b600080600080600080600060c0888a0312156152ba57600080fd5b87356152c581615f2e565b96506020880135955060408801356152dc81615f2e565b9450606088013593506080880135925060a08801356001600160401b0381111561530557600080fd5b6153118a828b016151f0565b989b979a50959850939692959293505050565b6000806040838503121561533757600080fd5b823561534281615f2e565b9150602083013561535281615f2e565b809150509250929050565b6000806000806080858703121561537357600080fd5b843561537e81615f2e565b9350602085013561538e81615f2e565b9250604085013561539e81615f2e565b915060608501356153ae81615f2e565b939692955090935050565b6000806000606084860312156153ce57600080fd5b83356153d981615f2e565b925060208401356153e981615f2e565b915060408401356001600160401b0381111561540457600080fd5b61541086828701615231565b9150509250925092565b60008060006060848603121561542f57600080fd5b833561543a81615f2e565b9250602084013561544a81615f2e565b929592945050506040919091013590565b6000806000806080858703121561547157600080fd5b843561547c81615f2e565b9350602085013561548c81615f2e565b92506040850135915060608501356001600160401b038111156154ae57600080fd5b6154ba87828801615231565b91505092959194509250565b600080604083850312156154d957600080fd5b82356154e481615f2e565b9150602083013561535281615f43565b6000806040838503121561550757600080fd5b823561551281615f2e565b946020939093013593505050565b60008060006060848603121561553557600080fd5b833561554081615f2e565b95602085013595506040909401359392505050565b6000806000806000806060878903121561556e57600080fd5b86356001600160401b038082111561558557600080fd5b6155918a838b016150ca565b909850965060208901359150808211156155aa57600080fd5b6155b68a838b016150ca565b909650945060408901359150808211156155cf57600080fd5b506155dc89828a016150ca565b979a9699509497509295939492505050565b60008060008060008060a0878903121561560757600080fd5b86356001600160401b038082111561561e57600080fd5b61562a8a838b01615190565b9750602089013591508082111561564057600080fd5b61564c8a838b01615115565b9650604089013591508082111561566257600080fd5b61566e8a838b01615115565b9550606089013591508082111561568457600080fd5b6156908a838b01615115565b945060808901359150808211156155cf57600080fd5b6000602082840312156156b857600080fd5b81516111a681615f43565b6000602082840312156156d557600080fd5b5035919050565b600080604083850312156156ef57600080fd5b82359150602083013561535281615f2e565b60006020828403121561571357600080fd5b81356111a681615f51565b60006020828403121561573057600080fd5b81516111a681615f51565b6000806020838503121561574e57600080fd5b82356001600160401b0381111561576457600080fd5b615770858286016151f0565b90969095509350505050565b60006020828403121561578e57600080fd5b81516001600160401b038111156157a457600080fd5b8201601f810184136157b557600080fd5b80516157c361513682615d7a565b8181528560208385010111156157d857600080fd5b6157e9826020830160208601615e59565b95945050505050565b60006020828403121561580457600080fd5b5051919050565b60008060006060848603121561582057600080fd5b83359250602084013561544a81615f2e565b6000806040838503121561584557600080fd5b50508035926020909101359150565b60008060008060006080868803121561586c57600080fd5b8535945060208601359350604086013561588581615f2e565b925060608601356001600160401b038111156158a057600080fd5b6158ac888289016151f0565b969995985093965092949392505050565b6000806000606084860312156158d257600080fd5b505081359360208301359350604090920135919050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6000815180845261592a816020860160208601615e59565b601f01601f19169290920160200192915050565b60008251615950818460208701615e59565b9190910192915050565b6000855161596c818460208a01615e59565b855190830190615980818360208a01615e59565b8551910190615993818360208901615e59565b84519101906159a6818360208801615e59565b019695505050505050565b76020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b8152600083516159e3816017850160208801615e59565b7001034b99036b4b9b9b4b733903937b6329607d1b6017918401918201528351615a14816028840160208801615e59565b01602801949350505050565b6001600160a01b0391909116815260200190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b6001600160a01b03888116825287811660208301528616604082015260c060608201819052600090615a8d90830186886158e9565b60808301949094525060a0015295945050505050565b6001600160a01b038581168252848116602083015283166040820152608060608201819052600090612e9d90830184615912565b6001600160a01b0385811682528416602082015260408101839052608060608201819052600090612e9d90830184615912565b6020815260006146be6020830184866158e9565b6020815260006111a66020830184615912565b60208082526016908201527512d15657d514905394d1915494d7d11254d05093115160521b604082015260600190565b6020808252600f908201526e494e56414c49445f4144445245535360881b604082015260600190565b602080825260189082015277494e53554646494349454e545f45524332305f56414c554560401b604082015260600190565b602080825260129082015271494e53554646494349454e545f56414c554560701b604082015260600190565b60208082526010908201526f2a2920a729a322a92faa27afa9a2a62360811b604082015260600190565b6020808252600d908201526c1313d0d2d7d4d3d31117d3d555609a1b604082015260600190565b6020808252600c908201526b20a8282927ab22afa9a2a62360a11b604082015260600190565b6020808252602b908201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960408201526a6e697469616c697a696e6760a81b606082015260800190565b6020808252601d908201527f4e4f4e5f434f4d504c49414e545f4552433732315f5245434549564552000000604082015260600190565b6000808335601e19843603018112615cf857600080fd5b8301803591506001600160401b03821115615d1257600080fd5b60200191503681900382131561510e57600080fd5b604051601f8201601f191681016001600160401b0381118282101715615d4f57615d4f615f18565b604052919050565b60006001600160401b03821115615d7057615d70615f18565b5060051b60200190565b60006001600160401b03821115615d9357615d93615f18565b50601f01601f191660200190565b60008219821115615db457615db4615eec565b500190565b600060ff821660ff84168060ff03821115615dd657615dd6615eec565b019392505050565b600082615dfb57634e487b7160e01b600052601260045260246000fd5b500490565b6000816000190483118215151615615e1a57615e1a615eec565b500290565b600061ffff83811690831681811015615e3a57615e3a615eec565b039392505050565b600082821015615e5457615e54615eec565b500390565b60005b83811015615e74578181015183820152602001615e5c565b838111156123d55750506000910152565b600081615e9457615e94615eec565b506000190190565b600181811c90821680615eb057607f821691505b60208210811415611dbd57634e487b7160e01b600052602260045260246000fd5b6000600019821415615ee557615ee5615eec565b5060010190565b634e487b7160e01b600052601160045260246000fd5b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052604160045260246000fd5b6001600160a01b0381168114611ba857600080fd5b8015158114611ba857600080fd5b6001600160e01b031981168114611ba857600080fdfeb309c40027c81d382c3b58d8de24207a34b27e1db369b1434e4a11311f154b5eddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efb89cdd26cddd51301940bf2715f765b626b8a5a9e2681ac62dc83cc2db2530c0a2646970667358221220fa5eda210be05319db90c40fdffb53a7d189847a0ec560bcf8b9352f8b2407f664736f6c63430008070033

Block Transaction Difficulty Gas Used Reward
View All Blocks Produced

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

Validator Index Block Amount
View All Withdrawals

Transaction Hash Block Value Eth2 PubKey Valid
View All Deposits
Loading...
Loading
[ 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.