ETH Price: $2,672.29 (+1.33%)

Contract

0x7a5813d46a6c0098B8d6831679B72722fDaa102d
 

Overview

ETH Balance

0 ETH

Eth Value

$0.00

Multichain Info

No addresses found
Transaction Hash
Method
Block
From
To
0x60806040157670362022-10-17 9:48:47675 days ago1666000127IN
 Create: PublicLock
0 ETH0.0582489810.86836403

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.13+commit.abaa5c0e

Optimization Enabled:
Yes with 80 runs

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

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 36 : Initializable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (proxy/utils/Initializable.sol)

pragma solidity ^0.8.2;

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

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

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

    /**
     * @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
     * `onlyInitializing` functions can be used to initialize parent contracts. Equivalent to `reinitializer(1)`.
     */
    modifier initializer() {
        bool isTopLevelCall = !_initializing;
        require(
            (isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
            "Initializable: contract is already initialized"
        );
        _initialized = 1;
        if (isTopLevelCall) {
            _initializing = true;
        }
        _;
        if (isTopLevelCall) {
            _initializing = false;
            emit Initialized(1);
        }
    }

    /**
     * @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
     * contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
     * used to initialize parent contracts.
     *
     * `initializer` is equivalent to `reinitializer(1)`, so a reinitializer may be used after the original
     * initialization step. This is essential to configure modules that are added through upgrades and that require
     * initialization.
     *
     * Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
     * a contract, executing them in the right order is up to the developer or operator.
     */
    modifier reinitializer(uint8 version) {
        require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
        _initialized = version;
        _initializing = true;
        _;
        _initializing = false;
        emit Initialized(version);
    }

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

    /**
     * @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
     * Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
     * to any version. It is recommended to use this to lock implementation contracts that are designed to be called
     * through proxies.
     */
    function _disableInitializers() internal virtual {
        require(!_initializing, "Initializable: contract is initializing");
        if (_initialized < type(uint8).max) {
            _initialized = type(uint8).max;
            emit Initialized(type(uint8).max);
        }
    }
}

File 3 of 36 : 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 {
    }

    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;
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[49] private __gap;
}

File 4 of 36 : 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 5 of 36 : MixinERC721Enumerable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import './MixinKeys.sol';
import './MixinLockCore.sol';
import './MixinErrors.sol';
import '@openzeppelin/contracts-upgradeable/utils/introspection/ERC165StorageUpgradeable.sol';


/**
 * @title Implements the ERC-721 Enumerable extension.
 */
contract MixinERC721Enumerable is
  ERC165StorageUpgradeable,
  MixinErrors,
  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)
  {
    if(_index >= _totalSupply) {
      revert 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 6 of 36 : MixinFunds.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import './MixinErrors.sol';
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.
 */
contract MixinFunds is MixinErrors
{
  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
  {
    if(
      _tokenAddress != address(0) 
      && 
      IERC20Upgradeable(_tokenAddress).totalSupply() < 0
    ) {
      revert 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 7 of 36 : MixinGrantKeys.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import './MixinKeys.sol';
import './MixinRoles.sol';
import './MixinErrors.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
  MixinErrors,
  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 
    returns (uint[] memory)
  {
    _lockIsUpToDate();
    if(!isKeyGranter(msg.sender) && !isLockManager(msg.sender)) {
      revert ONLY_LOCK_MANAGER_OR_KEY_GRANTER();
    }

    uint[] memory tokenIds = new uint[](_recipients.length);
    for(uint i = 0; i < _recipients.length; i++) {
      // an event is triggered
      tokenIds[i] = _createNewKey(
        _recipients[i],
        _keyManagers[i],  
        _expirationTimestamps[i]
      ); 

      if(address(onKeyGrantHook) != address(0)) {
        onKeyGrantHook.onKeyGranted(
          tokenIds[i],
          msg.sender, 
          _recipients[i],
          _keyManagers[i],  
          _expirationTimestamps[i]
        );
      }
    }
    return tokenIds;
  }
  
  /**
   * Allows the Lock owner or key granter to extend an existing keys with no charge. This is the "renewal" equivalent of `grantKeys`.
   * @param _tokenId The id of the token to extend
   * @param _duration The duration in secondes to add ot the key
   * @dev set `_duration` to 0 to use the default duration of the lock
   */
  function grantKeyExtension(uint _tokenId, uint _duration) external {
    _lockIsUpToDate();
    _isKey(_tokenId);
    if(!isKeyGranter(msg.sender) && !isLockManager(msg.sender)) {
      revert ONLY_LOCK_MANAGER_OR_KEY_GRANTER();
    }
    _extendKey(_tokenId, _duration);
  }

  uint256[1000] private __safe_upgrade_gap;
}

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

import './MixinLockCore.sol';
import './MixinErrors.sol';

/**
 * @title Mixin for managing `Key` data, as well as the * Approval related functions needed to meet the ERC721
 * standard.
 * @dev `Mixins` are a design pattern seen in the 0x contracts.  It simply
 * separates logically groupings of code to ease readability.
 */
contract MixinKeys is
  MixinErrors,
  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 newExpiration,
    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
  );

  event LockConfig(
    uint expirationDuration,
    uint maxNumberOfKeys,
    uint maxKeysPerAcccount
  );

  // 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) internal 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
  {
    address realKeyOwner = keyManagerOf[_tokenId] == address(0) ? _ownerOf[_tokenId] : keyManagerOf[_tokenId];
    if(
      !_isKeyManager(_tokenId, msg.sender)
      && approved[_tokenId] != msg.sender
      && !isApprovedForAll(realKeyOwner, msg.sender)
    ) {
      revert ONLY_KEY_MANAGER_OR_APPROVED();
    }
  }

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

  /**
   * Check if a key actually exists
   * @dev This is a modifier
   */
  function _isKey(
    uint _tokenId
  ) 
  internal
  view 
  {
    if(_keys[_tokenId].tokenId == 0) {
      revert 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.
    * No data migration needed for v10 > v11
    */
  function migrate(bytes calldata) virtual public {
    schemaVersion = publicLockVersion();
  }

  /**
   * 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` >= `totalKeys(_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 `totalKeys(_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)
  {
      if(_index >= totalKeys(_keyOwner)) {
        revert OUT_OF_RANGE();
      }
      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) {

    if(_recipient == address(0)) { 
        revert INVALID_ADDRESS();
    }
    
    // We increment the tokenId counter
    _totalSupply++;
    tokenId = _totalSupply;

    // create the key
    _keys[tokenId] = Key(tokenId, expirationTimestamp);
    
    // increase total number of unique owners
    if(totalKeys(_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,
    uint _duration
  ) internal 
    returns (
      uint newTimestamp
    )
  {
    uint expirationTimestamp = _keys[_tokenId].expirationTimestamp;

    // prevent extending a valid non-expiring key
    if(expirationTimestamp == type(uint).max){
      revert CANT_EXTEND_NON_EXPIRING_KEY();
    }
    
    // if non-expiring but not valid then extend
    uint duration = _duration == 0 ? expirationDuration : _duration;
    if(duration == type(uint).max) {
      newTimestamp = type(uint).max;
    } else {
      if (expirationTimestamp > block.timestamp) {
        // extends a valid key  
        newTimestamp = expirationTimestamp + duration;
      } else {
        // renew an expired or cancelled key
        newTimestamp = block.timestamp + duration;
      }
    }

    _keys[_tokenId].expirationTimestamp = newTimestamp;

    emit KeyExtended(_tokenId, newTimestamp);

    // call the hook
    if(address(onKeyExtendHook) != address(0)) {
      onKeyExtendHook.onKeyExtend(
        _tokenId, 
        msg.sender,
        newTimestamp,
        expirationTimestamp
      );
    }
  } 

  /**
   * 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 = totalKeys(_recipient);
    
    // make sure address does not have more keys than allowed
    if(length >= _maxKeysPerAddress) {
      revert MAX_KEYS_REACHED();
    }

    // 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
    if(
      _amount > keyExpirationTimestampFor(_tokenIdFrom) - block.timestamp
    ) {
      revert 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 = totalKeys(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(totalKeys(previousOwner) == 1 ) {
      numberOfOwners--;
    }
    // update balance
    _balances[previousOwner] -= 1;
  }

  /**
   * Internal logic to 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 {
    
    // expire the key
    _keys[_tokenId].expirationTimestamp = block.timestamp;

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

  /**
   * @return The number of keys owned by `_keyOwner` (expired or not)
  */
  function totalKeys(
    address _keyOwner
  )
    public
    view
    returns (uint)
  {
    if(_keyOwner == address(0)) { 
      revert INVALID_ADDRESS();
    }

    return _balances[_keyOwner];
  }

  /**
   * In the specific case of a Lock, `balanceOf` returns only the tokens with a valid expiration timerange
   * @return balance The number of valid keys owned by `_keyOwner`
  */
  function balanceOf(
    address _keyOwner
  )
    public
    view
    returns (uint balance)
  {
    uint length = totalKeys(_keyOwner);
    for (uint i = 0; i < length; i++) {
      if(isValidKey(tokenOfOwnerByIndex(_keyOwner, i))) {
        balance++;
      }
    }
  }

  /**
   * 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)
  { 
    // `balanceOf` returns only valid keys
    isValid = balanceOf(_keyOwner) > 0;

    // 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 setting the manager 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);
    if(
      // is already key manager
      !_isKeyManager(_tokenId, msg.sender) 
      // is lock manager
      && !isLockManager(msg.sender)
    ) {
      revert 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);
    if(msg.sender == _approved) {
      revert CANNOT_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 explicitly set as key manager, or if the 
   * address is the owner but no km is set.
   * identified by _tokenId
   */
  function _isKeyManager(
    uint _tokenId,
    address _keyManager
  ) internal view
    returns (bool)
  {
    if(
      // is explicitely a key manager
      keyManagerOf[_tokenId] == _keyManager 
      ||
      (
        // is owner and no key manager is set
        ownerOf(_tokenId) == _keyManager)
        && keyManagerOf[_tokenId] == address(0) 
      ) {
      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, 
      _keys[_tokenId].expirationTimestamp,
      _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);
    }
  }

  /**
   * Update the main key properties for the entire lock: 
   * - default duration of each key
   * - the maximum number of keys the lock can edit
   * - the maximum number of keys a single address can hold
   * @notice keys previously bought are unaffected by this changes in expiration duration (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
   * @param _maxKeysPerAcccount the maximum amount of key a single user can own
   * @param _maxNumberOfKeys uint the maximum number of keys
   * @dev _maxNumberOfKeys Can't be smaller than the existing supply 
   */
   function updateLockConfig(
    uint _newExpirationDuration,
    uint _maxNumberOfKeys,
    uint _maxKeysPerAcccount
  ) external {
     _onlyLockManager();
     if(_maxKeysPerAcccount == 0) {
       revert NULL_VALUE();
     }
     if (_maxNumberOfKeys < _totalSupply) {
       revert CANT_BE_SMALLER_THAN_SUPPLY();
     }
     _maxKeysPerAddress = _maxKeysPerAcccount;
     expirationDuration = _newExpirationDuration;
     maxNumberOfKeys = _maxNumberOfKeys;

     emit LockConfig(
      _newExpirationDuration, 
      _maxNumberOfKeys,
      _maxKeysPerAddress
      );
  }
  

  /**
   * @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 9 of 36 : MixinLockCore.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import '@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol';
import './MixinDisable.sol';
import './MixinRoles.sol';
import './MixinErrors.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/ILockKeyGrantHook.sol';
import '../interfaces/hooks/ILockTokenURIHook.sol';
import '../interfaces/hooks/ILockKeyTransferHook.sol';
import '../interfaces/hooks/ILockKeyExtendHook.sol';

/**
 * @title Mixin for core lock data and functions.
 * @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 recipient,
    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
  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
  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;

  // DEPREC: this is not used anymore (kept as private var for storage layout compat)
  address payable private beneficiary;

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

  // lock hooks
  ILockKeyPurchaseHook public onKeyPurchaseHook;
  ILockKeyCancelHook public onKeyCancelHook;
  ILockValidKeyHook public onValidKeyHook;
  ILockTokenURIHook public onTokenURIHook;

  // use to check data version (added to v10)
  uint public schemaVersion;

  // keep track of how many key a single address can use (added to v10)
  uint internal _maxKeysPerAddress;

  // one more hook (added to v11)
  ILockKeyTransferHook public onKeyTransferHook;

  // two more hooks (added to v12)
  ILockKeyExtendHook public onKeyExtendHook;
  ILockKeyGrantHook public onKeyGrantHook;

  // modifier to check if data has been upgraded
  function _lockIsUpToDate() internal view {
    if(schemaVersion != publicLockVersion()) {
      revert MIGRATION_REQUIRED();
    }
  }
  
  function _initializeMixinLockCore(
    address payable,
    uint _expirationDuration,
    uint _keyPrice,
    uint _maxNumberOfKeys
  ) internal
  {
    unlockProtocol = IUnlock(msg.sender); // Make sure we link back to Unlock's smart contract.
    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 12;
  }

  /**
   * @dev Called by owner to withdraw all ETH funds from the lock
   * @param _recipient specifies the address to send ETH to.
   * @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,
    address payable _recipient,
    uint _amount
  ) external
  {
    _onlyLockManager();

    // 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)
    {
      if(balance <= 0) {
        revert NOT_ENOUGH_FUNDS();
      }
      amount = balance;
    }
    else
    {
      amount = _amount;
    }

    emit Withdrawal(msg.sender, _tokenAddress, _recipient, amount);
    // Security: re-entrancy not a risk as this is the last line of an external function
    _transfer(_tokenAddress, _recipient, 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);
  }

  /**
   * @notice Allows a lock manager to add or remove an event hook
   */
  function setEventHooks(
    address _onKeyPurchaseHook,
    address _onKeyCancelHook,
    address _onValidKeyHook,
    address _onTokenURIHook,
    address _onKeyTransferHook,
    address _onKeyExtendHook,
    address _onKeyGrantHook
  ) external
  {
    _onlyLockManager();

    if(_onKeyPurchaseHook != address(0) && !_onKeyPurchaseHook.isContract()) { revert INVALID_HOOK(0); }
    if(_onKeyCancelHook != address(0) && !_onKeyCancelHook.isContract()) { revert INVALID_HOOK(1); }
    if(_onValidKeyHook != address(0) && !_onValidKeyHook.isContract()) { revert INVALID_HOOK(2); }
    if(_onTokenURIHook != address(0) && !_onTokenURIHook.isContract()) { revert INVALID_HOOK(3); }
    if(_onKeyTransferHook != address(0) && !_onKeyTransferHook.isContract()) { revert INVALID_HOOK(4); }
    if(_onKeyExtendHook != address(0) && !_onKeyExtendHook.isContract()) { revert INVALID_HOOK(5); }
    if(_onKeyGrantHook != address(0) && !_onKeyGrantHook.isContract()) { revert INVALID_HOOK(6); }
    
    onKeyPurchaseHook = ILockKeyPurchaseHook(_onKeyPurchaseHook);
    onKeyCancelHook = ILockKeyCancelHook(_onKeyCancelHook);
    onTokenURIHook = ILockTokenURIHook(_onTokenURIHook);
    onValidKeyHook = ILockValidKeyHook(_onValidKeyHook);
    onKeyTransferHook = ILockKeyTransferHook(_onKeyTransferHook);
    onKeyExtendHook = ILockKeyExtendHook(_onKeyExtendHook);
    onKeyGrantHook = ILockKeyGrantHook(_onKeyGrantHook);
  }

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

  // decreased from 1000 to 998 when adding `schemaVersion` and `maxKeysPerAddress` in v10 
  // decreased from 998 to 997 when adding `onKeyTransferHook` in v11
  // decreased from 997 to 996 when adding `onKeyExtendHook` in v12
  // decreased from 996 to 995 when adding `onKeyGrantHook` in v12
  uint256[995] private __safe_upgrade_gap;
}

File 10 of 36 : 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.
 * @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 LockMetadata(
    string name, 
    string symbol, 
    string baseTokenURI
  );

  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 
   * @param _lockName a descriptive name for this Lock.
   * @param _lockSymbol a Symbol for this Lock (default to KEY).
   * @param _baseTokenURI the baseTokenURI for this Lock
   */
  function setLockMetadata(
    string calldata _lockName,
    string calldata _lockSymbol,
    string calldata _baseTokenURI
  ) public {
    _onlyLockManager();

    name = _lockName;
    lockSymbol = _lockSymbol;
    baseTokenURI = _baseTokenURI;

    emit LockMetadata(name, lockSymbol, baseTokenURI);
  }

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

  /**  @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 11 of 36 : MixinPurchase.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

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

/**
 * @title Mixin for the purchase-related functions.
 * @dev `Mixins` are a design pattern seen in the 0x contracts.  It simply
 * separates logically groupings of code to ease readability.
 */
contract MixinPurchase is
  MixinErrors,
  MixinFunds,
  MixinDisable,
  MixinLockCore,
  MixinKeys
{
  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) internal _originalPrices;
  
  // Keep track of duration when purchased
  mapping(uint256 => uint256) internal _originalDurations;
  
  // keep track of token pricing when purchased
  mapping(uint256 => address) internal _originalTokens;

  mapping(address => uint) public referrerFees;

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

  /**
  * Set a specific percentage of the keyPrice to be sent to the referrer while purchasing, 
  * extending or renewing a key. 
  * @param _referrer the address of the referrer. If set to the 0x address, any referrer will receive the fee.
  * @param _feeBasisPoint the percentage of the price to be used for this 
  * specific referrer (in basis points)
  * @dev To send a fixed percentage of the key price to all referrers, sett a percentage to `address(0)`
  */
  function setReferrerFee(address _referrer, uint _feeBasisPoint) public {
    _onlyLockManager();
    referrerFees[_referrer] = _feeBasisPoint;
  }

  /** 
  @dev internal function to execute the payments to referrers if any is set
  */
  function _payReferrer (address _referrer) internal {
    // get default value
    uint basisPointsToPay = referrerFees[address(0)];

    // get value for the referrer
    if(referrerFees[_referrer] != 0) {
      basisPointsToPay = referrerFees[_referrer];
    }
    
    // pay the referrer if necessary
    if (basisPointsToPay != 0) {
      _transfer(
        tokenAddress,
        payable(_referrer), 
        keyPrice * basisPointsToPay / BASIS_POINTS_DEN
      );
    }
  }

  /**
  * @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
    returns (uint[] memory)
  {
    _lockIsUpToDate();
    if(_totalSupply +  _recipients.length > maxNumberOfKeys) {
      revert LOCK_SOLD_OUT();
    }
    if(
      (_recipients.length != _referrers.length)
      ||
      (_recipients.length != _keyManagers.length)
      ) {
      revert INVALID_LENGTH();
    }

    uint totalPriceToPay;
    uint[] memory tokenIds = new uint[](_recipients.length);

    for (uint256 i = 0; i < _recipients.length; i++) {
      // check recipient address
      address _recipient = _recipients[i];

      // create a new key, check for a non-expiring key
      tokenIds[i] = _createNewKey(
        _recipient,
        _keyManagers[i],
        expirationDuration == type(uint).max ? type(uint).max : block.timestamp + expirationDuration
      );

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

      // store values at purchase time
      _originalPrices[tokenIds[i]] = inMemoryKeyPrice;
      _originalDurations[tokenIds[i]] = expirationDuration;
      _originalTokens[tokenIds[i]] = tokenAddress;


      if(tokenAddress != address(0) && _values[i] < inMemoryKeyPrice) {
        revert 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(
          tokenIds[i],
          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 if(msg.value < totalPriceToPay) {
      // We explicitly allow for greater amounts of ETH or tokens to allow 'donations'
      revert INSUFFICIENT_VALUE();
    }

    // refund gas
    _refundGas();

    // send what is due to referrers
    for (uint256 i = 0; i < _referrers.length; i++) { 
      _payReferrer(_referrers[i]);
    }

    return tokenIds;
  }

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

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

    // process in unlock
    _recordKeyPurchase(inMemoryKeyPrice, _referrer);

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

    // if params have changed, then update them
    if(_originalPrices[_tokenId] != inMemoryKeyPrice) {
      _originalPrices[_tokenId] = inMemoryKeyPrice;
    }
    if(_originalDurations[_tokenId] != expirationDuration) {
      _originalDurations[_tokenId] = expirationDuration;
    }
    if(_originalTokens[_tokenId] != tokenAddress) {
      _originalTokens[_tokenId] = tokenAddress;
    }

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

    // send what is due to referrer
    _payReferrer(_referrer);
  }

  /**
  * 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
    if(_originalDurations[_tokenId] == type(uint).max || tokenAddress == address(0)) {
      revert NON_RENEWABLE_LOCK();
    }

    // make sure duration and pricing havent changed  
    uint keyPrice = purchasePriceFor(ownerOf(_tokenId), _referrer, '');
    if(
      _originalPrices[_tokenId] != keyPrice
      ||
      _originalDurations[_tokenId] != expirationDuration
      || 
      _originalTokens[_tokenId] != tokenAddress
    ) {
      revert LOCK_HAS_CHANGED();
    }

    // make sure key is ready for renewal
    if(isValidKey(_tokenId)) {
      revert NOT_READY_FOR_RENEWAL();
    }

    // extend key duration
    _extendKey(_tokenId, 0);

    // 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();

    // send what is due to referrer
    _payReferrer(_referrer);
  }

  /**
   * @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);
        // send tokens to refun gas
        token.transfer(msg.sender, _gasRefundValue);
      } else {
        (bool success, ) = msg.sender.call{value: _gasRefundValue}("");
        if(!success) {
          revert GAS_REFUND_FAILED();
        }
      }
      emit GasRefunded(msg.sender, _gasRefundValue, tokenAddress);
    }
  }

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

File 12 of 36 : 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;
    _originalPrices[_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 13 of 36 : 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 './MixinErrors.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.
 * @dev `Mixins` are a design pattern seen in the 0x contracts.  It simply
 * separates logically groupings of code to ease readability.
 */

contract MixinTransfer is
  MixinErrors,
  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();
    if(maxNumberOfKeys <= _totalSupply) {
      revert LOCK_SOLD_OUT();
    }
    _onlyKeyManagerOrApproved(_tokenIdFrom);
    _isValidKey(_tokenIdFrom);
    if(transferFeeBasisPoints >= BASIS_POINTS_DEN) {
      revert KEY_TRANSFERS_DISABLED();
    }

    address keyOwner = _ownerOf[_tokenIdFrom];

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

    if(!_checkOnERC721Received(keyOwner, _to, tokenIdTo, '')) {
      revert NON_COMPLIANT_ERC721_RECEIVER();
    }
  }

  /** 
  * an ERC721-like function to transfer a token from one account to another. 
  * @param _from the owner of token to transfer
  * @param _recipient the address that will receive the token
  * @param _tokenId the id of the token
  * @dev Requirements: if the caller is not `from`, it must be approved to move this token by
  * either {approve} or {setApprovalForAll}. 
  * The key manager will be reset to address zero after the transfer
  */
  function transferFrom(
    address _from,
    address _recipient,
    uint _tokenId
  )
    public
  {
    _isValidKey(_tokenId);
    _onlyKeyManagerOrApproved(_tokenId);
    
    // reset key manager to address zero
    keyManagerOf[_tokenId] = address(0);

    _transferFrom(_from, _recipient, _tokenId);
  }

  /** 
  * Lending a key allows you to transfer the token while retaining the 
  * ownerships right by setting yourself as a key manager first. 
  * @param _from the owner of token to transfer
  * @param _recipient the address that will receive the token
  * @param _tokenId the id of the token
  * @notice This function can only called by 1) the key owner when no key manager is set or 2) the key manager.
  * After calling the function, the `_recipient` will be the new owner, and the sender of the tx
  * will become the key manager.
  */
  function lendKey(
    address _from,
    address _recipient,
    uint _tokenId
  )
    public
  {
    // make sure caller is either owner or key manager 
    if(!_isKeyManager(_tokenId, msg.sender)) {
      revert UNAUTHORIZED();
    }
    
    // transfer key ownership to lender
    _transferFrom(_from, _recipient, _tokenId);

    // set key owner as key manager
    keyManagerOf[_tokenId] = msg.sender;
  }

  /** 
  * Unlend is called when you have lent a key and want to claim its full ownership back. 
  * @param _recipient the address that will receive the token ownership
  * @param _tokenId the id of the token
  * @dev Only the key manager of the token can call this function
  */
  function unlendKey(
    address _recipient,
    uint _tokenId
  ) public {
    _isValidKey(_tokenId);

    if(msg.sender != keyManagerOf[_tokenId]) {
      revert UNAUTHORIZED();
    }
    _transferFrom(ownerOf(_tokenId), _recipient, _tokenId);
  }

  /**
   * This functions contains the logic to transfer a token
   * from an account to another
   */
  function _transferFrom(
    address _from,
    address _recipient,
    uint _tokenId
  ) private {

    _isValidKey(_tokenId);

    // incorrect _from field
    if (ownerOf(_tokenId) != _from) {
      revert UNAUTHORIZED();
    }

    if(transferFeeBasisPoints >= BASIS_POINTS_DEN) {
      revert KEY_TRANSFERS_DISABLED();
    }
    if(_recipient == address(0)) {
      revert INVALID_ADDRESS();
    }
    if(_from == _recipient) {
      revert 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(totalKeys(_recipient) == 0 ) {
      numberOfOwners++;
    }

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

    // clear any previous approvals
    _clearApproval(_tokenId);

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

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

    // fire hook if it exists
    if(address(onKeyTransferHook) != address(0)) {
      onKeyTransferHook.onKeyTransfer(
        address(this),
        _tokenId,
        msg.sender, // operator
        _from,
        _recipient,
        key.expirationTimestamp
      );
    }
  }

  /**
   * @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);
    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
  {
    if(_to == msg.sender) {
      revert CANNOT_APPROVE_SELF();
    }
    if(transferFeeBasisPoints >= BASIS_POINTS_DEN) {
      revert 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);
    if(!_checkOnERC721Received(_from, _to, _tokenId, _data)) {
      revert 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 14 of 36 : 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';
import './MixinErrors.sol';

contract MixinRoles is AccessControlUpgradeable, MixinErrors {

  // 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
  {
    if(!hasRole(LOCK_MANAGER_ROLE, msg.sender)) {
      revert 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 15 of 36 : MixinConvenienceOwnable.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import './MixinLockCore.sol';
import './MixinErrors.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 MixinErrors, 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();
    if(account == address(0)) {
      revert 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 16 of 36 : AddressUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Address.sol)

pragma solidity ^0.8.1;

/**
 * @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
     * ====
     *
     * [IMPORTANT]
     * ====
     * You shouldn't rely on `isContract` to protect against flash loan attacks!
     *
     * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
     * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
     * constructor.
     * ====
     */
    function isContract(address account) internal view returns (bool) {
        // This method relies on extcodesize/address.code.length, which returns 0
        // for contracts in construction, since the code is only stored at the end
        // of the constructor execution.

        return account.code.length > 0;
    }

    /**
     * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
     * `recipient`, forwarding all available gas and reverting on errors.
     *
     * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
     * of certain opcodes, possibly making contracts go over the 2300 gas limit
     * imposed by `transfer`, making them unable to receive funds via
     * `transfer`. {sendValue} removes this limitation.
     *
     * https://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
                /// @solidity memory-safe-assembly
                assembly {
                    let returndata_size := mload(returndata)
                    revert(add(32, returndata), returndata_size)
                }
            } else {
                revert(errorMessage);
            }
        }
    }
}

File 17 of 36 : 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 {
    }

    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;
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[50] private __gap;
}

File 18 of 36 : 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 19 of 36 : MixinErrors.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

/**
 * @title List of all error messages 
 * (replace string errors message to save on contract size)
 */
contract MixinErrors {
  
  // generic
  error OUT_OF_RANGE();
  error NULL_VALUE();
  error INVALID_ADDRESS();
  error INVALID_TOKEN();
  error INVALID_LENGTH();
  error UNAUTHORIZED();

  // erc 721
  error NON_COMPLIANT_ERC721_RECEIVER();

  // roles
  error ONLY_LOCK_MANAGER_OR_KEY_GRANTER();
  error ONLY_KEY_MANAGER_OR_APPROVED();
  error UNAUTHORIZED_KEY_MANAGER_UPDATE();
  error ONLY_LOCK_MANAGER();

  // single key status
  error KEY_NOT_VALID();
  error NO_SUCH_KEY();

  // single key operations
  error CANT_EXTEND_NON_EXPIRING_KEY();
  error NOT_ENOUGH_TIME();
  error NOT_ENOUGH_FUNDS();

  // migration & data schema
  error SCHEMA_VERSION_NOT_CORRECT();
  error MIGRATION_REQUIRED();

  // lock status/settings
  error OWNER_CANT_BE_ADDRESS_ZERO();
  error MAX_KEYS_REACHED();
  error KEY_TRANSFERS_DISABLED();
  error CANT_BE_SMALLER_THAN_SUPPLY();

  // transfers and approvals
  error TRANSFER_TO_SELF();
  error CANNOT_APPROVE_SELF();

  // keys management 
  error LOCK_SOLD_OUT();

  // purchase
  error INSUFFICIENT_ERC20_VALUE();
  error INSUFFICIENT_VALUE();

  // renewals
  error NON_RENEWABLE_LOCK();
  error LOCK_HAS_CHANGED();
  error NOT_READY_FOR_RENEWAL();

  // gas refund
  error GAS_REFUND_FAILED();

  // hooks
  // NB: `hookIndex` designed the index of hook address in the params of `setEventHooks`
  error INVALID_HOOK(uint8 hookIndex);

}

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


/**
 * @title The Unlock Interface
**/

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;

  /** 
  * Retrieve the contract address of the proxy admin that manages the locks
  * @return the address of the ProxyAdmin instance
  */ 
  function proxyAdminAddress() external view returns (address);

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

    /**
   * @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
    pure
    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
  * @param _version the number of the version of the template 
  * @return address of the lock templates
  */ 
  function publicLockImpls(uint16 _version) external view returns (address);
  
  /** 
  * Match version numbers with lock templates addresses 
  * @param _impl the address of the deployed template contract (PublicLock)
  * @return number of the version corresponding to this address
  */ 
  function publicLockVersions(address _impl) external view returns (uint16);

  /** 
  * Retrive the latest existing lock template version
  * @return the version number of the latest template (used to deploy contracts)
  */ 
  function publicLockLatestVersion() external view returns (uint16);

  /**
   * @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 36 : 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 36 : 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 tokenId the id of the purchased key
   * @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(
    uint tokenId,
    address from,
    address recipient,
    address referrer,
    bytes calldata data,
    uint minKeyPrice,
    uint pricePaid
  ) external;
}

File 23 of 36 : 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 `getHasValidKey` 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 36 : ILockKeyGrantHook.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.17 <0.9.0;


/**
 * @notice Functions to be implemented by a KeyGrantedHook.
 * @dev Lock hooks are configured by calling `setEventHooks` on the lock.
 */
interface ILockKeyGrantHook
{
  /**
   * @notice If the lock owner has registered an implementer then this hook
   * is called with every key granted.
   * @param tokenId the id of the granted key
   * @param from the msg.sender granting the key
   * @param recipient the account which will be granted a key
   * @param keyManager an additional keyManager for the key
   * @param expiration the expiration timestamp of the key
   * @dev the lock's address is the `msg.sender` when this function is called
   */
  function onKeyGranted(
    uint tokenId,
    address from,
    address recipient,
    address keyManager,
    uint expiration
  ) external;
}

File 25 of 36 : 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 26 of 36 : ILockKeyTransferHook.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.17 <0.9.0;


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

  /**
   * @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 tokenId the Id of the transferred key 
   * @param operator who initiated the transfer
   * @param from the previous owner of transferred key 
   * @param from the previous owner of transferred key 
   * @param to the new owner of the key
   * @param expirationTimestamp the key expiration timestamp (after transfer)
   */
  function onKeyTransfer(
    address lockAddress,
    uint tokenId,
    address operator,
    address from,
    address to,
    uint expirationTimestamp
  ) external;
}

File 27 of 36 : ILockKeyExtendHook.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.17 <0.9.0;


/**
 * @notice Functions to be implemented by a keyExtendHook.
 * @dev Lock hooks are configured by calling `setEventHooks` on the lock.
 */
interface ILockKeyExtendHook
{
  /**
   * @notice This hook every time a key is extended.
   * @param tokenId tje id of the key
   * @param from the msg.sender making the purchase
   * @param newTimestamp the new expiration timestamp after the key extension
   * @param prevTimestamp the expiration timestamp of the key before being extended
   */
  function onKeyExtend(
    uint tokenId,
    address from,
    uint newTimestamp,
    uint prevTimestamp
  ) external;
}

File 28 of 36 : AccessControlUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (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 {
    }

    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);
        _;
    }

    /**
     * @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 virtual override returns (bool) {
        return _roles[role].members[account];
    }

    /**
     * @dev Revert with a standard message if `_msgSender()` is missing `role`.
     * Overriding this function changes the behavior of the {onlyRole} modifier.
     *
     * Format of the revert message is described in {_checkRole}.
     *
     * _Available since v4.6._
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @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 virtual {
        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 virtual 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.
     *
     * May emit a {RoleGranted} event.
     */
    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.
     *
     * May emit a {RoleRevoked} event.
     */
    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`.
     *
     * May emit a {RoleRevoked} event.
     */
    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.
     *
     * May emit a {RoleGranted} event.
     *
     * [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.
     *
     * May emit a {RoleGranted} event.
     */
    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.
     *
     * May emit a {RoleRevoked} event.
     */
    function _revokeRole(bytes32 role, address account) internal virtual {
        if (hasRole(role, account)) {
            _roles[role].members[account] = false;
            emit RoleRevoked(role, account, _msgSender());
        }
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[49] private __gap;
}

File 29 of 36 : 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 30 of 36 : 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 {
    }

    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;
    }

    /**
     * @dev This empty reserved space is put in place to allow future versions to add new
     * variables without shifting down storage in the inheritance chain.
     * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
     */
    uint256[50] private __gap;
}

File 31 of 36 : StringsUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (utils/Strings.sol)

pragma solidity ^0.8.0;

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

    /**
     * @dev Converts a `uint256` to its ASCII `string` decimal representation.
     */
    function toString(uint256 value) internal pure returns (string memory) {
        // 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);
    }

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

File 32 of 36 : IERC20Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 standard as defined in the EIP.
 */
interface IERC20Upgradeable {
    /**
     * @dev Emitted when `value` tokens are moved from one account (`from`) to
     * another (`to`).
     *
     * Note that `value` may be zero.
     */
    event Transfer(address indexed from, address indexed to, uint256 value);

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

    /**
     * @dev Returns the amount of tokens in existence.
     */
    function totalSupply() external view returns (uint256);

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

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

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

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

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

File 33 of 36 : SafeERC20Upgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/utils/SafeERC20.sol)

pragma solidity ^0.8.0;

import "../IERC20Upgradeable.sol";
import "../extensions/draft-IERC20PermitUpgradeable.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));
        }
    }

    function safePermit(
        IERC20PermitUpgradeable token,
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) internal {
        uint256 nonceBefore = token.nonces(owner);
        token.permit(owner, spender, value, deadline, v, r, s);
        uint256 nonceAfter = token.nonces(owner);
        require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
    }

    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(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 34 of 36 : draft-IERC20PermitUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)

pragma solidity ^0.8.0;

/**
 * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
 * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
 *
 * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
 * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
 * need to send a transaction, and thus is not required to hold Ether at all.
 */
interface IERC20PermitUpgradeable {
    /**
     * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
     * given ``owner``'s signed approval.
     *
     * IMPORTANT: The same issues {IERC20-approve} has related to transaction
     * ordering also apply here.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `deadline` must be a timestamp in the future.
     * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
     * over the EIP712-formatted function arguments.
     * - the signature must use ``owner``'s current nonce (see {nonces}).
     *
     * For more information on the signature format, see the
     * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
     * section].
     */
    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    /**
     * @dev Returns the current nonce for `owner`. This value must be
     * included whenever a signature is generated for {permit}.
     *
     * Every successful call to {permit} increases ``owner``'s nonce by one. This
     * prevents a signature from being used multiple times.
     */
    function nonces(address owner) external view returns (uint256);

    /**
     * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
     */
    // solhint-disable-next-line func-name-mixedcase
    function DOMAIN_SEPARATOR() external view returns (bytes32);
}

File 35 of 36 : UnlockUtils.sol
// SPDX-License-Identifier: MIT
pragma solidity >=0.5.17 <=0.8.13;

// 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 36 of 36 : IERC721ReceiverUpgradeable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC721/IERC721Receiver.sol)

pragma solidity ^0.8.0;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
interface 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 `IERC721Receiver.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

[{"inputs":[],"name":"CANNOT_APPROVE_SELF","type":"error"},{"inputs":[],"name":"CANT_BE_SMALLER_THAN_SUPPLY","type":"error"},{"inputs":[],"name":"CANT_EXTEND_NON_EXPIRING_KEY","type":"error"},{"inputs":[],"name":"GAS_REFUND_FAILED","type":"error"},{"inputs":[],"name":"INSUFFICIENT_ERC20_VALUE","type":"error"},{"inputs":[],"name":"INSUFFICIENT_VALUE","type":"error"},{"inputs":[],"name":"INVALID_ADDRESS","type":"error"},{"inputs":[{"internalType":"uint8","name":"hookIndex","type":"uint8"}],"name":"INVALID_HOOK","type":"error"},{"inputs":[],"name":"INVALID_LENGTH","type":"error"},{"inputs":[],"name":"INVALID_TOKEN","type":"error"},{"inputs":[],"name":"KEY_NOT_VALID","type":"error"},{"inputs":[],"name":"KEY_TRANSFERS_DISABLED","type":"error"},{"inputs":[],"name":"LOCK_HAS_CHANGED","type":"error"},{"inputs":[],"name":"LOCK_SOLD_OUT","type":"error"},{"inputs":[],"name":"MAX_KEYS_REACHED","type":"error"},{"inputs":[],"name":"MIGRATION_REQUIRED","type":"error"},{"inputs":[],"name":"NON_COMPLIANT_ERC721_RECEIVER","type":"error"},{"inputs":[],"name":"NON_RENEWABLE_LOCK","type":"error"},{"inputs":[],"name":"NOT_ENOUGH_FUNDS","type":"error"},{"inputs":[],"name":"NOT_ENOUGH_TIME","type":"error"},{"inputs":[],"name":"NOT_READY_FOR_RENEWAL","type":"error"},{"inputs":[],"name":"NO_SUCH_KEY","type":"error"},{"inputs":[],"name":"NULL_VALUE","type":"error"},{"inputs":[],"name":"ONLY_KEY_MANAGER_OR_APPROVED","type":"error"},{"inputs":[],"name":"ONLY_LOCK_MANAGER","type":"error"},{"inputs":[],"name":"ONLY_LOCK_MANAGER_OR_KEY_GRANTER","type":"error"},{"inputs":[],"name":"OUT_OF_RANGE","type":"error"},{"inputs":[],"name":"OWNER_CANT_BE_ADDRESS_ZERO","type":"error"},{"inputs":[],"name":"SCHEMA_VERSION_NOT_CORRECT","type":"error"},{"inputs":[],"name":"TRANSFER_TO_SELF","type":"error"},{"inputs":[],"name":"UNAUTHORIZED","type":"error"},{"inputs":[],"name":"UNAUTHORIZED_KEY_MANAGER_UPDATE","type":"error"},{"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":"newExpiration","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":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","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":false,"internalType":"uint256","name":"expirationDuration","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"maxNumberOfKeys","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"maxKeysPerAcccount","type":"uint256"}],"name":"LockConfig","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":"name","type":"string"},{"indexed":false,"internalType":"string","name":"symbol","type":"string"},{"indexed":false,"internalType":"string","name":"baseTokenURI","type":"string"}],"name":"LockMetadata","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":"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":"recipient","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":"_keyOwner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"balance","type":"uint256"}],"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":"uint256","name":"_tokenId","type":"uint256"},{"internalType":"uint256","name":"_duration","type":"uint256"}],"name":"grantKeyExtension","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_recipients","type":"address[]"},{"internalType":"uint256[]","name":"_expirationTimestamps","type":"uint256[]"},{"internalType":"address[]","name":"_keyManagers","type":"address[]"}],"name":"grantKeys","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"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":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"lendKey","outputs":[],"stateMutability":"nonpayable","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":"","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":"onKeyExtendHook","outputs":[{"internalType":"contract ILockKeyExtendHook","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"onKeyGrantHook","outputs":[{"internalType":"contract ILockKeyGrantHook","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"onKeyPurchaseHook","outputs":[{"internalType":"contract ILockKeyPurchaseHook","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"onKeyTransferHook","outputs":[{"internalType":"contract ILockKeyTransferHook","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":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"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":[{"internalType":"address","name":"","type":"address"}],"name":"referrerFees","outputs":[{"internalType":"uint256","name":"","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":"address","name":"_onKeyPurchaseHook","type":"address"},{"internalType":"address","name":"_onKeyCancelHook","type":"address"},{"internalType":"address","name":"_onValidKeyHook","type":"address"},{"internalType":"address","name":"_onTokenURIHook","type":"address"},{"internalType":"address","name":"_onKeyTransferHook","type":"address"},{"internalType":"address","name":"_onKeyExtendHook","type":"address"},{"internalType":"address","name":"_onKeyGrantHook","type":"address"}],"name":"setEventHooks","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":"string","name":"_lockName","type":"string"},{"internalType":"string","name":"_lockSymbol","type":"string"},{"internalType":"string","name":"_baseTokenURI","type":"string"}],"name":"setLockMetadata","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"setOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_referrer","type":"address"},{"internalType":"uint256","name":"_feeBasisPoint","type":"uint256"}],"name":"setReferrerFee","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":[{"internalType":"address","name":"_keyOwner","type":"address"}],"name":"totalKeys","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"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":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"unlendKey","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unlockProtocol","outputs":[{"internalType":"contract IUnlock","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_keyPrice","type":"uint256"},{"internalType":"address","name":"_tokenAddress","type":"address"}],"name":"updateKeyPricing","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newExpirationDuration","type":"uint256"},{"internalType":"uint256","name":"_maxNumberOfKeys","type":"uint256"},{"internalType":"uint256","name":"_maxKeysPerAcccount","type":"uint256"}],"name":"updateLockConfig","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":"address payable","name":"_recipient","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

608060405234801561001057600080fd5b50615ffb80620000216000396000f3fe60806040526004361061044f5760003560e01c806370a0823111610236578063b11d7ec11161012f578063d52e4a10116100b1578063d52e4a1014610e33578063d547741f14610e49578063d813cc1914610e69578063d9caed1214610e7c578063debe2b0d14610e9c578063e985e9c514610ebc578063f0ba604014610edc578063f12c6b6e14610ef1578063f32e8b2414610f11578063f5766b3914610f26578063f8548e3614610f4657600080fd5b8063b11d7ec114610cc7578063b129694e14610ce7578063b1a3b25d14610d08578063b88d4fde14610d28578063c23135dd14610d48578063c87b56dd14610d76578063c907c3ec14610d96578063d1b8759b14610db7578063d1bbd49c14610dd7578063d250348514610df3578063d32bfb6c14610e1357600080fd5b806391d14854116101b857806391d1485414610b7b57806392ac98a514610b9b57806393fd184414610bbb57806395d89b4114610bd25780639d76ea5814610be7578063a217fddf14610c08578063a22cb46514610c1d578063a2e4cd2e14610c3d578063a375cb0514610c5d578063a98d362314610c74578063aae4b8f714610ca757600080fd5b806370a0823114610a1d57806374b6c10614610a3d57806374cac47d14610a545780637ec2a72414610a74578063812eecd414610a9557806381a3c94314610ab55780638505fe9514610ad55780638577a6d514610af55780638932a90d14610b155780638ca2fbad14610b3a5780638da5cb5b14610b5c57600080fd5b80632f54bf6e116103485780634e2ce6d3116102ca5780634e2ce6d3146108c85780634f6ccce7146108df57806352b0f638146108ff57806354b249fb1461091f578063558b71e914610950578063564aa99d1461097057806356e0d51f146109905780636207a8da146109a75780636352211e146109bd5780636d8ea5b4146109dd5780636eadde43146109fd57600080fd5b80632f54bf6e146107405780632f745c5914610770578063338189971461079057806336568abe146107b0578063389f07e8146107d057806339f46986146107f1578063407dc5891461081157806342842e0e1461083157806342966c68146108515780634cd38c1d146108715780634d025fed1461089157600080fd5b806318160ddd116103d157806318160ddd146105de578063183767da146105f4578063217751bc1461060b578063231005091461062c57806323b872dd1461064e578063248a9ca31461066e57806326e9ca071461069e578063282478df146106bf5780632af9162a146106df5780632d33dd5b146106ff5780632f2ff15d1461072057600080fd5b806301ffc9a71461045b578063068208cd1461049057806306fdde03146104b2578063081812fc146104d4578063095ea7b314610501578063097ba333146105215780630c2db8d11461054f5780630f15023b1461056f57806310e569731461059057806311a4c03a146105a757806313af4035146105be57600080fd5b3661045657005b600080fd5b34801561046757600080fd5b5061047b610476366004615101565b610f66565b60405190151581526020015b60405180910390f35b34801561049c57600080fd5b506104b06104ab36600461511e565b610f77565b005b3480156104be57600080fd5b506104c7610ff6565b60405161048791906151a2565b3480156104e057600080fd5b506104f46104ef3660046151b5565b611085565b60405161048791906151ce565b34801561050d57600080fd5b506104b061051c3660046151f7565b6110ad565b34801561052d57600080fd5b5061054161053c3660046152e6565b611148565b604051908152602001610487565b34801561055b57600080fd5b506104b061056a366004615347565b6111e9565b34801561057b57600080fd5b50610c83546104f4906001600160a01b031681565b34801561059c57600080fd5b50610541610c855481565b3480156105b357600080fd5b50610541610c845481565b3480156105ca57600080fd5b506104b06105d9366004615388565b61123e565b3480156105ea57600080fd5b50610c8754610541565b34801561060057600080fd5b506105416124075481565b34801561061757600080fd5b50610c8a546104f4906001600160a01b031681565b34801561063857600080fd5b50610541600080516020615f6683398151915281565b34801561065a57600080fd5b506104b0610669366004615347565b6112cf565b34801561067a57600080fd5b506105416106893660046151b5565b60009081526097602052604090206001015490565b3480156106aa57600080fd5b50610c8b546104f4906001600160a01b031681565b3480156106cb57600080fd5b506104b06106da36600461511e565b611309565b3480156106eb57600080fd5b506104b06106fa366004615388565b6113ae565b34801561070b57600080fd5b50610c89546104f4906001600160a01b031681565b34801561072c57600080fd5b506104b061073b3660046153a5565b611405565b34801561074c57600080fd5b5061047b61075b366004615388565b612bda546001600160a01b0390811691161490565b34801561077c57600080fd5b5061054161078b3660046151f7565b61142a565b6107a361079e36600461550d565b61147e565b60405161048791906155e4565b3480156107bc57600080fd5b506104b06107cb3660046153a5565b6119b7565b3480156107dc57600080fd5b50610c8f546104f4906001600160a01b031681565b3480156107fd57600080fd5b506104b061080c366004615628565b611a3a565b34801561081d57600080fd5b506104b061082c3660046151f7565b611a88565b34801561083d57600080fd5b506104b061084c366004615347565b611adc565b34801561085d57600080fd5b506104b061086c3660046151b5565b611af7565b34801561087d57600080fd5b506104b061088c366004615628565b611b48565b34801561089d57600080fd5b506104f46108ac3660046151b5565b611078602052600090815260409020546001600160a01b031681565b3480156108d457600080fd5b50610541610c8d5481565b3480156108eb57600080fd5b506105416108fa3660046151b5565b611b9d565b34801561090b57600080fd5b5061047b61091a366004615388565b611bc6565b34801561092b57600080fd5b5061054161093a3660046151b5565b600090815261107b602052604090206001015490565b34801561095c57600080fd5b506104b061096b366004615628565b611be0565b34801561097c57600080fd5b506104b061098b366004615388565b611c04565b34801561099c57600080fd5b506105416127f05481565b3480156109b357600080fd5b5061201e54610541565b3480156109c957600080fd5b506104f46109d83660046151b5565b611c5b565b3480156109e957600080fd5b5061047b6109f8366004615388565b611c77565b348015610a0957600080fd5b506104b0610a1836600461568b565b611d22565b348015610a2957600080fd5b50610541610a38366004615388565b611ea1565b348015610a4957600080fd5b50610541610c865481565b348015610a6057600080fd5b506104b0610a6f366004615710565b611ef3565b348015610a8057600080fd5b50610c8c546104f4906001600160a01b031681565b348015610aa157600080fd5b50610541610ab0366004615388565b612178565b348015610ac157600080fd5b506107a3610ad03660046157a6565b6121be565b348015610ae157600080fd5b506104b0610af03660046153a5565b61241e565b348015610b0157600080fd5b506104b0610b103660046151b5565b6125f5565b348015610b2157600080fd5b506104b0610b30366004615820565b5050600c610c8d55565b348015610b4657600080fd5b50610541600080516020615fa683398151915281565b348015610b6857600080fd5b50612bda546001600160a01b03166104f4565b348015610b8757600080fd5b5061047b610b963660046153a5565b612636565b348015610ba757600080fd5b50610541610bb63660046151b5565b612661565b348015610bc757600080fd5b506105416110775481565b348015610bde57600080fd5b506104c7612750565b348015610bf357600080fd5b506104b1546104f4906001600160a01b031681565b348015610c1457600080fd5b50610541600081565b348015610c2957600080fd5b506104b0610c3836600461586f565b61287b565b348015610c4957600080fd5b506104b0610c583660046153a5565b612936565b348015610c6957600080fd5b506105416127f15481565b348015610c8057600080fd5b5061047b610c8f3660046151b5565b600090815261107b6020526040902060010154421090565b348015610cb357600080fd5b5061047b610cc2366004615388565b6129c4565b348015610cd357600080fd5b506104b0610ce23660046153a5565b6129de565b348015610cf357600080fd5b50610c91546104f4906001600160a01b031681565b348015610d1457600080fd5b50610541610d23366004615628565b612a2c565b348015610d3457600080fd5b506104b0610d4336600461589d565b612a9f565b348015610d5457600080fd5b50610541610d63366004615388565b6120226020526000908152604090205481565b348015610d8257600080fd5b506104c7610d913660046151b5565b612ad3565b348015610da257600080fd5b50610c90546104f4906001600160a01b031681565b348015610dc357600080fd5b506104b0610dd2366004615908565b612d65565b348015610de357600080fd5b50604051600c8152602001610487565b348015610dff57600080fd5b506104b0610e0e366004615388565b612de0565b348015610e1f57600080fd5b506104b0610e2e3660046151b5565b612e37565b348015610e3f57600080fd5b50610c8e54610541565b348015610e5557600080fd5b506104b0610e643660046153a5565b612e69565b6104b0610e7736600461598f565b612e8e565b348015610e8857600080fd5b506104b0610e97366004615347565b61309e565b348015610ea857600080fd5b506104b0610eb73660046151f7565b6131d4565b348015610ec857600080fd5b5061047b610ed73660046159f8565b6131f9565b348015610ee857600080fd5b506104b0613228565b348015610efd57600080fd5b506104b0610f0c366004615a26565b61326d565b348015610f1d57600080fd5b506104b0613429565b348015610f3257600080fd5b506104b0610f413660046151b5565b613439565b348015610f5257600080fd5b5061047b610f61366004615a5b565b613447565b6000610f71826134a9565b92915050565b610f80836134b4565b610f89836134e3565b610f9283613515565b610f9b826134b4565b600083815261107b6020526040902060010154610fb9904290615a98565b811115610fd9576040516310e88eed60e31b815260040160405180910390fd5b610fe5838260006135cd565b610ff1828260016135cd565b505050565b611463805461100490615aaf565b80601f016020809104026020016040519081016040528092919081815260200182805461103090615aaf565b801561107d5780601f106110525761010080835404028352916020019161107d565b820191906000526020600020905b81548152906001019060200180831161106057829003601f168201915b505050505081565b6000611090826134b4565b50600090815261107960205260409020546001600160a01b031690565b6110b681613515565b6001600160a01b03821633036110df57604051637899146560e11b815260040160405180910390fd5b60008181526110796020908152604080832080546001600160a01b0319166001600160a01b03878116918217909255611076909352818420549151859492909116917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a45050565b610c89546000906001600160a01b0316156111dc57610c895460405163221c1fd160e01b81526001600160a01b039091169063221c1fd190611194903390889088908890600401615ae3565b602060405180830381865afa1580156111b1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111d59190615b17565b90506111e2565b50610c85545b9392505050565b6111f3813361369f565b6112105760405163075fd2b160e01b815260040160405180910390fd5b61121b838383613717565b60009081526110786020526040902080546001600160a01b031916331790555050565b61124661391a565b6001600160a01b03811661126d576040516330c6e09f60e21b815260040160405180910390fd5b612bda80546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0910160405180910390a15050565b6112d8816134e3565b6112e181613515565b60008181526110786020526040902080546001600160a01b0319169055610ff1838383613717565b61131161391a565b806000036113325760405163e03b033d60e01b815260040160405180910390fd5b610c875482101561135657604051631d00cd6b60e01b815260040160405180910390fd5b610c8e819055610c84839055610c8682905560408051848152602081018490529081018290527f9a09448a3f24d3a01ccc67103c7cddbeea820176a18182cc83d0bce585f26a5b9060600160405180910390a1505050565b6113b661391a565b6113ce600080516020615f6683398151915282612e69565b6040516001600160a01b038216907f766f6199fea59554b9ff688e413302b9200f85d74811c053c12d945ac6d8dd7a90600090a250565b60008281526097602052604090206001015461142081613951565b610ff1838361395b565b600061143583612178565b821061145457604051630471175760e11b815260040160405180910390fd5b506001600160a01b0391909116600090815261107c60209081526040808320938352929052205490565b60606114886139e1565b610c86548651610c875461149c9190615b30565b11156114bb576040516331af695160e01b815260040160405180910390fd5b845186511415806114ce57508351865114155b156114ec576040516376b3b52560e11b815260040160405180910390fd5b60008087516001600160401b0381111561150857611508615223565b604051908082528060200260200182016040528015611531578160200160208202803683370190505b50905060005b88518110156118ad57600089828151811061155457611554615b48565b602002602001015190506115a38189848151811061157457611574615b48565b6020026020010151600019610c84541461159b57610c84546115969042615b30565b613a05565b600019613a05565b8383815181106115b5576115b5615b48565b602002602001018181525050600061163f828b85815181106115d9576115d9615b48565b60200260200101518a8a878181106115f3576115f3615b48565b90506020028101906116059190615b5e565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061114892505050565b905061164b8186615b30565b94508061201f600086868151811061166557611665615b48565b6020026020010151815260200190815260200160002081905550610c8454612020600086868151811061169a5761169a615b48565b60200260200101518152602001908152602001600020819055506104b160009054906101000a90046001600160a01b031661202160008686815181106116e2576116e2615b48565b602090810291909101810151825281019190915260400160002080546001600160a01b0319166001600160a01b039283161790556104b15416158015906117415750808c848151811061173757611737615b48565b6020026020010151105b1561175f576040516330005fb160e21b815260040160405180910390fd5b611782818b858151811061177557611775615b48565b6020026020010151613ae0565b6104b1546000906001600160a01b0316156117b6578c84815181106117a9576117a9615b48565b60200260200101516117b8565b345b610c89549091506001600160a01b03161561189757610c895485516001600160a01b0390911690635e895f29908790879081106117f7576117f7615b48565b602002602001015133868f898151811061181357611813615b48565b60200260200101518e8e8b81811061182d5761182d615b48565b905060200281019061183f9190615b5e565b89896040518963ffffffff1660e01b8152600401611864989796959493929190615ba4565b600060405180830381600087803b15801561187e57600080fd5b505af1158015611892573d6000803e3d6000fd5b505050505b50505080806118a590615c0e565b915050611537565b506104b1546001600160a01b031615611941576104b1546040516323b872dd60e01b81526001600160a01b039091169081906323b872dd906118f790339030908890600401615c27565b6020604051808303816000875af1158015611916573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061193a9190615c4b565b5050611962565b81341015611962576040516306c3cddf60e41b815260040160405180910390fd5b61196a613be5565b60005b87518110156119aa5761199888828151811061198b5761198b615b48565b6020026020010151613d3f565b806119a281615c0e565b91505061196d565b5098975050505050505050565b6001600160a01b0381163314611a2c5760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b60648201526084015b60405180910390fd5b611a368282613dd8565b5050565b611a4261391a565b60408051838152602081018390527fd6867bc538320e67d7bdc35860c27c08486eb490b4fd9b820fff18fb28381d3c910160405180910390a16127f1919091556127f055565b611a91816134e3565b600081815261107860205260409020546001600160a01b03163314611ac95760405163075fd2b160e01b815260040160405180910390fd5b611a36611ad582611c5b565b8383613717565b610ff183838360405180602001604052806000815250612a9f565b611b00816134b4565b611b0981613515565b600081815261107660205260408082205490518392916001600160a01b031690600080516020615f86833981519152908390a4611b4581613e3f565b50565b611b506139e1565b611b59826134b4565b611b6233611bc6565b158015611b755750611b73336129c4565b155b15611b9357604051631798fedb60e01b815260040160405180910390fd5b610ff18282613e70565b6000610c87548210611bc257604051630471175760e11b815260040160405180910390fd5b5090565b6000610f71600080516020615f6683398151915283612636565b611be9826134b4565b611bf2826134e3565b611bfa61391a565b611a368282613fd1565b611c0c61391a565b611c24600080516020615f6683398151915282611405565b6040516001600160a01b038216907f684f8a71407db0c334454ebe9c9b288549317893a20b10dc779ec5c118dcd12190600090a250565b600090815261107660205260409020546001600160a01b031690565b600080611c8383611ea1565b610c8b54911091506001600160a01b031615611d1d57610c8b546040516370b6638f60e11b81523060048201526001600160a01b0384811660248301526000604483015283151560648301529091169063e16cc71e90608401602060405180830381865afa158015611cf9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f719190615c4b565b919050565b600054610100900460ff1615808015611d425750600054600160ff909116105b80611d635750611d51306140ef565b158015611d63575060005460ff166001145b611dc65760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401611a23565b6000805460ff191660011790558015611de9576000805461ff0019166101001790555b611df2866140fe565b611dfe8888878761412a565b611e08838361416b565b611e10614191565b611e1c6103e86127f055565b611e25886141a1565b612bda80546001600160a01b0319166001600160a01b038a16179055611e516380ac58cd60e01b614229565b8015611e97576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b600080611ead83612178565b905060005b81811015611eec57611ec7610c8f858361142a565b15611eda5782611ed681615c0e565b9350505b80611ee481615c0e565b915050611eb2565b5050919050565b611efb61391a565b6001600160a01b03871615801590611f225750611f20876001600160a01b03166140ef565b155b15611f4357604051636788e02b60e01b815260006004820152602401611a23565b6001600160a01b03861615801590611f6a5750611f68866001600160a01b03166140ef565b155b15611f8b57604051636788e02b60e01b815260016004820152602401611a23565b6001600160a01b03851615801590611fb25750611fb0856001600160a01b03166140ef565b155b15611fd357604051636788e02b60e01b815260026004820152602401611a23565b6001600160a01b03841615801590611ffa5750611ff8846001600160a01b03166140ef565b155b1561201b57604051636788e02b60e01b815260036004820152602401611a23565b6001600160a01b038316158015906120425750612040836001600160a01b03166140ef565b155b1561206257604051636788e02b60e01b8152600481810152602401611a23565b6001600160a01b038216158015906120895750612087826001600160a01b03166140ef565b155b156120aa57604051636788e02b60e01b815260056004820152602401611a23565b6001600160a01b038116158015906120d157506120cf816001600160a01b03166140ef565b155b156120f257604051636788e02b60e01b815260066004820152602401611a23565b610c8980546001600160a01b03199081166001600160a01b03998a1617909155610c8a8054821697891697909717909655610c8c8054871694881694909417909355610c8b8054861694871694909417909355610c8f80548516918616919091179055610c908054841692851692909217909155610c9180549092169216919091179055565b60006001600160a01b0382166121a157604051635963709b60e01b815260040160405180910390fd5b506001600160a01b0316600090815261107e602052604090205490565b60606121c86139e1565b6121d133611bc6565b1580156121e457506121e2336129c4565b155b1561220257604051631798fedb60e01b815260040160405180910390fd5b6000866001600160401b0381111561221c5761221c615223565b604051908082528060200260200182016040528015612245578160200160208202803683370190505b50905060005b87811015612412576122c289898381811061226857612268615b48565b905060200201602081019061227d9190615388565b86868481811061228f5761228f615b48565b90506020020160208101906122a49190615388565b8989858181106122b6576122b6615b48565b90506020020135613a05565b8282815181106122d4576122d4615b48565b6020908102919091010152610c91546001600160a01b03161561240057610c915482516001600160a01b03909116906348a254b89084908490811061231b5761231b615b48565b6020026020010151338c8c8681811061233657612336615b48565b905060200201602081019061234b9190615388565b89898781811061235d5761235d615b48565b90506020020160208101906123729190615388565b8c8c8881811061238457612384615b48565b6040516001600160e01b031960e08a901b16815260048101979097526001600160a01b039586166024880152938516604487015250921660648401526020020135608482015260a401600060405180830381600087803b1580156123e757600080fd5b505af11580156123fb573d6000803e3d6000fd5b505050505b8061240a81615c0e565b91505061224b565b50979650505050505050565b6124266139e1565b61242f826134b4565b60008281526120206020526040902054600019148061245857506104b1546001600160a01b0316155b1561247657604051636cd40e1160e11b815260040160405180910390fd5b600061249a61248484611c5b565b8360405180602001604052806000815250611148565b600084815261201f6020526040902054909150811415806124cd5750610c84546000848152612020602052604090205414155b806124f757506104b154600084815261202160205260409020546001600160a01b03908116911614155b156125155760405163986739e760e01b815260040160405180910390fd5b600083815261107b6020526040902060010154421015612548576040516360d8ec3360e11b815260040160405180910390fd5b612553836000613e70565b5061255e8183613ae0565b6104b1546001600160a01b0316806323b872dd61257a86611c5b565b30856040518463ffffffff1660e01b815260040161259a93929190615c27565b6020604051808303816000875af11580156125b9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125dd9190615c4b565b506125e6613be5565b6125ef83613d3f565b50505050565b6125fd61391a565b6040518181527f0496ed1e61eb69727f9659a8e859288db4758ffb1f744d1c1424634f90a257f49060200160405180910390a161240755565b60009182526097602090815260408084206001600160a01b0393909316845291905290205460ff1690565b600061266c826134e3565b600019610c845403612681575050610c855490565b600082815261107b602052604081206001015461269f904290615a98565b9050610c84546127f154826126b49190615b30565b106126c457610c855491506126e4565b610c845481610c85546126d79190615c68565b6126e19190615c87565b91505b6127f15415806127035750610c84546127f1546127019083615b30565b105b1561274a5760006127106127f054610c855461271f9190615c68565b6127299190615c87565b9050808311156127445761273d8184615a98565b9250611eec565b60009250505b50919050565b6060611464805461276090615aaf565b90506000036127ea57610c8360009054906101000a90046001600160a01b03166001600160a01b031663cec410526040518163ffffffff1660e01b8152600401600060405180830381865afa1580156127bd573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526127e59190810190615ca9565b905090565b61146480546127f890615aaf565b80601f016020809104026020016040519081016040528092919081815260200182805461282490615aaf565b80156128715780601f1061284657610100808354040283529160200191612871565b820191906000526020600020905b81548152906001019060200180831161285457829003601f168201915b5050505050905090565b336001600160a01b038316036128a457604051637899146560e11b815260040160405180910390fd5b61271061240754106128c9576040516323f21a3d60e21b815260040160405180910390fd5b33600081815261107a602090815260408083206001600160a01b03871680855290835292819020805460ff191686151590811790915590519081529192917f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a35050565b61293e61391a565b612947816142a8565b610c8580546104b18054928590556001600160a01b031983166001600160a01b0385811691821790925560408051848152602081018890529290941693820184905260608201529091907f3615065ccf48367ac483ac86701248e2e5ff55bdd9be845007d34a3b68d719d49060800160405180910390a150505050565b6000610f71600080516020615fa683398151915283612636565b6129e7826134b4565b6129f1823361369f565b158015612a045750612a02336129c4565b155b15612a225760405163866c2fa760e01b815260040160405180910390fd5b611a36828261433f565b6000612a37836134b4565b600083815261107b602052604090206001015442811015612a5c576000915050610f71565b600083600003612a7757612a704283615a98565b9050612a7a565b50825b6127106124075482612a8c9190615c68565b612a969190615c87565b92505050610f71565b612aaa8484846112cf565b612ab6848484846143cc565b6125ef576040516303f8ea1560e41b815260040160405180910390fd5b606080806000612ae230614483565b905060608515612afc57612af58661466e565b9250612b0f565b6040518060200160405280600081525092505b610c8c546001600160a01b031615612be657600086815261107b6020526040902060010154610c8c546001600160a01b031663988b93ad3033612b518b611c5b565b60405160e085901b6001600160e01b03191681526001600160a01b03938416600482015291831660248301529091166044820152606481018a90526084810184905260a401600060405180830381865afa158015612bb3573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612bdb9190810190615ca9565b979650505050505050565b6114658054612bf490615aaf565b9050600003612c9c57610c8360009054906101000a90046001600160a01b03166001600160a01b031663a998e9fb6040518163ffffffff1660e01b8152600401600060405180830381865afa158015612c51573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612c799190810190615ca9565b9350604051806040016040528060018152602001602f60f81b8152509050612d4f565b6114658054612caa90615aaf565b80601f0160208091040260200160405190810160405280929190818152602001828054612cd690615aaf565b8015612d235780601f10612cf857610100808354040283529160200191612d23565b820191906000526020600020905b815481529060010190602001808311612d0657829003601f168201915b505050505093506040518060200160405280600081525090506040518060200160405280600081525091505b612d5b8483838661479e565b9695505050505050565b612d6d61391a565b612d7a611463878761505b565b50612d88611464858561505b565b50612d96611465838361505b565b507f1e6d6a19e45ae156dcf4155bc83cf8f59e98d536000998f0e95f4cd330ecfb3e611463611464611465604051612dd093929190615dbe565b60405180910390a1505050505050565b612de861391a565b612e00600080516020615fa683398151915282611405565b6040516001600160a01b038216907f91d5c045d5bd98bf59a379b259ebca05b93bf79af1845fdf87e3172385d4c7f790600090a250565b612e40816134b4565b612e49816134e3565b612e5281613515565b6000612e5d82612661565b9050611a368282613fd1565b600082815260976020526040902060010154612e8481613951565b610ff18383613dd8565b612e966139e1565b612e9f846134b4565b612eaa846000613e70565b506000612ef6612eb986611c5b565b8585858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061114892505050565b9050612f028185613ae0565b6104b1546001600160a01b031615612fb65780861015612f35576040516330005fb160e21b815260040160405180910390fd5b6104b1546040516323b872dd60e01b81526001600160a01b039091169081906323b872dd90612f6c90339030908790600401615c27565b6020604051808303816000875af1158015612f8b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612faf9190615c4b565b5050612fd7565b80341015612fd7576040516306c3cddf60e41b815260040160405180910390fd5b600085815261201f6020526040902054811461300057600085815261201f602052604090208190555b610c8454600086815261202060205260409020541461302e57610c8454600086815261202060205260409020555b6104b154600086815261202160205260409020546001600160a01b03908116911614613085576104b15460008681526120216020526040902080546001600160a01b0319166001600160a01b039092169190911790555b61308d613be5565b61309684613d3f565b505050505050565b6130a661391a565b60006001600160a01b0384166130bd57504761312d565b6040516370a0823160e01b81526001600160a01b038516906370a08231906130e99030906004016151ce565b602060405180830381865afa158015613106573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061312a9190615b17565b90505b600082158061313b57508183115b156131685760008211613161576040516303e09bb960e31b815260040160405180910390fd5b508061316b565b50815b836001600160a01b0316856001600160a01b0316336001600160a01b03167f342e7ff505a8a0364cd0dc2ff195c315e43bce86b204846ecd36913e117b109e846040516131ba91815260200190565b60405180910390a46131cd8585836147d0565b5050505050565b6131dc61391a565b6001600160a01b0390911660009081526120226020526040902055565b6001600160a01b03918216600090815261107a6020908152604080832093909416825291909152205460ff1690565b613240600080516020615fa6833981519152336119b7565b60405133907f42885193b8178d25fca25a38e6fcc93918501e91be06d85e0c8afb3bad95238090600090a2565b6132756139e1565b610c8754610c86541161329b576040516331af695160e01b815260040160405180910390fd5b6132a482613515565b6132ad826134e3565b61271061240754106132d2576040516323f21a3d60e21b815260040160405180910390fd5b600082815261107660205260408120546001600160a01b031690804261330886600090815261107b602052604090206001015490565b6133129190615a98565b905060006133208686612a2c565b9050600061332e8287615b30565b90508281101561334c57859350613347878260006135cd565b6133a4565b6133568784612a2c565b91506133628284615a98565b600088815261107b6020526040808220426001909101555191955088917f59f2fe866dd27a1c2d34115520888c3150365cbc931aab97fa88c4b9ab40b7959190a25b60006133b589826115968842615b30565b905080896001600160a01b0316876001600160a01b0316600080516020615f8683398151915260405160405180910390a4613401868a83604051806020016040528060008152506143cc565b61341e576040516303f8ea1560e41b815260040160405180910390fd5b505050505050505050565b61343161391a565b600c610c8d55565b61344161391a565b61201e55565b6000613452846134e3565b6000612710834261347388600090815261107b602052604090206001015490565b61347d9190615a98565b6134879190615c68565b6134919190615c87565b905061349e84868361326d565b506001949350505050565b6000610f718261480c565b600081815261107b60205260408120549003611b45576040516378fe247360e01b815260040160405180910390fd5b600081815261107b60205260409020600101544210611b45576040516306cfa7d760e11b815260040160405180910390fd5b600081815261107860205260408120546001600160a01b03161561355157600082815261107860205260409020546001600160a01b031661356b565b600082815261107660205260409020546001600160a01b03165b9050613577823361369f565b15801561359c5750600082815261107960205260409020546001600160a01b03163314155b80156135af57506135ad81336131f9565b155b15611a365760405163e17c6d4560e01b815260040160405180910390fd5b6135d6836134b4565b600083815261107b602052604090206001015481156136235742811115613619576136018382615b30565b600085815261107b6020526040902060010155613641565b6136018342615b30565b61362d8382615a98565b600085815261107b60205260409020600101555b600084815261107b602090815260409182902060010154825190815290810185905283151581830152905185917f3c907806849e9204e0e26bb095dfe4b3071576c4323f766735c548211556d052919081900360600190a250505050565b600082815261107860205260408120546001600160a01b03838116911614806137025750816001600160a01b03166136d684611c5b565b6001600160a01b03161480156137025750600083815261107860205260409020546001600160a01b0316155b1561370f57506001610f71565b506000610f71565b613720816134e3565b826001600160a01b031661373382611c5b565b6001600160a01b03161461375a5760405163075fd2b160e01b815260040160405180910390fd5b612710612407541061377f576040516323f21a3d60e21b815260040160405180910390fd5b6001600160a01b0382166137a657604051635963709b60e01b815260040160405180910390fd5b816001600160a01b0316836001600160a01b0316036137d857604051633fbd1a4960e01b815260040160405180910390fd5b6137ee816137e7836000612a2c565b60006135cd565b600081815261107b6020526040902061380683612178565b60000361382457611077805490600061381e83615c0e565b91905055505b61382d82614817565b613837828461491d565b613840826149c2565b60008281526120206020908152604080832083905561201f9091528082208290555183916001600160a01b038087169290881691600080516020615f8683398151915291a4610c8f546001600160a01b0316156125ef57610c8f5460018201546040516375b37aef60e01b8152306004820152602481018590523360448201526001600160a01b038781166064830152868116608483015260a48201929092529116906375b37aef9060c401600060405180830381600087803b15801561390657600080fd5b505af1158015611e97573d6000803e3d6000fd5b613932600080516020615fa683398151915233612636565b61394f57604051632386d63160e21b815260040160405180910390fd5b565b611b4581336149ff565b6139658282612636565b611a365760008281526097602090815260408083206001600160a01b03851684529091529020805460ff1916600117905561399d3390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b610c8d54600c1461394f576040516302eae03b60e61b815260040160405180910390fd5b60006001600160a01b038416613a2e57604051635963709b60e01b815260040160405180910390fd5b610c878054906000613a3f83615c0e565b9091555050610c87546040805180820182528281526020808201868152600085815261107b9092529290209051815590516001909101559050613a8184612178565b600003613a9f576110778054906000613a9983615c0e565b91905055505b613aa9818561491d565b613ab3818461433f565b60405181906001600160a01b03861690600090600080516020615f86833981519152908290a49392505050565b610c83546001600160a01b03163b15613ba757610c835460405163939d9f1f60e01b8152600481018490526001600160a01b0383811660248301529091169063939d9f1f90620493e090604401600060405180830381600088803b158015613b4757600080fd5b5087f193505050508015613b59575060015b611a3657610c835460405130917f6b18946261693dfd6c760d986b28ad2238b5b0267f8e5b6bc40a2f998e2f20ac91613b9b916001600160a01b0316906151ce565b60405180910390a25050565b610c835460405130917f6b18946261693dfd6c760d986b28ad2238b5b0267f8e5b6bc40a2f998e2f20ac91613b9b916001600160a01b0316906151ce565b61201e541561394f576104b1546001600160a01b031615613c84576104b15461201e5460405163a9059cbb60e01b815233600482015260248101919091526001600160a01b0390911690819063a9059cbb906044016020604051808303816000875af1158015613c59573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613c7d9190615c4b565b5050613cf1565b61201e5460405160009133918381818185875af1925050503d8060008114613cc8576040519150601f19603f3d011682016040523d82523d6000602084013e613ccd565b606091505b5050905080613cef5760405163045ed26b60e11b815260040160405180910390fd5b505b61201e546104b154604080519283526001600160a01b03909116602083015233917f522a883b471164223f18b50f326da8671372b64b4792eac0e63d447e714c3e3b910160405180910390a2565b6120226020527fbed07da93ba22716a54f603f075ff3d6567a94916ac6d58738883fb4a4b47ea6546001600160a01b0382166000908152604090205415613d9c57506001600160a01b038116600090815261202260205260409020545b8015611a36576104b154610c8554611a36916001600160a01b031690849061271090613dc9908690615c68565b613dd39190615c87565b6147d0565b613de28282612636565b15611a365760008281526097602090815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b600090815261107b6020908152604080832042600190910155611076909152902080546001600160a01b0319169055565b600082815261107b60205260408120600190810154908101613ea557604051630fed19c160e11b815260040160405180910390fd5b60008315613eb35783613eb8565b610c84545b90506000198103613ecd576000199250613ef3565b42821115613ee657613edf8183615b30565b9250613ef3565b613ef08142615b30565b92505b600085815261107b6020526040908190206001018490555185907f3ca112768ff7861e008ace1c11570c52e404c043e585545b5957a1e20961dde390613f3c9086815260200190565b60405180910390a2610c90546001600160a01b031615613fc957610c90546040516202d72d60e71b81526004810187905233602482015260448101859052606481018490526001600160a01b039091169063016b968090608401600060405180830381600087803b158015613fb057600080fd5b505af1158015613fc4573d6000803e3d6000fd5b505050505b505092915050565b6000613fdc83611c5b565b9050613fe783613e3f565b336001600160a01b0316816001600160a01b0316847f0a7068a9989857441c039a14a42b67ed71dd1fcfe5a9b17cc87b252e47bce5288560405161402d91815260200190565b60405180910390a48115614053576104b154614053906001600160a01b031682846147d0565b60008381526120206020908152604080832083905561201f909152812055610c8a546001600160a01b031615610ff157610c8a5460405163b499b6c560e01b81526001600160a01b039091169063b499b6c5906140b890339085908790600401615c27565b600060405180830381600087803b1580156140d257600080fd5b505af11580156140e6573d6000803e3d6000fd5b50505050505050565b6001600160a01b03163b151590565b614107816142a8565b6104b180546001600160a01b0319166001600160a01b0392909216919091179055565b610c8380546001600160a01b03191633179055610c84839055610c85829055610c86819055614157600c90565b61ffff16610c8d5550506001610c8e555050565b614173614a63565b614180611463838361505b565b50611a36635b5e139f60e01b614229565b61394f63780e9d6360e01b614229565b6141b9600080516020615fa683398151915280614ace565b6141df600080516020615f66833981519152600080516020615fa6833981519152614ace565b6141e8816129c4565b61420457614204600080516020615fa683398151915282614b19565b61420d81611bc6565b611b4557611b45600080516020615f6683398151915282614b19565b6001600160e01b031980821690036142835760405162461bcd60e51b815260206004820152601c60248201527f4552433136353a20696e76616c696420696e74657266616365206964000000006044820152606401611a23565b6001600160e01b0319166000908152606560205260409020805460ff19166001179055565b6001600160a01b0381161580159061432157506000816001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156142fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061431f9190615b17565b105b15611b455760405163684cae7960e11b815260040160405180910390fd5b600082815261107860205260409020546001600160a01b03828116911614611a365760008281526110786020526040902080546001600160a01b0319166001600160a01b038316179055614392826149c2565b6040516001600160a01b0382169083907f9d2895c45a420624de863a2f437b022d879f457bf7a829044055a10c5a6fd5e390600090a35050565b60006143e0846001600160a01b03166140ef565b6143ec5750600161447b565b604051630a85bd0160e11b81526000906001600160a01b0386169063150b7a02906144219033908a9089908990600401615df7565b6020604051808303816000875af1158015614440573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906144649190615e2a565b6001600160e01b031916630a85bd0160e11b149150505b949350505050565b604080518082018252601081526f181899199a1a9b1b9c1cb0b131b232b360811b60208201528151602a80825260608281019094526001600160a01b0385169291600091602082018180368337019050509050600360fc1b816000815181106144ee576144ee615b48565b60200101906001600160f81b031916908160001a905350600f60fb1b8160018151811061451d5761451d615b48565b60200101906001600160f81b031916908160001a90535060005b6014811015614665578260048561454f84600c615b30565b6020811061455f5761455f615b48565b1a60f81b6001600160f81b031916901c60f81c60ff168151811061458557614585615b48565b01602001516001600160f81b031916826145a0836002615c68565b6145ab906002615b30565b815181106145bb576145bb615b48565b60200101906001600160f81b031916908160001a90535082846145df83600c615b30565b602081106145ef576145ef615b48565b825191901a600f1690811061460657614606615b48565b01602001516001600160f81b03191682614621836002615c68565b61462c906003615b30565b8151811061463c5761463c615b48565b60200101906001600160f81b031916908160001a9053508061465d81615c0e565b915050614537565b50949350505050565b60608160008190036146995750506040805180820190915260018152600360fc1b6020820152919050565b8260005b81156146c357806146ad81615c0e565b91506146bc9050600a83615c87565b915061469d565b6000816001600160401b038111156146dd576146dd615223565b6040519080825280601f01601f191660200182016040528015614707576020820181803683370190505b509050815b84156147945761471d600182615a98565b9050600061472c600a87615c87565b61473790600a615c68565b6147419087615a98565b61474c906030615e47565b905060008160f81b90508084848151811061476957614769615b48565b60200101906001600160f81b031916908160001a90535061478b600a88615c87565b9650505061470c565b5095945050505050565b6060848484846040516020016147b79493929190615e6c565b6040516020818303038152906040529050949350505050565b8015610ff1576001600160a01b0383166147f757610ff16001600160a01b03831682614b23565b826125ef6001600160a01b0382168484614c39565b6000610f7182614c8b565b600081815261107660205260408120546001600160a01b031690600161483c83612178565b6148469190615a98565b600084815261107d602052604090205490915080821461489c576001600160a01b038316600090815261107c60209081526040808320858452825280832054848452818420819055835261107d90915290208190555b6001600160a01b038316600090815261107c602090815260408083208584529091528120556148ca83612178565b6001036148e85761107780549060006148e283615ec3565b91905055505b6001600160a01b038316600090815261107e60205260408120805460019290614912908490615a98565b909155505050505050565b600061492882612178565b9050610c8e54811061494d57604051630bf6c32360e11b815260040160405180910390fd5b600083815261107d602090815260408083208490556001600160a01b03851680845261107c83528184208585528352818420879055868452611076835281842080546001600160a01b03191682179055835261107e90915281208054600192906149b8908490615b30565b9091555050505050565b600081815261107960205260409020546001600160a01b031615611b455760009081526110796020526040902080546001600160a01b0319169055565b614a098282612636565b611a3657614a21816001600160a01b03166014614cb0565b614a2c836020614cb0565b604051602001614a3d929190615eda565b60408051601f198184030181529082905262461bcd60e51b8252611a23916004016151a2565b600054610100900460ff1661394f5760405162461bcd60e51b815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201526a6e697469616c697a696e6760a81b6064820152608401611a23565b600082815260976020526040808220600101805490849055905190918391839186917fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff9190a4505050565b611a36828261395b565b80471015614b735760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e63650000006044820152606401611a23565b6000826001600160a01b03168260405160006040518083038185875af1925050503d8060008114614bc0576040519150601f19603f3d011682016040523d82523d6000602084013e614bc5565b606091505b5050905080610ff15760405162461bcd60e51b815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c20726044820152791958da5c1a595b9d081b585e481a185d99481c995d995c9d195960321b6064820152608401611a23565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b179052610ff1908490614e4b565b60006001600160e01b03198216637965db0b60e01b1480610f715750610f7182614f1d565b60606000614cbf836002615c68565b614cca906002615b30565b6001600160401b03811115614ce157614ce1615223565b6040519080825280601f01601f191660200182016040528015614d0b576020820181803683370190505b509050600360fc1b81600081518110614d2657614d26615b48565b60200101906001600160f81b031916908160001a905350600f60fb1b81600181518110614d5557614d55615b48565b60200101906001600160f81b031916908160001a9053506000614d79846002615c68565b614d84906001615b30565b90505b6001811115614dfc576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110614db857614db8615b48565b1a60f81b828281518110614dce57614dce615b48565b60200101906001600160f81b031916908160001a90535060049490941c93614df581615ec3565b9050614d87565b5083156111e25760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401611a23565b6000614ea0826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316614f599092919063ffffffff16565b805190915015610ff15780806020019051810190614ebe9190615c4b565b610ff15760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401611a23565b60006301ffc9a760e01b6001600160e01b031983161480610f715750506001600160e01b03191660009081526065602052604090205460ff1690565b606061447b848460008585614f6d856140ef565b614fb95760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401611a23565b600080866001600160a01b03168587604051614fd59190615f49565b60006040518083038185875af1925050503d8060008114615012576040519150601f19603f3d011682016040523d82523d6000602084013e615017565b606091505b5091509150612bdb828286606083156150315750816111e2565b8251156150415782518084602001fd5b8160405162461bcd60e51b8152600401611a2391906151a2565b82805461506790615aaf565b90600052602060002090601f01602090048101928261508957600085556150cf565b82601f106150a25782800160ff198235161785556150cf565b828001600101855582156150cf579182015b828111156150cf5782358255916020019190600101906150b4565b50611bc29291505b80821115611bc257600081556001016150d7565b6001600160e01b031981168114611b4557600080fd5b60006020828403121561511357600080fd5b81356111e2816150eb565b60008060006060848603121561513357600080fd5b505081359360208301359350604090920135919050565b60005b8381101561516557818101518382015260200161514d565b838111156125ef5750506000910152565b6000815180845261518e81602086016020860161514a565b601f01601f19169290920160200192915050565b6020815260006111e26020830184615176565b6000602082840312156151c757600080fd5b5035919050565b6001600160a01b0391909116815260200190565b6001600160a01b0381168114611b4557600080fd5b6000806040838503121561520a57600080fd5b8235615215816151e2565b946020939093013593505050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b038111828210171561526157615261615223565b604052919050565b60006001600160401b0382111561528257615282615223565b50601f01601f191660200190565b600082601f8301126152a157600080fd5b81356152b46152af82615269565b615239565b8181528460208386010111156152c957600080fd5b816020850160208301376000918101602001919091529392505050565b6000806000606084860312156152fb57600080fd5b8335615306816151e2565b92506020840135615316816151e2565b915060408401356001600160401b0381111561533157600080fd5b61533d86828701615290565b9150509250925092565b60008060006060848603121561535c57600080fd5b8335615367816151e2565b92506020840135615377816151e2565b929592945050506040919091013590565b60006020828403121561539a57600080fd5b81356111e2816151e2565b600080604083850312156153b857600080fd5b8235915060208301356153ca816151e2565b809150509250929050565b60006001600160401b038211156153ee576153ee615223565b5060051b60200190565b600082601f83011261540957600080fd5b813560206154196152af836153d5565b82815260059290921b8401810191818101908684111561543857600080fd5b8286015b84811015615453578035835291830191830161543c565b509695505050505050565b600082601f83011261546f57600080fd5b8135602061547f6152af836153d5565b82815260059290921b8401810191818101908684111561549e57600080fd5b8286015b848110156154535780356154b5816151e2565b83529183019183016154a2565b60008083601f8401126154d457600080fd5b5081356001600160401b038111156154eb57600080fd5b6020830191508360208260051b850101111561550657600080fd5b9250929050565b60008060008060008060a0878903121561552657600080fd5b86356001600160401b038082111561553d57600080fd5b6155498a838b016153f8565b9750602089013591508082111561555f57600080fd5b61556b8a838b0161545e565b9650604089013591508082111561558157600080fd5b61558d8a838b0161545e565b955060608901359150808211156155a357600080fd5b6155af8a838b0161545e565b945060808901359150808211156155c557600080fd5b506155d289828a016154c2565b979a9699509497509295939492505050565b6020808252825182820181905260009190848201906040850190845b8181101561561c57835183529284019291840191600101615600565b50909695505050505050565b6000806040838503121561563b57600080fd5b50508035926020909101359150565b60008083601f84011261565c57600080fd5b5081356001600160401b0381111561567357600080fd5b60208301915083602082850101111561550657600080fd5b600080600080600080600060c0888a0312156156a657600080fd5b87356156b1816151e2565b96506020880135955060408801356156c8816151e2565b9450606088013593506080880135925060a08801356001600160401b038111156156f157600080fd5b6156fd8a828b0161564a565b989b979a50959850939692959293505050565b600080600080600080600060e0888a03121561572b57600080fd5b8735615736816151e2565b96506020880135615746816151e2565b95506040880135615756816151e2565b94506060880135615766816151e2565b93506080880135615776816151e2565b925060a0880135615786816151e2565b915060c0880135615796816151e2565b8091505092959891949750929550565b600080600080600080606087890312156157bf57600080fd5b86356001600160401b03808211156157d657600080fd5b6157e28a838b016154c2565b909850965060208901359150808211156157fb57600080fd5b6158078a838b016154c2565b909650945060408901359150808211156155c557600080fd5b6000806020838503121561583357600080fd5b82356001600160401b0381111561584957600080fd5b6158558582860161564a565b90969095509350505050565b8015158114611b4557600080fd5b6000806040838503121561588257600080fd5b823561588d816151e2565b915060208301356153ca81615861565b600080600080608085870312156158b357600080fd5b84356158be816151e2565b935060208501356158ce816151e2565b92506040850135915060608501356001600160401b038111156158f057600080fd5b6158fc87828801615290565b91505092959194509250565b6000806000806000806060878903121561592157600080fd5b86356001600160401b038082111561593857600080fd5b6159448a838b0161564a565b9098509650602089013591508082111561595d57600080fd5b6159698a838b0161564a565b9096509450604089013591508082111561598257600080fd5b506155d289828a0161564a565b6000806000806000608086880312156159a757600080fd5b853594506020860135935060408601356159c0816151e2565b925060608601356001600160401b038111156159db57600080fd5b6159e78882890161564a565b969995985093965092949392505050565b60008060408385031215615a0b57600080fd5b8235615a16816151e2565b915060208301356153ca816151e2565b600080600060608486031215615a3b57600080fd5b8335615a46816151e2565b95602085013595506040909401359392505050565b600080600060608486031215615a7057600080fd5b833592506020840135615377816151e2565b634e487b7160e01b600052601160045260246000fd5b600082821015615aaa57615aaa615a82565b500390565b600181811c90821680615ac357607f821691505b60208210810361274a57634e487b7160e01b600052602260045260246000fd5b6001600160a01b038581168252848116602083015283166040820152608060608201819052600090612d5b90830184615176565b600060208284031215615b2957600080fd5b5051919050565b60008219821115615b4357615b43615a82565b500190565b634e487b7160e01b600052603260045260246000fd5b6000808335601e19843603018112615b7557600080fd5b8301803591506001600160401b03821115615b8f57600080fd5b60200191503681900382131561550657600080fd5b8881526001600160a01b03888116602083015287811660408301528616606082015260e06080820181905281018490526000610100858782850137600083870182015260a08301949094525060c0810191909152601f909201601f19169091010195945050505050565b600060018201615c2057615c20615a82565b5060010190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b600060208284031215615c5d57600080fd5b81516111e281615861565b6000816000190483118215151615615c8257615c82615a82565b500290565b600082615ca457634e487b7160e01b600052601260045260246000fd5b500490565b600060208284031215615cbb57600080fd5b81516001600160401b03811115615cd157600080fd5b8201601f81018413615ce257600080fd5b8051615cf06152af82615269565b818152856020838501011115615d0557600080fd5b615d1682602083016020860161514a565b95945050505050565b8054600090600181811c9080831680615d3957607f831692505b60208084108203615d5a57634e487b7160e01b600052602260045260246000fd5b83885260208801828015615d755760018114615d8657615db1565b60ff19871682528282019750615db1565b60008981526020902060005b87811015615dab57815484820152908601908401615d92565b83019850505b5050505050505092915050565b606081526000615dd16060830186615d1f565b8281036020840152615de38186615d1f565b90508281036040840152612d5b8185615d1f565b6001600160a01b0385811682528416602082015260408101839052608060608201819052600090612d5b90830184615176565b600060208284031215615e3c57600080fd5b81516111e2816150eb565b600060ff821660ff84168060ff03821115615e6457615e64615a82565b019392505050565b60008551615e7e818460208a0161514a565b855190830190615e92818360208a0161514a565b8551910190615ea581836020890161514a565b8451910190615eb881836020880161514a565b019695505050505050565b600081615ed257615ed2615a82565b506000190190565b76020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b815260008351615f0c81601785016020880161514a565b7001034b99036b4b9b9b4b733903937b6329607d1b6017918401918201528351615f3d81602884016020880161514a565b01602801949350505050565b60008251615f5b81846020870161514a565b919091019291505056feb309c40027c81d382c3b58d8de24207a34b27e1db369b1434e4a11311f154b5eddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efb89cdd26cddd51301940bf2715f765b626b8a5a9e2681ac62dc83cc2db2530c0a264697066735822122089f9df2fc859df255718d0c9d9e3be18db955937b69b89ca919e4b7a2aa8a29a64736f6c634300080d0033

Deployed Bytecode

0x60806040526004361061044f5760003560e01c806370a0823111610236578063b11d7ec11161012f578063d52e4a10116100b1578063d52e4a1014610e33578063d547741f14610e49578063d813cc1914610e69578063d9caed1214610e7c578063debe2b0d14610e9c578063e985e9c514610ebc578063f0ba604014610edc578063f12c6b6e14610ef1578063f32e8b2414610f11578063f5766b3914610f26578063f8548e3614610f4657600080fd5b8063b11d7ec114610cc7578063b129694e14610ce7578063b1a3b25d14610d08578063b88d4fde14610d28578063c23135dd14610d48578063c87b56dd14610d76578063c907c3ec14610d96578063d1b8759b14610db7578063d1bbd49c14610dd7578063d250348514610df3578063d32bfb6c14610e1357600080fd5b806391d14854116101b857806391d1485414610b7b57806392ac98a514610b9b57806393fd184414610bbb57806395d89b4114610bd25780639d76ea5814610be7578063a217fddf14610c08578063a22cb46514610c1d578063a2e4cd2e14610c3d578063a375cb0514610c5d578063a98d362314610c74578063aae4b8f714610ca757600080fd5b806370a0823114610a1d57806374b6c10614610a3d57806374cac47d14610a545780637ec2a72414610a74578063812eecd414610a9557806381a3c94314610ab55780638505fe9514610ad55780638577a6d514610af55780638932a90d14610b155780638ca2fbad14610b3a5780638da5cb5b14610b5c57600080fd5b80632f54bf6e116103485780634e2ce6d3116102ca5780634e2ce6d3146108c85780634f6ccce7146108df57806352b0f638146108ff57806354b249fb1461091f578063558b71e914610950578063564aa99d1461097057806356e0d51f146109905780636207a8da146109a75780636352211e146109bd5780636d8ea5b4146109dd5780636eadde43146109fd57600080fd5b80632f54bf6e146107405780632f745c5914610770578063338189971461079057806336568abe146107b0578063389f07e8146107d057806339f46986146107f1578063407dc5891461081157806342842e0e1461083157806342966c68146108515780634cd38c1d146108715780634d025fed1461089157600080fd5b806318160ddd116103d157806318160ddd146105de578063183767da146105f4578063217751bc1461060b578063231005091461062c57806323b872dd1461064e578063248a9ca31461066e57806326e9ca071461069e578063282478df146106bf5780632af9162a146106df5780632d33dd5b146106ff5780632f2ff15d1461072057600080fd5b806301ffc9a71461045b578063068208cd1461049057806306fdde03146104b2578063081812fc146104d4578063095ea7b314610501578063097ba333146105215780630c2db8d11461054f5780630f15023b1461056f57806310e569731461059057806311a4c03a146105a757806313af4035146105be57600080fd5b3661045657005b600080fd5b34801561046757600080fd5b5061047b610476366004615101565b610f66565b60405190151581526020015b60405180910390f35b34801561049c57600080fd5b506104b06104ab36600461511e565b610f77565b005b3480156104be57600080fd5b506104c7610ff6565b60405161048791906151a2565b3480156104e057600080fd5b506104f46104ef3660046151b5565b611085565b60405161048791906151ce565b34801561050d57600080fd5b506104b061051c3660046151f7565b6110ad565b34801561052d57600080fd5b5061054161053c3660046152e6565b611148565b604051908152602001610487565b34801561055b57600080fd5b506104b061056a366004615347565b6111e9565b34801561057b57600080fd5b50610c83546104f4906001600160a01b031681565b34801561059c57600080fd5b50610541610c855481565b3480156105b357600080fd5b50610541610c845481565b3480156105ca57600080fd5b506104b06105d9366004615388565b61123e565b3480156105ea57600080fd5b50610c8754610541565b34801561060057600080fd5b506105416124075481565b34801561061757600080fd5b50610c8a546104f4906001600160a01b031681565b34801561063857600080fd5b50610541600080516020615f6683398151915281565b34801561065a57600080fd5b506104b0610669366004615347565b6112cf565b34801561067a57600080fd5b506105416106893660046151b5565b60009081526097602052604090206001015490565b3480156106aa57600080fd5b50610c8b546104f4906001600160a01b031681565b3480156106cb57600080fd5b506104b06106da36600461511e565b611309565b3480156106eb57600080fd5b506104b06106fa366004615388565b6113ae565b34801561070b57600080fd5b50610c89546104f4906001600160a01b031681565b34801561072c57600080fd5b506104b061073b3660046153a5565b611405565b34801561074c57600080fd5b5061047b61075b366004615388565b612bda546001600160a01b0390811691161490565b34801561077c57600080fd5b5061054161078b3660046151f7565b61142a565b6107a361079e36600461550d565b61147e565b60405161048791906155e4565b3480156107bc57600080fd5b506104b06107cb3660046153a5565b6119b7565b3480156107dc57600080fd5b50610c8f546104f4906001600160a01b031681565b3480156107fd57600080fd5b506104b061080c366004615628565b611a3a565b34801561081d57600080fd5b506104b061082c3660046151f7565b611a88565b34801561083d57600080fd5b506104b061084c366004615347565b611adc565b34801561085d57600080fd5b506104b061086c3660046151b5565b611af7565b34801561087d57600080fd5b506104b061088c366004615628565b611b48565b34801561089d57600080fd5b506104f46108ac3660046151b5565b611078602052600090815260409020546001600160a01b031681565b3480156108d457600080fd5b50610541610c8d5481565b3480156108eb57600080fd5b506105416108fa3660046151b5565b611b9d565b34801561090b57600080fd5b5061047b61091a366004615388565b611bc6565b34801561092b57600080fd5b5061054161093a3660046151b5565b600090815261107b602052604090206001015490565b34801561095c57600080fd5b506104b061096b366004615628565b611be0565b34801561097c57600080fd5b506104b061098b366004615388565b611c04565b34801561099c57600080fd5b506105416127f05481565b3480156109b357600080fd5b5061201e54610541565b3480156109c957600080fd5b506104f46109d83660046151b5565b611c5b565b3480156109e957600080fd5b5061047b6109f8366004615388565b611c77565b348015610a0957600080fd5b506104b0610a1836600461568b565b611d22565b348015610a2957600080fd5b50610541610a38366004615388565b611ea1565b348015610a4957600080fd5b50610541610c865481565b348015610a6057600080fd5b506104b0610a6f366004615710565b611ef3565b348015610a8057600080fd5b50610c8c546104f4906001600160a01b031681565b348015610aa157600080fd5b50610541610ab0366004615388565b612178565b348015610ac157600080fd5b506107a3610ad03660046157a6565b6121be565b348015610ae157600080fd5b506104b0610af03660046153a5565b61241e565b348015610b0157600080fd5b506104b0610b103660046151b5565b6125f5565b348015610b2157600080fd5b506104b0610b30366004615820565b5050600c610c8d55565b348015610b4657600080fd5b50610541600080516020615fa683398151915281565b348015610b6857600080fd5b50612bda546001600160a01b03166104f4565b348015610b8757600080fd5b5061047b610b963660046153a5565b612636565b348015610ba757600080fd5b50610541610bb63660046151b5565b612661565b348015610bc757600080fd5b506105416110775481565b348015610bde57600080fd5b506104c7612750565b348015610bf357600080fd5b506104b1546104f4906001600160a01b031681565b348015610c1457600080fd5b50610541600081565b348015610c2957600080fd5b506104b0610c3836600461586f565b61287b565b348015610c4957600080fd5b506104b0610c583660046153a5565b612936565b348015610c6957600080fd5b506105416127f15481565b348015610c8057600080fd5b5061047b610c8f3660046151b5565b600090815261107b6020526040902060010154421090565b348015610cb357600080fd5b5061047b610cc2366004615388565b6129c4565b348015610cd357600080fd5b506104b0610ce23660046153a5565b6129de565b348015610cf357600080fd5b50610c91546104f4906001600160a01b031681565b348015610d1457600080fd5b50610541610d23366004615628565b612a2c565b348015610d3457600080fd5b506104b0610d4336600461589d565b612a9f565b348015610d5457600080fd5b50610541610d63366004615388565b6120226020526000908152604090205481565b348015610d8257600080fd5b506104c7610d913660046151b5565b612ad3565b348015610da257600080fd5b50610c90546104f4906001600160a01b031681565b348015610dc357600080fd5b506104b0610dd2366004615908565b612d65565b348015610de357600080fd5b50604051600c8152602001610487565b348015610dff57600080fd5b506104b0610e0e366004615388565b612de0565b348015610e1f57600080fd5b506104b0610e2e3660046151b5565b612e37565b348015610e3f57600080fd5b50610c8e54610541565b348015610e5557600080fd5b506104b0610e643660046153a5565b612e69565b6104b0610e7736600461598f565b612e8e565b348015610e8857600080fd5b506104b0610e97366004615347565b61309e565b348015610ea857600080fd5b506104b0610eb73660046151f7565b6131d4565b348015610ec857600080fd5b5061047b610ed73660046159f8565b6131f9565b348015610ee857600080fd5b506104b0613228565b348015610efd57600080fd5b506104b0610f0c366004615a26565b61326d565b348015610f1d57600080fd5b506104b0613429565b348015610f3257600080fd5b506104b0610f413660046151b5565b613439565b348015610f5257600080fd5b5061047b610f61366004615a5b565b613447565b6000610f71826134a9565b92915050565b610f80836134b4565b610f89836134e3565b610f9283613515565b610f9b826134b4565b600083815261107b6020526040902060010154610fb9904290615a98565b811115610fd9576040516310e88eed60e31b815260040160405180910390fd5b610fe5838260006135cd565b610ff1828260016135cd565b505050565b611463805461100490615aaf565b80601f016020809104026020016040519081016040528092919081815260200182805461103090615aaf565b801561107d5780601f106110525761010080835404028352916020019161107d565b820191906000526020600020905b81548152906001019060200180831161106057829003601f168201915b505050505081565b6000611090826134b4565b50600090815261107960205260409020546001600160a01b031690565b6110b681613515565b6001600160a01b03821633036110df57604051637899146560e11b815260040160405180910390fd5b60008181526110796020908152604080832080546001600160a01b0319166001600160a01b03878116918217909255611076909352818420549151859492909116917f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591a45050565b610c89546000906001600160a01b0316156111dc57610c895460405163221c1fd160e01b81526001600160a01b039091169063221c1fd190611194903390889088908890600401615ae3565b602060405180830381865afa1580156111b1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111d59190615b17565b90506111e2565b50610c85545b9392505050565b6111f3813361369f565b6112105760405163075fd2b160e01b815260040160405180910390fd5b61121b838383613717565b60009081526110786020526040902080546001600160a01b031916331790555050565b61124661391a565b6001600160a01b03811661126d576040516330c6e09f60e21b815260040160405180910390fd5b612bda80546001600160a01b038381166001600160a01b031983168117909355604080519190921680825260208201939093527f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0910160405180910390a15050565b6112d8816134e3565b6112e181613515565b60008181526110786020526040902080546001600160a01b0319169055610ff1838383613717565b61131161391a565b806000036113325760405163e03b033d60e01b815260040160405180910390fd5b610c875482101561135657604051631d00cd6b60e01b815260040160405180910390fd5b610c8e819055610c84839055610c8682905560408051848152602081018490529081018290527f9a09448a3f24d3a01ccc67103c7cddbeea820176a18182cc83d0bce585f26a5b9060600160405180910390a1505050565b6113b661391a565b6113ce600080516020615f6683398151915282612e69565b6040516001600160a01b038216907f766f6199fea59554b9ff688e413302b9200f85d74811c053c12d945ac6d8dd7a90600090a250565b60008281526097602052604090206001015461142081613951565b610ff1838361395b565b600061143583612178565b821061145457604051630471175760e11b815260040160405180910390fd5b506001600160a01b0391909116600090815261107c60209081526040808320938352929052205490565b60606114886139e1565b610c86548651610c875461149c9190615b30565b11156114bb576040516331af695160e01b815260040160405180910390fd5b845186511415806114ce57508351865114155b156114ec576040516376b3b52560e11b815260040160405180910390fd5b60008087516001600160401b0381111561150857611508615223565b604051908082528060200260200182016040528015611531578160200160208202803683370190505b50905060005b88518110156118ad57600089828151811061155457611554615b48565b602002602001015190506115a38189848151811061157457611574615b48565b6020026020010151600019610c84541461159b57610c84546115969042615b30565b613a05565b600019613a05565b8383815181106115b5576115b5615b48565b602002602001018181525050600061163f828b85815181106115d9576115d9615b48565b60200260200101518a8a878181106115f3576115f3615b48565b90506020028101906116059190615b5e565b8080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061114892505050565b905061164b8186615b30565b94508061201f600086868151811061166557611665615b48565b6020026020010151815260200190815260200160002081905550610c8454612020600086868151811061169a5761169a615b48565b60200260200101518152602001908152602001600020819055506104b160009054906101000a90046001600160a01b031661202160008686815181106116e2576116e2615b48565b602090810291909101810151825281019190915260400160002080546001600160a01b0319166001600160a01b039283161790556104b15416158015906117415750808c848151811061173757611737615b48565b6020026020010151105b1561175f576040516330005fb160e21b815260040160405180910390fd5b611782818b858151811061177557611775615b48565b6020026020010151613ae0565b6104b1546000906001600160a01b0316156117b6578c84815181106117a9576117a9615b48565b60200260200101516117b8565b345b610c89549091506001600160a01b03161561189757610c895485516001600160a01b0390911690635e895f29908790879081106117f7576117f7615b48565b602002602001015133868f898151811061181357611813615b48565b60200260200101518e8e8b81811061182d5761182d615b48565b905060200281019061183f9190615b5e565b89896040518963ffffffff1660e01b8152600401611864989796959493929190615ba4565b600060405180830381600087803b15801561187e57600080fd5b505af1158015611892573d6000803e3d6000fd5b505050505b50505080806118a590615c0e565b915050611537565b506104b1546001600160a01b031615611941576104b1546040516323b872dd60e01b81526001600160a01b039091169081906323b872dd906118f790339030908890600401615c27565b6020604051808303816000875af1158015611916573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061193a9190615c4b565b5050611962565b81341015611962576040516306c3cddf60e41b815260040160405180910390fd5b61196a613be5565b60005b87518110156119aa5761199888828151811061198b5761198b615b48565b6020026020010151613d3f565b806119a281615c0e565b91505061196d565b5098975050505050505050565b6001600160a01b0381163314611a2c5760405162461bcd60e51b815260206004820152602f60248201527f416363657373436f6e74726f6c3a2063616e206f6e6c792072656e6f756e636560448201526e103937b632b9903337b91039b2b63360891b60648201526084015b60405180910390fd5b611a368282613dd8565b5050565b611a4261391a565b60408051838152602081018390527fd6867bc538320e67d7bdc35860c27c08486eb490b4fd9b820fff18fb28381d3c910160405180910390a16127f1919091556127f055565b611a91816134e3565b600081815261107860205260409020546001600160a01b03163314611ac95760405163075fd2b160e01b815260040160405180910390fd5b611a36611ad582611c5b565b8383613717565b610ff183838360405180602001604052806000815250612a9f565b611b00816134b4565b611b0981613515565b600081815261107660205260408082205490518392916001600160a01b031690600080516020615f86833981519152908390a4611b4581613e3f565b50565b611b506139e1565b611b59826134b4565b611b6233611bc6565b158015611b755750611b73336129c4565b155b15611b9357604051631798fedb60e01b815260040160405180910390fd5b610ff18282613e70565b6000610c87548210611bc257604051630471175760e11b815260040160405180910390fd5b5090565b6000610f71600080516020615f6683398151915283612636565b611be9826134b4565b611bf2826134e3565b611bfa61391a565b611a368282613fd1565b611c0c61391a565b611c24600080516020615f6683398151915282611405565b6040516001600160a01b038216907f684f8a71407db0c334454ebe9c9b288549317893a20b10dc779ec5c118dcd12190600090a250565b600090815261107660205260409020546001600160a01b031690565b600080611c8383611ea1565b610c8b54911091506001600160a01b031615611d1d57610c8b546040516370b6638f60e11b81523060048201526001600160a01b0384811660248301526000604483015283151560648301529091169063e16cc71e90608401602060405180830381865afa158015611cf9573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610f719190615c4b565b919050565b600054610100900460ff1615808015611d425750600054600160ff909116105b80611d635750611d51306140ef565b158015611d63575060005460ff166001145b611dc65760405162461bcd60e51b815260206004820152602e60248201527f496e697469616c697a61626c653a20636f6e747261637420697320616c72656160448201526d191e481a5b9a5d1a585b1a5e995960921b6064820152608401611a23565b6000805460ff191660011790558015611de9576000805461ff0019166101001790555b611df2866140fe565b611dfe8888878761412a565b611e08838361416b565b611e10614191565b611e1c6103e86127f055565b611e25886141a1565b612bda80546001600160a01b0319166001600160a01b038a16179055611e516380ac58cd60e01b614229565b8015611e97576000805461ff0019169055604051600181527f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024989060200160405180910390a15b5050505050505050565b600080611ead83612178565b905060005b81811015611eec57611ec7610c8f858361142a565b15611eda5782611ed681615c0e565b9350505b80611ee481615c0e565b915050611eb2565b5050919050565b611efb61391a565b6001600160a01b03871615801590611f225750611f20876001600160a01b03166140ef565b155b15611f4357604051636788e02b60e01b815260006004820152602401611a23565b6001600160a01b03861615801590611f6a5750611f68866001600160a01b03166140ef565b155b15611f8b57604051636788e02b60e01b815260016004820152602401611a23565b6001600160a01b03851615801590611fb25750611fb0856001600160a01b03166140ef565b155b15611fd357604051636788e02b60e01b815260026004820152602401611a23565b6001600160a01b03841615801590611ffa5750611ff8846001600160a01b03166140ef565b155b1561201b57604051636788e02b60e01b815260036004820152602401611a23565b6001600160a01b038316158015906120425750612040836001600160a01b03166140ef565b155b1561206257604051636788e02b60e01b8152600481810152602401611a23565b6001600160a01b038216158015906120895750612087826001600160a01b03166140ef565b155b156120aa57604051636788e02b60e01b815260056004820152602401611a23565b6001600160a01b038116158015906120d157506120cf816001600160a01b03166140ef565b155b156120f257604051636788e02b60e01b815260066004820152602401611a23565b610c8980546001600160a01b03199081166001600160a01b03998a1617909155610c8a8054821697891697909717909655610c8c8054871694881694909417909355610c8b8054861694871694909417909355610c8f80548516918616919091179055610c908054841692851692909217909155610c9180549092169216919091179055565b60006001600160a01b0382166121a157604051635963709b60e01b815260040160405180910390fd5b506001600160a01b0316600090815261107e602052604090205490565b60606121c86139e1565b6121d133611bc6565b1580156121e457506121e2336129c4565b155b1561220257604051631798fedb60e01b815260040160405180910390fd5b6000866001600160401b0381111561221c5761221c615223565b604051908082528060200260200182016040528015612245578160200160208202803683370190505b50905060005b87811015612412576122c289898381811061226857612268615b48565b905060200201602081019061227d9190615388565b86868481811061228f5761228f615b48565b90506020020160208101906122a49190615388565b8989858181106122b6576122b6615b48565b90506020020135613a05565b8282815181106122d4576122d4615b48565b6020908102919091010152610c91546001600160a01b03161561240057610c915482516001600160a01b03909116906348a254b89084908490811061231b5761231b615b48565b6020026020010151338c8c8681811061233657612336615b48565b905060200201602081019061234b9190615388565b89898781811061235d5761235d615b48565b90506020020160208101906123729190615388565b8c8c8881811061238457612384615b48565b6040516001600160e01b031960e08a901b16815260048101979097526001600160a01b039586166024880152938516604487015250921660648401526020020135608482015260a401600060405180830381600087803b1580156123e757600080fd5b505af11580156123fb573d6000803e3d6000fd5b505050505b8061240a81615c0e565b91505061224b565b50979650505050505050565b6124266139e1565b61242f826134b4565b60008281526120206020526040902054600019148061245857506104b1546001600160a01b0316155b1561247657604051636cd40e1160e11b815260040160405180910390fd5b600061249a61248484611c5b565b8360405180602001604052806000815250611148565b600084815261201f6020526040902054909150811415806124cd5750610c84546000848152612020602052604090205414155b806124f757506104b154600084815261202160205260409020546001600160a01b03908116911614155b156125155760405163986739e760e01b815260040160405180910390fd5b600083815261107b6020526040902060010154421015612548576040516360d8ec3360e11b815260040160405180910390fd5b612553836000613e70565b5061255e8183613ae0565b6104b1546001600160a01b0316806323b872dd61257a86611c5b565b30856040518463ffffffff1660e01b815260040161259a93929190615c27565b6020604051808303816000875af11580156125b9573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906125dd9190615c4b565b506125e6613be5565b6125ef83613d3f565b50505050565b6125fd61391a565b6040518181527f0496ed1e61eb69727f9659a8e859288db4758ffb1f744d1c1424634f90a257f49060200160405180910390a161240755565b60009182526097602090815260408084206001600160a01b0393909316845291905290205460ff1690565b600061266c826134e3565b600019610c845403612681575050610c855490565b600082815261107b602052604081206001015461269f904290615a98565b9050610c84546127f154826126b49190615b30565b106126c457610c855491506126e4565b610c845481610c85546126d79190615c68565b6126e19190615c87565b91505b6127f15415806127035750610c84546127f1546127019083615b30565b105b1561274a5760006127106127f054610c855461271f9190615c68565b6127299190615c87565b9050808311156127445761273d8184615a98565b9250611eec565b60009250505b50919050565b6060611464805461276090615aaf565b90506000036127ea57610c8360009054906101000a90046001600160a01b03166001600160a01b031663cec410526040518163ffffffff1660e01b8152600401600060405180830381865afa1580156127bd573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526127e59190810190615ca9565b905090565b61146480546127f890615aaf565b80601f016020809104026020016040519081016040528092919081815260200182805461282490615aaf565b80156128715780601f1061284657610100808354040283529160200191612871565b820191906000526020600020905b81548152906001019060200180831161285457829003601f168201915b5050505050905090565b336001600160a01b038316036128a457604051637899146560e11b815260040160405180910390fd5b61271061240754106128c9576040516323f21a3d60e21b815260040160405180910390fd5b33600081815261107a602090815260408083206001600160a01b03871680855290835292819020805460ff191686151590811790915590519081529192917f17307eab39ab6107e8899845ad3d59bd9653f200f220920489ca2b5937696c31910160405180910390a35050565b61293e61391a565b612947816142a8565b610c8580546104b18054928590556001600160a01b031983166001600160a01b0385811691821790925560408051848152602081018890529290941693820184905260608201529091907f3615065ccf48367ac483ac86701248e2e5ff55bdd9be845007d34a3b68d719d49060800160405180910390a150505050565b6000610f71600080516020615fa683398151915283612636565b6129e7826134b4565b6129f1823361369f565b158015612a045750612a02336129c4565b155b15612a225760405163866c2fa760e01b815260040160405180910390fd5b611a36828261433f565b6000612a37836134b4565b600083815261107b602052604090206001015442811015612a5c576000915050610f71565b600083600003612a7757612a704283615a98565b9050612a7a565b50825b6127106124075482612a8c9190615c68565b612a969190615c87565b92505050610f71565b612aaa8484846112cf565b612ab6848484846143cc565b6125ef576040516303f8ea1560e41b815260040160405180910390fd5b606080806000612ae230614483565b905060608515612afc57612af58661466e565b9250612b0f565b6040518060200160405280600081525092505b610c8c546001600160a01b031615612be657600086815261107b6020526040902060010154610c8c546001600160a01b031663988b93ad3033612b518b611c5b565b60405160e085901b6001600160e01b03191681526001600160a01b03938416600482015291831660248301529091166044820152606481018a90526084810184905260a401600060405180830381865afa158015612bb3573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612bdb9190810190615ca9565b979650505050505050565b6114658054612bf490615aaf565b9050600003612c9c57610c8360009054906101000a90046001600160a01b03166001600160a01b031663a998e9fb6040518163ffffffff1660e01b8152600401600060405180830381865afa158015612c51573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f19168201604052612c799190810190615ca9565b9350604051806040016040528060018152602001602f60f81b8152509050612d4f565b6114658054612caa90615aaf565b80601f0160208091040260200160405190810160405280929190818152602001828054612cd690615aaf565b8015612d235780601f10612cf857610100808354040283529160200191612d23565b820191906000526020600020905b815481529060010190602001808311612d0657829003601f168201915b505050505093506040518060200160405280600081525090506040518060200160405280600081525091505b612d5b8483838661479e565b9695505050505050565b612d6d61391a565b612d7a611463878761505b565b50612d88611464858561505b565b50612d96611465838361505b565b507f1e6d6a19e45ae156dcf4155bc83cf8f59e98d536000998f0e95f4cd330ecfb3e611463611464611465604051612dd093929190615dbe565b60405180910390a1505050505050565b612de861391a565b612e00600080516020615fa683398151915282611405565b6040516001600160a01b038216907f91d5c045d5bd98bf59a379b259ebca05b93bf79af1845fdf87e3172385d4c7f790600090a250565b612e40816134b4565b612e49816134e3565b612e5281613515565b6000612e5d82612661565b9050611a368282613fd1565b600082815260976020526040902060010154612e8481613951565b610ff18383613dd8565b612e966139e1565b612e9f846134b4565b612eaa846000613e70565b506000612ef6612eb986611c5b565b8585858080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525061114892505050565b9050612f028185613ae0565b6104b1546001600160a01b031615612fb65780861015612f35576040516330005fb160e21b815260040160405180910390fd5b6104b1546040516323b872dd60e01b81526001600160a01b039091169081906323b872dd90612f6c90339030908790600401615c27565b6020604051808303816000875af1158015612f8b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190612faf9190615c4b565b5050612fd7565b80341015612fd7576040516306c3cddf60e41b815260040160405180910390fd5b600085815261201f6020526040902054811461300057600085815261201f602052604090208190555b610c8454600086815261202060205260409020541461302e57610c8454600086815261202060205260409020555b6104b154600086815261202160205260409020546001600160a01b03908116911614613085576104b15460008681526120216020526040902080546001600160a01b0319166001600160a01b039092169190911790555b61308d613be5565b61309684613d3f565b505050505050565b6130a661391a565b60006001600160a01b0384166130bd57504761312d565b6040516370a0823160e01b81526001600160a01b038516906370a08231906130e99030906004016151ce565b602060405180830381865afa158015613106573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061312a9190615b17565b90505b600082158061313b57508183115b156131685760008211613161576040516303e09bb960e31b815260040160405180910390fd5b508061316b565b50815b836001600160a01b0316856001600160a01b0316336001600160a01b03167f342e7ff505a8a0364cd0dc2ff195c315e43bce86b204846ecd36913e117b109e846040516131ba91815260200190565b60405180910390a46131cd8585836147d0565b5050505050565b6131dc61391a565b6001600160a01b0390911660009081526120226020526040902055565b6001600160a01b03918216600090815261107a6020908152604080832093909416825291909152205460ff1690565b613240600080516020615fa6833981519152336119b7565b60405133907f42885193b8178d25fca25a38e6fcc93918501e91be06d85e0c8afb3bad95238090600090a2565b6132756139e1565b610c8754610c86541161329b576040516331af695160e01b815260040160405180910390fd5b6132a482613515565b6132ad826134e3565b61271061240754106132d2576040516323f21a3d60e21b815260040160405180910390fd5b600082815261107660205260408120546001600160a01b031690804261330886600090815261107b602052604090206001015490565b6133129190615a98565b905060006133208686612a2c565b9050600061332e8287615b30565b90508281101561334c57859350613347878260006135cd565b6133a4565b6133568784612a2c565b91506133628284615a98565b600088815261107b6020526040808220426001909101555191955088917f59f2fe866dd27a1c2d34115520888c3150365cbc931aab97fa88c4b9ab40b7959190a25b60006133b589826115968842615b30565b905080896001600160a01b0316876001600160a01b0316600080516020615f8683398151915260405160405180910390a4613401868a83604051806020016040528060008152506143cc565b61341e576040516303f8ea1560e41b815260040160405180910390fd5b505050505050505050565b61343161391a565b600c610c8d55565b61344161391a565b61201e55565b6000613452846134e3565b6000612710834261347388600090815261107b602052604090206001015490565b61347d9190615a98565b6134879190615c68565b6134919190615c87565b905061349e84868361326d565b506001949350505050565b6000610f718261480c565b600081815261107b60205260408120549003611b45576040516378fe247360e01b815260040160405180910390fd5b600081815261107b60205260409020600101544210611b45576040516306cfa7d760e11b815260040160405180910390fd5b600081815261107860205260408120546001600160a01b03161561355157600082815261107860205260409020546001600160a01b031661356b565b600082815261107660205260409020546001600160a01b03165b9050613577823361369f565b15801561359c5750600082815261107960205260409020546001600160a01b03163314155b80156135af57506135ad81336131f9565b155b15611a365760405163e17c6d4560e01b815260040160405180910390fd5b6135d6836134b4565b600083815261107b602052604090206001015481156136235742811115613619576136018382615b30565b600085815261107b6020526040902060010155613641565b6136018342615b30565b61362d8382615a98565b600085815261107b60205260409020600101555b600084815261107b602090815260409182902060010154825190815290810185905283151581830152905185917f3c907806849e9204e0e26bb095dfe4b3071576c4323f766735c548211556d052919081900360600190a250505050565b600082815261107860205260408120546001600160a01b03838116911614806137025750816001600160a01b03166136d684611c5b565b6001600160a01b03161480156137025750600083815261107860205260409020546001600160a01b0316155b1561370f57506001610f71565b506000610f71565b613720816134e3565b826001600160a01b031661373382611c5b565b6001600160a01b03161461375a5760405163075fd2b160e01b815260040160405180910390fd5b612710612407541061377f576040516323f21a3d60e21b815260040160405180910390fd5b6001600160a01b0382166137a657604051635963709b60e01b815260040160405180910390fd5b816001600160a01b0316836001600160a01b0316036137d857604051633fbd1a4960e01b815260040160405180910390fd5b6137ee816137e7836000612a2c565b60006135cd565b600081815261107b6020526040902061380683612178565b60000361382457611077805490600061381e83615c0e565b91905055505b61382d82614817565b613837828461491d565b613840826149c2565b60008281526120206020908152604080832083905561201f9091528082208290555183916001600160a01b038087169290881691600080516020615f8683398151915291a4610c8f546001600160a01b0316156125ef57610c8f5460018201546040516375b37aef60e01b8152306004820152602481018590523360448201526001600160a01b038781166064830152868116608483015260a48201929092529116906375b37aef9060c401600060405180830381600087803b15801561390657600080fd5b505af1158015611e97573d6000803e3d6000fd5b613932600080516020615fa683398151915233612636565b61394f57604051632386d63160e21b815260040160405180910390fd5b565b611b4581336149ff565b6139658282612636565b611a365760008281526097602090815260408083206001600160a01b03851684529091529020805460ff1916600117905561399d3390565b6001600160a01b0316816001600160a01b0316837f2f8788117e7eff1d82e926ec794901d17c78024a50270940304540a733656f0d60405160405180910390a45050565b610c8d54600c1461394f576040516302eae03b60e61b815260040160405180910390fd5b60006001600160a01b038416613a2e57604051635963709b60e01b815260040160405180910390fd5b610c878054906000613a3f83615c0e565b9091555050610c87546040805180820182528281526020808201868152600085815261107b9092529290209051815590516001909101559050613a8184612178565b600003613a9f576110778054906000613a9983615c0e565b91905055505b613aa9818561491d565b613ab3818461433f565b60405181906001600160a01b03861690600090600080516020615f86833981519152908290a49392505050565b610c83546001600160a01b03163b15613ba757610c835460405163939d9f1f60e01b8152600481018490526001600160a01b0383811660248301529091169063939d9f1f90620493e090604401600060405180830381600088803b158015613b4757600080fd5b5087f193505050508015613b59575060015b611a3657610c835460405130917f6b18946261693dfd6c760d986b28ad2238b5b0267f8e5b6bc40a2f998e2f20ac91613b9b916001600160a01b0316906151ce565b60405180910390a25050565b610c835460405130917f6b18946261693dfd6c760d986b28ad2238b5b0267f8e5b6bc40a2f998e2f20ac91613b9b916001600160a01b0316906151ce565b61201e541561394f576104b1546001600160a01b031615613c84576104b15461201e5460405163a9059cbb60e01b815233600482015260248101919091526001600160a01b0390911690819063a9059cbb906044016020604051808303816000875af1158015613c59573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190613c7d9190615c4b565b5050613cf1565b61201e5460405160009133918381818185875af1925050503d8060008114613cc8576040519150601f19603f3d011682016040523d82523d6000602084013e613ccd565b606091505b5050905080613cef5760405163045ed26b60e11b815260040160405180910390fd5b505b61201e546104b154604080519283526001600160a01b03909116602083015233917f522a883b471164223f18b50f326da8671372b64b4792eac0e63d447e714c3e3b910160405180910390a2565b6120226020527fbed07da93ba22716a54f603f075ff3d6567a94916ac6d58738883fb4a4b47ea6546001600160a01b0382166000908152604090205415613d9c57506001600160a01b038116600090815261202260205260409020545b8015611a36576104b154610c8554611a36916001600160a01b031690849061271090613dc9908690615c68565b613dd39190615c87565b6147d0565b613de28282612636565b15611a365760008281526097602090815260408083206001600160a01b0385168085529252808320805460ff1916905551339285917ff6391f5c32d9c69d2a47ea670b442974b53935d1edc7fd64eb21e047a839171b9190a45050565b600090815261107b6020908152604080832042600190910155611076909152902080546001600160a01b0319169055565b600082815261107b60205260408120600190810154908101613ea557604051630fed19c160e11b815260040160405180910390fd5b60008315613eb35783613eb8565b610c84545b90506000198103613ecd576000199250613ef3565b42821115613ee657613edf8183615b30565b9250613ef3565b613ef08142615b30565b92505b600085815261107b6020526040908190206001018490555185907f3ca112768ff7861e008ace1c11570c52e404c043e585545b5957a1e20961dde390613f3c9086815260200190565b60405180910390a2610c90546001600160a01b031615613fc957610c90546040516202d72d60e71b81526004810187905233602482015260448101859052606481018490526001600160a01b039091169063016b968090608401600060405180830381600087803b158015613fb057600080fd5b505af1158015613fc4573d6000803e3d6000fd5b505050505b505092915050565b6000613fdc83611c5b565b9050613fe783613e3f565b336001600160a01b0316816001600160a01b0316847f0a7068a9989857441c039a14a42b67ed71dd1fcfe5a9b17cc87b252e47bce5288560405161402d91815260200190565b60405180910390a48115614053576104b154614053906001600160a01b031682846147d0565b60008381526120206020908152604080832083905561201f909152812055610c8a546001600160a01b031615610ff157610c8a5460405163b499b6c560e01b81526001600160a01b039091169063b499b6c5906140b890339085908790600401615c27565b600060405180830381600087803b1580156140d257600080fd5b505af11580156140e6573d6000803e3d6000fd5b50505050505050565b6001600160a01b03163b151590565b614107816142a8565b6104b180546001600160a01b0319166001600160a01b0392909216919091179055565b610c8380546001600160a01b03191633179055610c84839055610c85829055610c86819055614157600c90565b61ffff16610c8d5550506001610c8e555050565b614173614a63565b614180611463838361505b565b50611a36635b5e139f60e01b614229565b61394f63780e9d6360e01b614229565b6141b9600080516020615fa683398151915280614ace565b6141df600080516020615f66833981519152600080516020615fa6833981519152614ace565b6141e8816129c4565b61420457614204600080516020615fa683398151915282614b19565b61420d81611bc6565b611b4557611b45600080516020615f6683398151915282614b19565b6001600160e01b031980821690036142835760405162461bcd60e51b815260206004820152601c60248201527f4552433136353a20696e76616c696420696e74657266616365206964000000006044820152606401611a23565b6001600160e01b0319166000908152606560205260409020805460ff19166001179055565b6001600160a01b0381161580159061432157506000816001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156142fb573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061431f9190615b17565b105b15611b455760405163684cae7960e11b815260040160405180910390fd5b600082815261107860205260409020546001600160a01b03828116911614611a365760008281526110786020526040902080546001600160a01b0319166001600160a01b038316179055614392826149c2565b6040516001600160a01b0382169083907f9d2895c45a420624de863a2f437b022d879f457bf7a829044055a10c5a6fd5e390600090a35050565b60006143e0846001600160a01b03166140ef565b6143ec5750600161447b565b604051630a85bd0160e11b81526000906001600160a01b0386169063150b7a02906144219033908a9089908990600401615df7565b6020604051808303816000875af1158015614440573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906144649190615e2a565b6001600160e01b031916630a85bd0160e11b149150505b949350505050565b604080518082018252601081526f181899199a1a9b1b9c1cb0b131b232b360811b60208201528151602a80825260608281019094526001600160a01b0385169291600091602082018180368337019050509050600360fc1b816000815181106144ee576144ee615b48565b60200101906001600160f81b031916908160001a905350600f60fb1b8160018151811061451d5761451d615b48565b60200101906001600160f81b031916908160001a90535060005b6014811015614665578260048561454f84600c615b30565b6020811061455f5761455f615b48565b1a60f81b6001600160f81b031916901c60f81c60ff168151811061458557614585615b48565b01602001516001600160f81b031916826145a0836002615c68565b6145ab906002615b30565b815181106145bb576145bb615b48565b60200101906001600160f81b031916908160001a90535082846145df83600c615b30565b602081106145ef576145ef615b48565b825191901a600f1690811061460657614606615b48565b01602001516001600160f81b03191682614621836002615c68565b61462c906003615b30565b8151811061463c5761463c615b48565b60200101906001600160f81b031916908160001a9053508061465d81615c0e565b915050614537565b50949350505050565b60608160008190036146995750506040805180820190915260018152600360fc1b6020820152919050565b8260005b81156146c357806146ad81615c0e565b91506146bc9050600a83615c87565b915061469d565b6000816001600160401b038111156146dd576146dd615223565b6040519080825280601f01601f191660200182016040528015614707576020820181803683370190505b509050815b84156147945761471d600182615a98565b9050600061472c600a87615c87565b61473790600a615c68565b6147419087615a98565b61474c906030615e47565b905060008160f81b90508084848151811061476957614769615b48565b60200101906001600160f81b031916908160001a90535061478b600a88615c87565b9650505061470c565b5095945050505050565b6060848484846040516020016147b79493929190615e6c565b6040516020818303038152906040529050949350505050565b8015610ff1576001600160a01b0383166147f757610ff16001600160a01b03831682614b23565b826125ef6001600160a01b0382168484614c39565b6000610f7182614c8b565b600081815261107660205260408120546001600160a01b031690600161483c83612178565b6148469190615a98565b600084815261107d602052604090205490915080821461489c576001600160a01b038316600090815261107c60209081526040808320858452825280832054848452818420819055835261107d90915290208190555b6001600160a01b038316600090815261107c602090815260408083208584529091528120556148ca83612178565b6001036148e85761107780549060006148e283615ec3565b91905055505b6001600160a01b038316600090815261107e60205260408120805460019290614912908490615a98565b909155505050505050565b600061492882612178565b9050610c8e54811061494d57604051630bf6c32360e11b815260040160405180910390fd5b600083815261107d602090815260408083208490556001600160a01b03851680845261107c83528184208585528352818420879055868452611076835281842080546001600160a01b03191682179055835261107e90915281208054600192906149b8908490615b30565b9091555050505050565b600081815261107960205260409020546001600160a01b031615611b455760009081526110796020526040902080546001600160a01b0319169055565b614a098282612636565b611a3657614a21816001600160a01b03166014614cb0565b614a2c836020614cb0565b604051602001614a3d929190615eda565b60408051601f198184030181529082905262461bcd60e51b8252611a23916004016151a2565b600054610100900460ff1661394f5760405162461bcd60e51b815260206004820152602b60248201527f496e697469616c697a61626c653a20636f6e7472616374206973206e6f74206960448201526a6e697469616c697a696e6760a81b6064820152608401611a23565b600082815260976020526040808220600101805490849055905190918391839186917fbd79b86ffe0ab8e8776151514217cd7cacd52c909f66475c3af44e129f0b00ff9190a4505050565b611a36828261395b565b80471015614b735760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a20696e73756666696369656e742062616c616e63650000006044820152606401611a23565b6000826001600160a01b03168260405160006040518083038185875af1925050503d8060008114614bc0576040519150601f19603f3d011682016040523d82523d6000602084013e614bc5565b606091505b5050905080610ff15760405162461bcd60e51b815260206004820152603a60248201527f416464726573733a20756e61626c6520746f2073656e642076616c75652c20726044820152791958da5c1a595b9d081b585e481a185d99481c995d995c9d195960321b6064820152608401611a23565b604080516001600160a01b038416602482015260448082018490528251808303909101815260649091019091526020810180516001600160e01b031663a9059cbb60e01b179052610ff1908490614e4b565b60006001600160e01b03198216637965db0b60e01b1480610f715750610f7182614f1d565b60606000614cbf836002615c68565b614cca906002615b30565b6001600160401b03811115614ce157614ce1615223565b6040519080825280601f01601f191660200182016040528015614d0b576020820181803683370190505b509050600360fc1b81600081518110614d2657614d26615b48565b60200101906001600160f81b031916908160001a905350600f60fb1b81600181518110614d5557614d55615b48565b60200101906001600160f81b031916908160001a9053506000614d79846002615c68565b614d84906001615b30565b90505b6001811115614dfc576f181899199a1a9b1b9c1cb0b131b232b360811b85600f1660108110614db857614db8615b48565b1a60f81b828281518110614dce57614dce615b48565b60200101906001600160f81b031916908160001a90535060049490941c93614df581615ec3565b9050614d87565b5083156111e25760405162461bcd60e51b815260206004820181905260248201527f537472696e67733a20686578206c656e67746820696e73756666696369656e746044820152606401611a23565b6000614ea0826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b0316614f599092919063ffffffff16565b805190915015610ff15780806020019051810190614ebe9190615c4b565b610ff15760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e6044820152691bdd081cdd58d8d9595960b21b6064820152608401611a23565b60006301ffc9a760e01b6001600160e01b031983161480610f715750506001600160e01b03191660009081526065602052604090205460ff1690565b606061447b848460008585614f6d856140ef565b614fb95760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401611a23565b600080866001600160a01b03168587604051614fd59190615f49565b60006040518083038185875af1925050503d8060008114615012576040519150601f19603f3d011682016040523d82523d6000602084013e615017565b606091505b5091509150612bdb828286606083156150315750816111e2565b8251156150415782518084602001fd5b8160405162461bcd60e51b8152600401611a2391906151a2565b82805461506790615aaf565b90600052602060002090601f01602090048101928261508957600085556150cf565b82601f106150a25782800160ff198235161785556150cf565b828001600101855582156150cf579182015b828111156150cf5782358255916020019190600101906150b4565b50611bc29291505b80821115611bc257600081556001016150d7565b6001600160e01b031981168114611b4557600080fd5b60006020828403121561511357600080fd5b81356111e2816150eb565b60008060006060848603121561513357600080fd5b505081359360208301359350604090920135919050565b60005b8381101561516557818101518382015260200161514d565b838111156125ef5750506000910152565b6000815180845261518e81602086016020860161514a565b601f01601f19169290920160200192915050565b6020815260006111e26020830184615176565b6000602082840312156151c757600080fd5b5035919050565b6001600160a01b0391909116815260200190565b6001600160a01b0381168114611b4557600080fd5b6000806040838503121561520a57600080fd5b8235615215816151e2565b946020939093013593505050565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b038111828210171561526157615261615223565b604052919050565b60006001600160401b0382111561528257615282615223565b50601f01601f191660200190565b600082601f8301126152a157600080fd5b81356152b46152af82615269565b615239565b8181528460208386010111156152c957600080fd5b816020850160208301376000918101602001919091529392505050565b6000806000606084860312156152fb57600080fd5b8335615306816151e2565b92506020840135615316816151e2565b915060408401356001600160401b0381111561533157600080fd5b61533d86828701615290565b9150509250925092565b60008060006060848603121561535c57600080fd5b8335615367816151e2565b92506020840135615377816151e2565b929592945050506040919091013590565b60006020828403121561539a57600080fd5b81356111e2816151e2565b600080604083850312156153b857600080fd5b8235915060208301356153ca816151e2565b809150509250929050565b60006001600160401b038211156153ee576153ee615223565b5060051b60200190565b600082601f83011261540957600080fd5b813560206154196152af836153d5565b82815260059290921b8401810191818101908684111561543857600080fd5b8286015b84811015615453578035835291830191830161543c565b509695505050505050565b600082601f83011261546f57600080fd5b8135602061547f6152af836153d5565b82815260059290921b8401810191818101908684111561549e57600080fd5b8286015b848110156154535780356154b5816151e2565b83529183019183016154a2565b60008083601f8401126154d457600080fd5b5081356001600160401b038111156154eb57600080fd5b6020830191508360208260051b850101111561550657600080fd5b9250929050565b60008060008060008060a0878903121561552657600080fd5b86356001600160401b038082111561553d57600080fd5b6155498a838b016153f8565b9750602089013591508082111561555f57600080fd5b61556b8a838b0161545e565b9650604089013591508082111561558157600080fd5b61558d8a838b0161545e565b955060608901359150808211156155a357600080fd5b6155af8a838b0161545e565b945060808901359150808211156155c557600080fd5b506155d289828a016154c2565b979a9699509497509295939492505050565b6020808252825182820181905260009190848201906040850190845b8181101561561c57835183529284019291840191600101615600565b50909695505050505050565b6000806040838503121561563b57600080fd5b50508035926020909101359150565b60008083601f84011261565c57600080fd5b5081356001600160401b0381111561567357600080fd5b60208301915083602082850101111561550657600080fd5b600080600080600080600060c0888a0312156156a657600080fd5b87356156b1816151e2565b96506020880135955060408801356156c8816151e2565b9450606088013593506080880135925060a08801356001600160401b038111156156f157600080fd5b6156fd8a828b0161564a565b989b979a50959850939692959293505050565b600080600080600080600060e0888a03121561572b57600080fd5b8735615736816151e2565b96506020880135615746816151e2565b95506040880135615756816151e2565b94506060880135615766816151e2565b93506080880135615776816151e2565b925060a0880135615786816151e2565b915060c0880135615796816151e2565b8091505092959891949750929550565b600080600080600080606087890312156157bf57600080fd5b86356001600160401b03808211156157d657600080fd5b6157e28a838b016154c2565b909850965060208901359150808211156157fb57600080fd5b6158078a838b016154c2565b909650945060408901359150808211156155c557600080fd5b6000806020838503121561583357600080fd5b82356001600160401b0381111561584957600080fd5b6158558582860161564a565b90969095509350505050565b8015158114611b4557600080fd5b6000806040838503121561588257600080fd5b823561588d816151e2565b915060208301356153ca81615861565b600080600080608085870312156158b357600080fd5b84356158be816151e2565b935060208501356158ce816151e2565b92506040850135915060608501356001600160401b038111156158f057600080fd5b6158fc87828801615290565b91505092959194509250565b6000806000806000806060878903121561592157600080fd5b86356001600160401b038082111561593857600080fd5b6159448a838b0161564a565b9098509650602089013591508082111561595d57600080fd5b6159698a838b0161564a565b9096509450604089013591508082111561598257600080fd5b506155d289828a0161564a565b6000806000806000608086880312156159a757600080fd5b853594506020860135935060408601356159c0816151e2565b925060608601356001600160401b038111156159db57600080fd5b6159e78882890161564a565b969995985093965092949392505050565b60008060408385031215615a0b57600080fd5b8235615a16816151e2565b915060208301356153ca816151e2565b600080600060608486031215615a3b57600080fd5b8335615a46816151e2565b95602085013595506040909401359392505050565b600080600060608486031215615a7057600080fd5b833592506020840135615377816151e2565b634e487b7160e01b600052601160045260246000fd5b600082821015615aaa57615aaa615a82565b500390565b600181811c90821680615ac357607f821691505b60208210810361274a57634e487b7160e01b600052602260045260246000fd5b6001600160a01b038581168252848116602083015283166040820152608060608201819052600090612d5b90830184615176565b600060208284031215615b2957600080fd5b5051919050565b60008219821115615b4357615b43615a82565b500190565b634e487b7160e01b600052603260045260246000fd5b6000808335601e19843603018112615b7557600080fd5b8301803591506001600160401b03821115615b8f57600080fd5b60200191503681900382131561550657600080fd5b8881526001600160a01b03888116602083015287811660408301528616606082015260e06080820181905281018490526000610100858782850137600083870182015260a08301949094525060c0810191909152601f909201601f19169091010195945050505050565b600060018201615c2057615c20615a82565b5060010190565b6001600160a01b039384168152919092166020820152604081019190915260600190565b600060208284031215615c5d57600080fd5b81516111e281615861565b6000816000190483118215151615615c8257615c82615a82565b500290565b600082615ca457634e487b7160e01b600052601260045260246000fd5b500490565b600060208284031215615cbb57600080fd5b81516001600160401b03811115615cd157600080fd5b8201601f81018413615ce257600080fd5b8051615cf06152af82615269565b818152856020838501011115615d0557600080fd5b615d1682602083016020860161514a565b95945050505050565b8054600090600181811c9080831680615d3957607f831692505b60208084108203615d5a57634e487b7160e01b600052602260045260246000fd5b83885260208801828015615d755760018114615d8657615db1565b60ff19871682528282019750615db1565b60008981526020902060005b87811015615dab57815484820152908601908401615d92565b83019850505b5050505050505092915050565b606081526000615dd16060830186615d1f565b8281036020840152615de38186615d1f565b90508281036040840152612d5b8185615d1f565b6001600160a01b0385811682528416602082015260408101839052608060608201819052600090612d5b90830184615176565b600060208284031215615e3c57600080fd5b81516111e2816150eb565b600060ff821660ff84168060ff03821115615e6457615e64615a82565b019392505050565b60008551615e7e818460208a0161514a565b855190830190615e92818360208a0161514a565b8551910190615ea581836020890161514a565b8451910190615eb881836020880161514a565b019695505050505050565b600081615ed257615ed2615a82565b506000190190565b76020b1b1b2b9b9a1b7b73a3937b61d1030b1b1b7bab73a1604d1b815260008351615f0c81601785016020880161514a565b7001034b99036b4b9b9b4b733903937b6329607d1b6017918401918201528351615f3d81602884016020880161514a565b01602801949350505050565b60008251615f5b81846020870161514a565b919091019291505056feb309c40027c81d382c3b58d8de24207a34b27e1db369b1434e4a11311f154b5eddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3efb89cdd26cddd51301940bf2715f765b626b8a5a9e2681ac62dc83cc2db2530c0a264697066735822122089f9df2fc859df255718d0c9d9e3be18db955937b69b89ca919e4b7a2aa8a29a64736f6c634300080d0033

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.