creation_sourcecode: //
  //
  // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  // @@                                                                                                @@
  // @@   This token was launched using software provided by Metadrop. To learn more or to launch      @@
  // @@   your own token, visit: https://metadrop.com. See legal info at the end of this file.         @@
  // @@                                                                                                @@
  // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  //
  // SPDX-License-Identifier: BUSL-1.1
  // Metadrop Contracts (v2.1.0)
  //
  // Sources flattened with hardhat v2.17.2 https://hardhat.org
  
  // File @openzeppelin/contracts/token/ERC20/IERC20.sol@v4.9.3
  
  // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/IERC20.sol)
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Interface of the ERC20 standard as defined in the EIP.
   */
  interface IERC20 {
      /**
       * @dev Emitted when `value` tokens are moved from one account (`from`) to
       * another (`to`).
       *
       * Note that `value` may be zero.
       */
      event Transfer(address indexed from, address indexed to, uint256 value);
  
      /**
       * @dev Emitted when the allowance of a `spender` for an `owner` is set by
       * a call to {approve}. `value` is the new allowance.
       */
      event Approval(address indexed owner, address indexed spender, uint256 value);
  
      /**
       * @dev Returns the amount of tokens in existence.
       */
      function totalSupply() external view returns (uint256);
  
      /**
       * @dev Returns the amount of tokens owned by `account`.
       */
      function balanceOf(address account) external view returns (uint256);
  
      /**
       * @dev Moves `amount` tokens from the caller's account to `to`.
       *
       * Returns a boolean value indicating whether the operation succeeded.
       *
       * Emits a {Transfer} event.
       */
      function transfer(address to, uint256 amount) external returns (bool);
  
      /**
       * @dev Returns the remaining number of tokens that `spender` will be
       * allowed to spend on behalf of `owner` through {transferFrom}. This is
       * zero by default.
       *
       * This value changes when {approve} or {transferFrom} are called.
       */
      function allowance(address owner, address spender) external view returns (uint256);
  
      /**
       * @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
       *
       * Returns a boolean value indicating whether the operation succeeded.
       *
       * IMPORTANT: Beware that changing an allowance with this method brings the risk
       * that someone may use both the old and the new allowance by unfortunate
       * transaction ordering. One possible solution to mitigate this race
       * condition is to first reduce the spender's allowance to 0 and set the
       * desired value afterwards:
       * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
       *
       * Emits an {Approval} event.
       */
      function approve(address spender, uint256 amount) external returns (bool);
  
      /**
       * @dev Moves `amount` tokens from `from` to `to` using the
       * allowance mechanism. `amount` is then deducted from the caller's
       * allowance.
       *
       * Returns a boolean value indicating whether the operation succeeded.
       *
       * Emits a {Transfer} event.
       */
      function transferFrom(address from, address to, uint256 amount) external returns (bool);
  }
  
  // File @openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol@v4.9.3
  
  // OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Interface for the optional metadata functions from the ERC20 standard.
   *
   * _Available since v4.1._
   */
  interface IERC20Metadata is IERC20 {
      /**
       * @dev Returns the name of the token.
       */
      function name() external view returns (string memory);
  
      /**
       * @dev Returns the symbol of the token.
       */
      function symbol() external view returns (string memory);
  
      /**
       * @dev Returns the decimals places of the token.
       */
      function decimals() external view returns (uint8);
  }
  
  // File @uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router01.sol@v1.1.0-beta.0
  
  pragma solidity >=0.6.2;
  
  interface IUniswapV2Router01 {
      function factory() external pure returns (address);
      function WETH() external pure returns (address);
  
      function addLiquidity(
          address tokenA,
          address tokenB,
          uint amountADesired,
          uint amountBDesired,
          uint amountAMin,
          uint amountBMin,
          address to,
          uint deadline
      ) external returns (uint amountA, uint amountB, uint liquidity);
      function addLiquidityETH(
          address token,
          uint amountTokenDesired,
          uint amountTokenMin,
          uint amountETHMin,
          address to,
          uint deadline
      ) external payable returns (uint amountToken, uint amountETH, uint liquidity);
      function removeLiquidity(
          address tokenA,
          address tokenB,
          uint liquidity,
          uint amountAMin,
          uint amountBMin,
          address to,
          uint deadline
      ) external returns (uint amountA, uint amountB);
      function removeLiquidityETH(
          address token,
          uint liquidity,
          uint amountTokenMin,
          uint amountETHMin,
          address to,
          uint deadline
      ) external returns (uint amountToken, uint amountETH);
      function removeLiquidityWithPermit(
          address tokenA,
          address tokenB,
          uint liquidity,
          uint amountAMin,
          uint amountBMin,
          address to,
          uint deadline,
          bool approveMax, uint8 v, bytes32 r, bytes32 s
      ) external returns (uint amountA, uint amountB);
      function removeLiquidityETHWithPermit(
          address token,
          uint liquidity,
          uint amountTokenMin,
          uint amountETHMin,
          address to,
          uint deadline,
          bool approveMax, uint8 v, bytes32 r, bytes32 s
      ) external returns (uint amountToken, uint amountETH);
      function swapExactTokensForTokens(
          uint amountIn,
          uint amountOutMin,
          address[] calldata path,
          address to,
          uint deadline
      ) external returns (uint[] memory amounts);
      function swapTokensForExactTokens(
          uint amountOut,
          uint amountInMax,
          address[] calldata path,
          address to,
          uint deadline
      ) external returns (uint[] memory amounts);
      function swapExactETHForTokens(uint amountOutMin, address[] calldata path, address to, uint deadline)
          external
          payable
          returns (uint[] memory amounts);
      function swapTokensForExactETH(uint amountOut, uint amountInMax, address[] calldata path, address to, uint deadline)
          external
          returns (uint[] memory amounts);
      function swapExactTokensForETH(uint amountIn, uint amountOutMin, address[] calldata path, address to, uint deadline)
          external
          returns (uint[] memory amounts);
      function swapETHForExactTokens(uint amountOut, address[] calldata path, address to, uint deadline)
          external
          payable
          returns (uint[] memory amounts);
  
      function quote(uint amountA, uint reserveA, uint reserveB) external pure returns (uint amountB);
      function getAmountOut(uint amountIn, uint reserveIn, uint reserveOut) external pure returns (uint amountOut);
      function getAmountIn(uint amountOut, uint reserveIn, uint reserveOut) external pure returns (uint amountIn);
      function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
      function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts);
  }
  
  // File @uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol@v1.1.0-beta.0
  
  pragma solidity >=0.6.2;
  
  interface IUniswapV2Router02 is IUniswapV2Router01 {
      function removeLiquidityETHSupportingFeeOnTransferTokens(
          address token,
          uint liquidity,
          uint amountTokenMin,
          uint amountETHMin,
          address to,
          uint deadline
      ) external returns (uint amountETH);
      function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
          address token,
          uint liquidity,
          uint amountTokenMin,
          uint amountETHMin,
          address to,
          uint deadline,
          bool approveMax, uint8 v, bytes32 r, bytes32 s
      ) external returns (uint amountETH);
  
      function swapExactTokensForTokensSupportingFeeOnTransferTokens(
          uint amountIn,
          uint amountOutMin,
          address[] calldata path,
          address to,
          uint deadline
      ) external;
      function swapExactETHForTokensSupportingFeeOnTransferTokens(
          uint amountOutMin,
          address[] calldata path,
          address to,
          uint deadline
      ) external payable;
      function swapExactTokensForETHSupportingFeeOnTransferTokens(
          uint amountIn,
          uint amountOutMin,
          address[] calldata path,
          address to,
          uint deadline
      ) external;
  }
  
  // File @openzeppelin/contracts/utils/Context.sol@v4.9.3
  
  // OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Provides information about the current execution context, including the
   * sender of the transaction and its data. While these are generally available
   * via msg.sender and msg.data, they should not be accessed in such a direct
   * manner, since when dealing with meta-transactions the account sending and
   * paying for execution may not be the actual sender (as far as an application
   * is concerned).
   *
   * This contract is only required for intermediate, library-like contracts.
   */
  abstract contract Context {
      function _msgSender() internal view virtual returns (address) {
          return msg.sender;
      }
  
      function _msgData() internal view virtual returns (bytes calldata) {
          return msg.data;
      }
  }
  
  // File @openzeppelin/contracts/utils/structs/EnumerableSet.sol@v4.9.3
  
  // OpenZeppelin Contracts (last updated v4.9.0) (utils/structs/EnumerableSet.sol)
  // This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Library for managing
   * https://en.wikipedia.org/wiki/Set_(abstract_data_type)[sets] of primitive
   * types.
   *
   * Sets have the following properties:
   *
   * - Elements are added, removed, and checked for existence in constant time
   * (O(1)).
   * - Elements are enumerated in O(n). No guarantees are made on the ordering.
   *
   * ```solidity
   * contract Example {
   *     // Add the library methods
   *     using EnumerableSet for EnumerableSet.AddressSet;
   *
   *     // Declare a set state variable
   *     EnumerableSet.AddressSet private mySet;
   * }
   * ```
   *
   * As of v3.3.0, sets of type `bytes32` (`Bytes32Set`), `address` (`AddressSet`)
   * and `uint256` (`UintSet`) are supported.
   *
   * [WARNING]
   * ====
   * Trying to delete such a structure from storage will likely result in data corruption, rendering the structure
   * unusable.
   * See https://github.com/ethereum/solidity/pull/11843[ethereum/solidity#11843] for more info.
   *
   * In order to clean an EnumerableSet, you can either remove all elements one by one or create a fresh instance using an
   * array of EnumerableSet.
   * ====
   */
  library EnumerableSet {
      // To implement this library for multiple types with as little code
      // repetition as possible, we write it in terms of a generic Set type with
      // bytes32 values.
      // The Set implementation uses private functions, and user-facing
      // implementations (such as AddressSet) are just wrappers around the
      // underlying Set.
      // This means that we can only create new EnumerableSets for types that fit
      // in bytes32.
  
      struct Set {
          // Storage of set values
          bytes32[] _values;
          // Position of the value in the `values` array, plus 1 because index 0
          // means a value is not in the set.
          mapping(bytes32 => uint256) _indexes;
      }
  
      /**
       * @dev Add a value to a set. O(1).
       *
       * Returns true if the value was added to the set, that is if it was not
       * already present.
       */
      function _add(Set storage set, bytes32 value) private returns (bool) {
          if (!_contains(set, value)) {
              set._values.push(value);
              // The value is stored at length-1, but we add 1 to all indexes
              // and use 0 as a sentinel value
              set._indexes[value] = set._values.length;
              return true;
          } else {
              return false;
          }
      }
  
      /**
       * @dev Removes a value from a set. O(1).
       *
       * Returns true if the value was removed from the set, that is if it was
       * present.
       */
      function _remove(Set storage set, bytes32 value) private returns (bool) {
          // We read and store the value's index to prevent multiple reads from the same storage slot
          uint256 valueIndex = set._indexes[value];
  
          if (valueIndex != 0) {
              // Equivalent to contains(set, value)
              // To delete an element from the _values array in O(1), we swap the element to delete with the last one in
              // the array, and then remove the last element (sometimes called as 'swap and pop').
              // This modifies the order of the array, as noted in {at}.
  
              uint256 toDeleteIndex = valueIndex - 1;
              uint256 lastIndex = set._values.length - 1;
  
              if (lastIndex != toDeleteIndex) {
                  bytes32 lastValue = set._values[lastIndex];
  
                  // Move the last value to the index where the value to delete is
                  set._values[toDeleteIndex] = lastValue;
                  // Update the index for the moved value
                  set._indexes[lastValue] = valueIndex; // Replace lastValue's index to valueIndex
              }
  
              // Delete the slot where the moved value was stored
              set._values.pop();
  
              // Delete the index for the deleted slot
              delete set._indexes[value];
  
              return true;
          } else {
              return false;
          }
      }
  
      /**
       * @dev Returns true if the value is in the set. O(1).
       */
      function _contains(Set storage set, bytes32 value) private view returns (bool) {
          return set._indexes[value] != 0;
      }
  
      /**
       * @dev Returns the number of values on the set. O(1).
       */
      function _length(Set storage set) private view returns (uint256) {
          return set._values.length;
      }
  
      /**
       * @dev Returns the value stored at position `index` in the set. O(1).
       *
       * Note that there are no guarantees on the ordering of values inside the
       * array, and it may change when more values are added or removed.
       *
       * Requirements:
       *
       * - `index` must be strictly less than {length}.
       */
      function _at(Set storage set, uint256 index) private view returns (bytes32) {
          return set._values[index];
      }
  
      /**
       * @dev Return the entire set in an array
       *
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
       */
      function _values(Set storage set) private view returns (bytes32[] memory) {
          return set._values;
      }
  
      // Bytes32Set
  
      struct Bytes32Set {
          Set _inner;
      }
  
      /**
       * @dev Add a value to a set. O(1).
       *
       * Returns true if the value was added to the set, that is if it was not
       * already present.
       */
      function add(Bytes32Set storage set, bytes32 value) internal returns (bool) {
          return _add(set._inner, value);
      }
  
      /**
       * @dev Removes a value from a set. O(1).
       *
       * Returns true if the value was removed from the set, that is if it was
       * present.
       */
      function remove(Bytes32Set storage set, bytes32 value) internal returns (bool) {
          return _remove(set._inner, value);
      }
  
      /**
       * @dev Returns true if the value is in the set. O(1).
       */
      function contains(Bytes32Set storage set, bytes32 value) internal view returns (bool) {
          return _contains(set._inner, value);
      }
  
      /**
       * @dev Returns the number of values in the set. O(1).
       */
      function length(Bytes32Set storage set) internal view returns (uint256) {
          return _length(set._inner);
      }
  
      /**
       * @dev Returns the value stored at position `index` in the set. O(1).
       *
       * Note that there are no guarantees on the ordering of values inside the
       * array, and it may change when more values are added or removed.
       *
       * Requirements:
       *
       * - `index` must be strictly less than {length}.
       */
      function at(Bytes32Set storage set, uint256 index) internal view returns (bytes32) {
          return _at(set._inner, index);
      }
  
      /**
       * @dev Return the entire set in an array
       *
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
       */
      function values(Bytes32Set storage set) internal view returns (bytes32[] memory) {
          bytes32[] memory store = _values(set._inner);
          bytes32[] memory result;
  
          /// @solidity memory-safe-assembly
          assembly {
              result := store
          }
  
          return result;
      }
  
      // AddressSet
  
      struct AddressSet {
          Set _inner;
      }
  
      /**
       * @dev Add a value to a set. O(1).
       *
       * Returns true if the value was added to the set, that is if it was not
       * already present.
       */
      function add(AddressSet storage set, address value) internal returns (bool) {
          return _add(set._inner, bytes32(uint256(uint160(value))));
      }
  
      /**
       * @dev Removes a value from a set. O(1).
       *
       * Returns true if the value was removed from the set, that is if it was
       * present.
       */
      function remove(AddressSet storage set, address value) internal returns (bool) {
          return _remove(set._inner, bytes32(uint256(uint160(value))));
      }
  
      /**
       * @dev Returns true if the value is in the set. O(1).
       */
      function contains(AddressSet storage set, address value) internal view returns (bool) {
          return _contains(set._inner, bytes32(uint256(uint160(value))));
      }
  
      /**
       * @dev Returns the number of values in the set. O(1).
       */
      function length(AddressSet storage set) internal view returns (uint256) {
          return _length(set._inner);
      }
  
      /**
       * @dev Returns the value stored at position `index` in the set. O(1).
       *
       * Note that there are no guarantees on the ordering of values inside the
       * array, and it may change when more values are added or removed.
       *
       * Requirements:
       *
       * - `index` must be strictly less than {length}.
       */
      function at(AddressSet storage set, uint256 index) internal view returns (address) {
          return address(uint160(uint256(_at(set._inner, index))));
      }
  
      /**
       * @dev Return the entire set in an array
       *
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
       */
      function values(AddressSet storage set) internal view returns (address[] memory) {
          bytes32[] memory store = _values(set._inner);
          address[] memory result;
  
          /// @solidity memory-safe-assembly
          assembly {
              result := store
          }
  
          return result;
      }
  
      // UintSet
  
      struct UintSet {
          Set _inner;
      }
  
      /**
       * @dev Add a value to a set. O(1).
       *
       * Returns true if the value was added to the set, that is if it was not
       * already present.
       */
      function add(UintSet storage set, uint256 value) internal returns (bool) {
          return _add(set._inner, bytes32(value));
      }
  
      /**
       * @dev Removes a value from a set. O(1).
       *
       * Returns true if the value was removed from the set, that is if it was
       * present.
       */
      function remove(UintSet storage set, uint256 value) internal returns (bool) {
          return _remove(set._inner, bytes32(value));
      }
  
      /**
       * @dev Returns true if the value is in the set. O(1).
       */
      function contains(UintSet storage set, uint256 value) internal view returns (bool) {
          return _contains(set._inner, bytes32(value));
      }
  
      /**
       * @dev Returns the number of values in the set. O(1).
       */
      function length(UintSet storage set) internal view returns (uint256) {
          return _length(set._inner);
      }
  
      /**
       * @dev Returns the value stored at position `index` in the set. O(1).
       *
       * Note that there are no guarantees on the ordering of values inside the
       * array, and it may change when more values are added or removed.
       *
       * Requirements:
       *
       * - `index` must be strictly less than {length}.
       */
      function at(UintSet storage set, uint256 index) internal view returns (uint256) {
          return uint256(_at(set._inner, index));
      }
  
      /**
       * @dev Return the entire set in an array
       *
       * WARNING: This operation will copy the entire storage to memory, which can be quite expensive. This is designed
       * to mostly be used by view accessors that are queried without any gas fees. Developers should keep in mind that
       * this function has an unbounded cost, and using it as part of a state-changing function may render the function
       * uncallable if the set grows to a point where copying to memory consumes too much gas to fit in a block.
       */
      function values(UintSet storage set) internal view returns (uint256[] memory) {
          bytes32[] memory store = _values(set._inner);
          uint256[] memory result;
  
          /// @solidity memory-safe-assembly
          assembly {
              result := store
          }
  
          return result;
      }
  }
  
  // File @uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol@v1.0.0
  
  pragma solidity >=0.5.0;
  
  interface IUniswapV2Factory {
      event PairCreated(address indexed token0, address indexed token1, address pair, uint);
  
      function feeTo() external view returns (address);
      function feeToSetter() external view returns (address);
  
      function getPair(address tokenA, address tokenB) external view returns (address pair);
      function allPairs(uint) external view returns (address pair);
      function allPairsLength() external view returns (uint);
  
      function createPair(address tokenA, address tokenB) external returns (address pair);
  
      function setFeeTo(address) external;
      function setFeeToSetter(address) external;
  }
  
  // File contracts/ERC20Factory/ERC20/IERC20ConfigByMetadrop.sol
  
  // Metadrop Contracts (v2.1.0)
  
  /**
   *
   * @title IERC20ByMetadrop.sol. Interface for metadrop ERC20 standard
   *
   * @author metadrop
   *
   */
  
  pragma solidity 0.8.21;
  
  interface IERC20ConfigByMetadrop {
    enum DRIPoolType {
      fundingLP,
      initialBuy
    }
  
    struct ERC20Config {
      bytes baseParameters;
      bytes supplyParameters;
      bytes taxParameters;
      bytes poolParameters;
    }
  
    struct ERC20BaseParameters {
      string name;
      string symbol;
      bool addLiquidityOnCreate;
      bool usesDRIPool;
      bytes distribution;
    }
  
    struct ERC20SupplyParameters {
      uint256 maxSupply;
      uint256 lpSupply;
      uint256 maxTokensPerWallet;
      uint256 maxTokensPerTxn;
      uint256 lpLockupInDays;
      uint256 botProtectionDurationInSeconds;
      address projectLPOwner;
      bool burnLPTokens;
    }
  
    struct ERC20TaxParameters {
      uint256 projectBuyTaxBasisPoints;
      uint256 projectSellTaxBasisPoints;
      uint256 taxSwapThresholdBasisPoints;
      uint256 metadropBuyTaxBasisPoints;
      uint256 metadropSellTaxBasisPoints;
      uint256 metadropTaxPeriodInDays;
      address projectTaxRecipient;
      address metadropTaxRecipient;
      uint256 metadropMinBuyTaxBasisPoints;
      uint256 metadropMinSellTaxBasisPoints;
      uint256 metadropBuyTaxProportionBasisPoints;
      uint256 metadropSellTaxProportionBasisPoints;
      uint256 autoBurnDurationInBlocks;
      uint256 autoBurnBasisPoints;
    }
  
    struct ERC20PoolParameters {
      uint256 poolType;
      uint256 poolSupply;
      uint256 poolStartDate;
      uint256 poolEndDate;
      uint256 poolVestingInSeconds;
      uint256 poolMaxETH;
      uint256 poolPerAddressMaxETH;
      uint256 poolMinETH;
      uint256 poolPerTransactionMinETH;
      uint256 poolContributionFeeBasisPoints;
      uint256 poolMaxInitialBuy;
      uint256 poolMaxInitialLiquidity;
      address poolFeeRecipient;
      address poolOwner;
    }
  }
  
  // File contracts/Global/IConfigStructures.sol
  
  // Metadrop Contracts (v2.1.0)
  
  /**
   *
   * @title IConfigStructures.sol. Interface for common config structures used accross the platform
   *
   * @author metadrop
   *
   */
  
  pragma solidity 0.8.21;
  
  interface IConfigStructures {
    enum DropStatus {
      approved,
      deployed,
      cancelled
    }
  
    enum TemplateStatus {
      live,
      terminated
    }
  
    // The current status of the mint:
    //   - notEnabled: This type of mint is not part of this drop
    //   - notYetOpen: This type of mint is part of the drop, but it hasn't started yet
    //   - open: it's ready for ya, get in there.
    //   - finished: been and gone.
    //   - unknown: theoretically impossible.
    enum MintStatus {
      notEnabled,
      notYetOpen,
      open,
      finished,
      unknown
    }
  
    struct SubListConfig {
      uint256 start;
      uint256 end;
      uint256 phaseMaxSupply;
    }
  
    struct PrimarySaleModuleInstance {
      address instanceAddress;
      string instanceDescription;
    }
  
    struct NFTModuleConfig {
      uint256 templateId;
      bytes configData;
      bytes vestingData;
    }
  
    struct PrimarySaleModuleConfig {
      uint256 templateId;
      bytes configData;
    }
  
    struct ProjectBeneficiary {
      address payable payeeAddress;
      uint256 payeeShares;
    }
  
    struct VestingConfig {
      uint256 start;
      uint256 projectUpFrontShare;
      uint256 projectVestedShare;
      uint256 vestingPeriodInDays;
      uint256 vestingCliff;
      ProjectBeneficiary[] projectPayees;
    }
  
    struct RoyaltySplitterModuleConfig {
      uint256 templateId;
      bytes configData;
    }
  
    struct InLifeModuleConfig {
      uint256 templateId;
      bytes configData;
    }
  
    struct InLifeModules {
      InLifeModuleConfig[] modules;
    }
  
    struct NFTConfig {
      uint256 supply;
      string name;
      string symbol;
      bytes32 positionProof;
      bool includePriorPhasesInMintTracking;
      bool singleMetadataCollection;
      uint256 reservedAllocation;
      uint256 assistanceRequestWindowInSeconds;
    }
  
    struct Template {
      TemplateStatus status;
      uint16 templateNumber;
      uint32 loadedDate;
      address payable templateAddress;
      string templateDescription;
    }
  
    struct RoyaltyDetails {
      address newRoyaltyPaymentSplitterInstance;
      uint96 royaltyFromSalesInBasisPoints;
    }
  
    struct SignedDropMessageDetails {
      uint256 messageTimeStamp;
      bytes32 messageHash;
      bytes messageSignature;
    }
  }
  
  // File contracts/ERC20Factory/ERC20/IERC20ByMetadrop.sol
  
  // Metadrop Contracts (v2.1.0)
  
  pragma solidity 0.8.21;
  
  /**
   * @dev Metadrop core ERC-20 contract, interface
   */
  interface IERC20ByMetadrop is
    IConfigStructures,
    IERC20,
    IERC20ConfigByMetadrop,
    IERC20Metadata
  {
    event AutoSwapThresholdUpdated(uint256 oldThreshold, uint256 newThreshold);
  
    event ExternalCallError(uint256 identifier);
  
    event InitialLiquidityAdded(uint256 tokenA, uint256 tokenB, uint256 lpToken);
  
    event LimitsUpdated(
      uint256 oldMaxTokensPerTransaction,
      uint256 newMaxTokensPerTransaction,
      uint256 oldMaxTokensPerWallet,
      uint256 newMaxTokensPerWallet
    );
  
    event LiquidityLocked(
      uint256 lpTokens,
      uint256 lpLockupInDays,
      uint256 streamId
    );
  
    event LiquidityBurned(uint256 lpTokens);
  
    event LiquidityPoolCreated(address addedPool);
  
    event LiquidityPoolAdded(address addedPool);
  
    event LiquidityPoolRemoved(address removedPool);
  
    event MetadropTaxBasisPointsChanged(
      uint256 oldBuyBasisPoints,
      uint256 newBuyBasisPoints,
      uint256 oldSellBasisPoints,
      uint256 newSellBasisPoints
    );
  
    event ProjectTaxBasisPointsChanged(
      uint256 oldBuyBasisPoints,
      uint256 newBuyBasisPoints,
      uint256 oldSellBasisPoints,
      uint256 newSellBasisPoints
    );
  
    event RevenueAutoSwap();
  
    event ProjectTaxRecipientUpdated(address treasury);
  
    event UnlimitedAddressAdded(address addedUnlimted);
  
    event UnlimitedAddressRemoved(address removedUnlimted);
  
    event ValidCallerAdded(bytes32 addedValidCaller);
  
    event ValidCallerRemoved(bytes32 removedValidCaller);
  
    /**
     * @dev function {addInitialLiquidity}
     *
     * Add initial liquidity to the uniswap pair
     *
     * @param vaultFee_ The vault fee in wei. This must match the required fee from the external vault contract.
     * @param lpLockupInDaysOverride_ The number of days to lock liquidity NOTE you can pass 0 to use the stored value.
     * This value is an override, and will override a stored value which is LOWER that it. If the value you are passing is
     * LOWER than the stored value the stored value will not be reduced.
     *
     * Example usage 1: When creating the coin the lpLockupInDays is set to 0. This means that on this call the
     * user can set the lockup to any value they like, as all integer values greater than zero will be used to override
     * that set in storage.
     *
     * Example usage 2: When using a DRI Pool the lockup period is set on this contract and the pool need not know anything
     * about this setting. The pool can pass back a 0 on this call and know that the existing value stored on this contract
     * will be used.
     * @param burnLPTokensOverride_ If the LP tokens should be burned (otherwise they are locked). This is an override field
     * that can ONLY be used to override a held value of FALSE with a new value of TRUE.
     *
     * Example usage 1: When creating the coin the user didn't add liquidity, or specify that the LP tokens were to be burned.
     * So burnLPTokens is held as FALSE. When they add liquidity they want to lock tokens, so they pass this in as FALSE again,
     * and it remains FALSE.
     *
     * Example usage 2: As above, but when later adding liquidity the user wants to burn the LP. So the stored value is FALSE
     * and the user passes TRUE into this method. The TRUE overrides the held value of FALSE and the tokens are burned.
     *
     * Example uusage 3: The user is using a DRI pool and they have specified on the coin creation that the LP tokens are to
     * be burned. This contract therefore holds TRUE for burnLPTokens. The DRI pool does not need to know what the user has
     * selected. It can safely pass back FALSE to this method call and the stored value of TRUE will remain, resulting in the
     * LP tokens being burned.
     */
    function addInitialLiquidity(
      uint256 vaultFee_,
      uint256 lpLockupInDaysOverride_,
      bool burnLPTokensOverride_
    ) external payable;
  
    /**
     * @dev function {isLiquidityPool}
     *
     * Return if an address is a liquidity pool
     *
     * @param queryAddress_ The address being queried
     * @return bool The address is / isn't a liquidity pool
     */
    function isLiquidityPool(address queryAddress_) external view returns (bool);
  
    /**
     * @dev function {liquidityPools}
     *
     * Returns a list of all liquidity pools
     *
     * @return liquidityPools_ a list of all liquidity pools
     */
    function liquidityPools()
      external
      view
      returns (address[] memory liquidityPools_);
  
    /**
     * @dev function {addLiquidityPool} onlyOwner
     *
     * Allows the manager to add a liquidity pool to the pool enumerable set
     *
     * @param newLiquidityPool_ The address of the new liquidity pool
     */
    function addLiquidityPool(address newLiquidityPool_) external;
  
    /**
     * @dev function {removeLiquidityPool} onlyOwner
     *
     * Allows the manager to remove a liquidity pool
     *
     * @param removedLiquidityPool_ The address of the old removed liquidity pool
     */
    function removeLiquidityPool(address removedLiquidityPool_) external;
  
    /**
     * @dev function {isUnlimited}
     *
     * Return if an address is unlimited (is not subject to per txn and per wallet limits)
     *
     * @param queryAddress_ The address being queried
     * @return bool The address is / isn't unlimited
     */
    function isUnlimited(address queryAddress_) external view returns (bool);
  
    /**
     * @dev function {unlimitedAddresses}
     *
     * Returns a list of all unlimited addresses
     *
     * @return unlimitedAddresses_ a list of all unlimited addresses
     */
    function unlimitedAddresses()
      external
      view
      returns (address[] memory unlimitedAddresses_);
  
    /**
     * @dev function {addUnlimited} onlyOwner
     *
     * Allows the manager to add an unlimited address
     *
     * @param newUnlimited_ The address of the new unlimited address
     */
    function addUnlimited(address newUnlimited_) external;
  
    /**
     * @dev function {removeUnlimited} onlyOwner
     *
     * Allows the manager to remove an unlimited address
     *
     * @param removedUnlimited_ The address of the old removed unlimited address
     */
    function removeUnlimited(address removedUnlimited_) external;
  
    /**
     * @dev function {setProjectTaxRecipient} onlyOwner
     *
     * Allows the manager to set the project tax recipient address
     *
     * @param projectTaxRecipient_ New recipient address
     */
    function setProjectTaxRecipient(address projectTaxRecipient_) external;
  
    /**
     * @dev function {setSwapThresholdBasisPoints} onlyOwner
     *
     * Allows the manager to set the autoswap threshold
     *
     * @param swapThresholdBasisPoints_ New swap threshold in basis points
     */
    function setSwapThresholdBasisPoints(
      uint16 swapThresholdBasisPoints_
    ) external;
  
    /**
     * @dev function {setProjectTaxRates} onlyOwner
     *
     * Change the tax rates, subject to only ever decreasing
     *
     * @param newProjectBuyTaxBasisPoints_ The new buy tax rate
     * @param newProjectSellTaxBasisPoints_ The new sell tax rate
     */
    function setProjectTaxRates(
      uint16 newProjectBuyTaxBasisPoints_,
      uint16 newProjectSellTaxBasisPoints_
    ) external;
  
    /**
     * @dev function {setLimits} onlyOwner
     *
     * Change the limits on transactions and holdings
     *
     * @param newMaxTokensPerTransaction_ The new per txn limit
     * @param newMaxTokensPerWallet_ The new tokens per wallet limit
     */
    function setLimits(
      uint256 newMaxTokensPerTransaction_,
      uint256 newMaxTokensPerWallet_
    ) external;
  
    /**
     * @dev function {limitsEnforced}
     *
     * Return if limits are enforced on this contract
     *
     * @return bool : they are / aren't
     */
    function limitsEnforced() external view returns (bool);
  
    /**
     * @dev getMetadropBuyTaxBasisPoints
     *
     * Return the metadrop buy tax basis points given the timed expiry
     */
    function getMetadropBuyTaxBasisPoints() external view returns (uint256);
  
    /**
     * @dev getMetadropSellTaxBasisPoints
     *
     * Return the metadrop sell tax basis points given the timed expiry
     */
    function getMetadropSellTaxBasisPoints() external view returns (uint256);
  
    /**
     * @dev totalBuyTaxBasisPoints
     *
     * Provide easy to view tax total:
     */
    function totalBuyTaxBasisPoints() external view returns (uint256);
  
    /**
     * @dev totalSellTaxBasisPoints
     *
     * Provide easy to view tax total:
     */
    function totalSellTaxBasisPoints() external view returns (uint256);
  
    /**
     * @dev distributeTaxTokens
     *
     * Allows the distribution of tax tokens to the designated recipient(s)
     *
     * As part of standard processing the tax token balance being above the threshold
     * will trigger an autoswap to ETH and distribution of this ETH to the designated
     * recipients. This is automatic and there is no need for user involvement.
     *
     * As part of this swap there are a number of calculations performed, particularly
     * if the tax balance is above MAX_SWAP_THRESHOLD_MULTIPLE.
     *
     * Testing indicates that these calculations are safe. But given the data / code
     * interactions it remains possible that some edge case set of scenarios may cause
     * an issue with these calculations.
     *
     * This method is therefore provided as a 'fallback' option to safely distribute
     * accumulated taxes from the contract, with a direct transfer of the ERC20 tokens
     * themselves.
     */
    function distributeTaxTokens() external;
  
    /**
     * @dev function {rescueETH} onlyOwner
     *
     * A withdraw function to allow ETH to be rescued.
     *
     * This contract should never hold ETH. The only envisaged scenario where
     * it might hold ETH is a failed autoswap where the uniswap swap has completed,
     * the recipient of ETH reverts, the contract then wraps to WETH and the
     * wrap to WETH fails.
     *
     * This feels unlikely. But, for safety, we include this method.
     *
     * @param amount_ The amount to withdraw
     */
    function rescueETH(uint256 amount_) external;
  
    /**
     * @dev function {rescueERC20}
     *
     * A withdraw function to allow ERC20s (except address(this)) to be rescued.
     *
     * This contract should never hold ERC20s other than tax tokens. The only envisaged
     * scenario where it might hold an ERC20 is a failed autoswap where the uniswap swap
     * has completed, the recipient of ETH reverts, the contract then wraps to WETH, the
     * wrap to WETH succeeds, BUT then the transfer of WETH fails.
     *
     * This feels even less likely than the scenario where ETH is held on the contract.
     * But, for safety, we include this method.
     *
     * @param token_ The ERC20 contract
     * @param amount_ The amount to withdraw
     */
    function rescueERC20(address token_, uint256 amount_) external;
  
    /**
     * @dev function {rescueExcessToken}
     *
     * A withdraw function to allow ERC20s from this address that are above
     * the accrued tax balance to be rescued.
     */
    function rescueExcessToken(uint256 amount_) external;
  
    /**
     * @dev Destroys a `value` amount of tokens from the caller.
     *
     * See {ERC20-_burn}.
     */
    function burn(uint256 value) external;
  
    /**
     * @dev Destroys a `value` amount of tokens from `account`, deducting from
     * the caller's allowance.
     *
     * See {ERC20-_burn} and {ERC20-allowance}.
     *
     * Requirements:
     *
     * - the caller must have allowance for ``accounts``'s tokens of at least
     * `value`.
     */
    function burnFrom(address account, uint256 value) external;
  }
  
  // File contracts/Global/IErrors.sol
  
  // Metadrop Contracts (v2.1.0)
  
  /**
   *
   * @title IErrors.sol. Interface for error definitions used across the platform
   *
   * @author metadrop
   *
   */
  
  pragma solidity 0.8.21;
  
  interface IErrors {
    enum BondingCurveErrorType {
      OK, //                                                  No error
      INVALID_NUMITEMS, //                                    The numItem value is 0
      SPOT_PRICE_OVERFLOW //                                  The updated spot price doesn't fit into 128 bits
    }
  
    error AdapterParamsMustBeEmpty(); //                      The adapter parameters on this LZ call must be empty.
  
    error AdditionToPoolIsBelowPerTransactionMinimum(); //    The contribution amount is less than the minimum.
  
    error AdditionToPoolWouldExceedPoolCap(); //              This addition to the pool would exceed the pool cap.
  
    error AdditionToPoolWouldExceedPerAddressCap(); //        This addition to the pool would exceed the per address cap.
  
    error AddressAlreadySet(); //                             The address being set can only be set once, and is already non-0.
  
    error AllowanceDecreasedBelowZero(); //                   You cannot decrease the allowance below zero.
  
    error AlreadyInitialised(); //                            The contract is already initialised: it cannot be initialised twice!
  
    error AmountExceedsAvailable(); //                        You are requesting more token than is available.
  
    error ApprovalCallerNotOwnerNorApproved(); //             The caller must own the token or be an approved operator.
  
    error ApproveFromTheZeroAddress(); //                     Approval cannot be called from the zero address (indeed, how have you??).
  
    error ApproveToTheZeroAddress(); //                       Approval cannot be given to the zero address.
  
    error ApprovalQueryForNonexistentToken(); //              The token does not exist.
  
    error AuctionStatusIsNotEnded(); //                       Throw if the action required the auction to be closed, and it isn't.
  
    error AuctionStatusIsNotOpen(); //                        Throw if the action requires the auction to be open, and it isn't.
  
    error AuxCallFailed(
      address[] modules,
      uint256 value,
      bytes data,
      uint256 txGas
    ); //                                                     An auxilliary call from the drop factory failed.
  
    error BalanceMismatch(); //                               An error when comparing balance amounts.
  
    error BalanceQueryForZeroAddress(); //                    Cannot query the balance for the zero address.
  
    error BidMustBeBelowTheFloorWhenReducingQuantity(); //    Only bids that are below the floor can reduce the quantity of the bid.
  
    error BidMustBeBelowTheFloorForRefundDuringAuction(); //  Only bids that are below the floor can be refunded during the auction.
  
    error BondingCurveError(BondingCurveErrorType error); //  An error of the type specified has occured in bonding curve processing.
  
    error botProtectionDurationInSecondsMustFitUint128(); //  botProtectionDurationInSeconds cannot be too large.
  
    error BurnExceedsBalance(); //                            The amount you have selected to burn exceeds the addresses balance.
  
    error BurnFromTheZeroAddress(); //                        Tokens cannot be burned from the zero address. (Also, how have you called this!?!)
  
    error CallerIsNotDepositBoxOwner(); //                    The caller is not the owner of the deposit box.
  
    error CallerIsNotFactory(); //                            The caller of this function must match the factory address in storage.
  
    error CallerIsNotFactoryOrProjectOwner(); //              The caller of this function must match the factory address OR project owner address.
  
    error CallerIsNotFactoryProjectOwnerOrPool(); //          The caller of this function must match the factory address, project owner or pool address.
  
    error CallerIsNotTheFeeRecipient(); //                    The caller is not the fee recipient.
  
    error CallerIsNotTheOwner(); //                           The caller is not the owner of this contract.
  
    error CallerIsNotTheManager(); //                         The caller is not the manager of this contract.
  
    error CallerMustBeLzApp(); //                             The caller must be an LZ application.
  
    error CallerIsNotPlatformAdmin(address caller); //        The caller of this function must be part of the platformAdmin group.
  
    error CallerIsNotSuperAdmin(address caller); //           The caller of this function must match the superAdmin address in storage.
  
    error CannotAddLiquidityOnCreateAndUseDRIPool(); //       Cannot use both liquidity added on create and a DRIPool in the same token.
  
    error CannotManuallyFundLPWhenUsingADRIPool(); //         Cannot add liquidity manually when using a DRI pool.
  
    error CannotPerformDuringAutoswap(); //                   Cannot call this function during an autoswap.
  
    error CannotPerformPriorToFunding(); //                   Cannot perform this operation before a token is funded (i.e. liquidity added).
  
    error CannotSetNewOwnerToTheZeroAddress(); //             You can't set the owner of this contract to the zero address (address(0)).
  
    error CannotSetToZeroAddress(); //                        The corresponding address cannot be set to the zero address (address(0)).
  
    error CannotSetNewManagerToTheZeroAddress(); //           Cannot transfer the manager to the zero address (address(0)).
  
    error CannotWithdrawThisToken(); //                       Cannot withdraw the specified token.
  
    error CanOnlyReduce(); //                                 The given operation can only reduce the value specified.
  
    error CollectionAlreadyRevealed(); //                     The collection is already revealed; you cannot call reveal again.
  
    error ContractIsDecommissioned(); //                      This contract is decommissioned!
  
    error ContractIsPaused(); //                              The call requires the contract to be unpaused, and it is paused.
  
    error ContractIsNotPaused(); //                           The call required the contract to be paused, and it is NOT paused.
  
    error DecreasedAllowanceBelowZero(); //                   The request would decrease the allowance below zero, and that is not allowed.
  
    error DestinationIsNotTrustedSource(); //                 The destination that is being called through LZ has not been set as trusted.
  
    error DeductionsOnBuyExceedOrEqualOneHundredPercent(); // The total of all buy deductions cannot equal or exceed 100%.
  
    error DeployerOnly(); //                                  This method can only be called by the deployer address.
  
    error DeploymentError(); //                               Error on deployment.
  
    error DepositBoxIsNotOpen(); //                           This action cannot complete as the deposit box is not open.
  
    error DriPoolAddressCannotBeAddressZero(); //             The Dri Pool address cannot be the zero address.
  
    error GasLimitIsTooLow(); //                              The gas limit for the LayerZero call is too low.
  
    error IncorrectConfirmationValue(); //                    You need to enter the right confirmation value to call this funtion (usually 69420).
  
    error IncorrectPayment(); //                              The function call did not include passing the correct payment.
  
    error InitialLiquidityAlreadyAdded(); //                  Initial liquidity has already been added. You can't do it again.
  
    error InitialLiquidityNotYetAdded(); //                   Initial liquidity needs to have been added for this to succedd.
  
    error InsufficientAllowance(); //                         There is not a high enough allowance for this operation.
  
    error InvalidAdapterParams(); //                          The current adapter params for LayerZero on this contract won't work :(.
  
    error InvalidAddress(); //                                An address being processed in the function is not valid.
  
    error InvalidEndpointCaller(); //                         The calling address is not a valid LZ endpoint. The LZ endpoint was set at contract creation
    //                                                        and cannot be altered after. Check the address LZ endpoint address on the contract.
  
    error InvalidHash(); //                                   The passed hash does not meet requirements.
  
    error InvalidMinGas(); //                                 The minimum gas setting for LZ in invalid.
  
    error InvalidOracleSignature(); //                        The signature provided with the contract call is not valid, either in format or signer.
  
    error InvalidPayload(); //                                The LZ payload is invalid
  
    error InvalidReceiver(); //                               The address used as a target for funds is not valid.
  
    error InvalidSourceSendingContract(); //                  The LZ message is being related from a source contract on another chain that is NOT trusted.
  
    error InvalidTotalShares(); //                            Total shares must equal 100 percent in basis points.
  
    error LimitsCanOnlyBeRaised(); //                         Limits are UP ONLY.
  
    error LimitTooHigh(); //                                  The limit has been set too high.
  
    error ListLengthMismatch(); //                            Two or more lists were compared and they did not match length.
  
    error LiquidityPoolMustBeAContractAddress(); //           Cannot add a non-contract as a liquidity pool.
  
    error LiquidityPoolCannotBeAddressZero(); //              Cannot add a liquidity pool from the zero address.
  
    error LPLockUpMustFitUint88(); //                         LP lockup is held in a uint88, so must fit.
  
    error NoTrustedPathRecord(); //                           LZ needs a trusted path record for this to work. What's that, you ask?
  
    error MachineAddressCannotBeAddressZero(); //             Cannot set the machine address to the zero address.
  
    error ManagerUnauthorizedAccount(); //                    The caller is not the pending manager.
  
    error MaxBidQuantityIs255(); //                           Validation: as we use a uint8 array to track bid positions the max bid quantity is 255.
  
    error MaxBuysPerBlockExceeded(); //                       You have exceeded the max buys per block.
  
    error MaxPublicMintAllowanceExceeded(
      uint256 requested,
      uint256 alreadyMinted,
      uint256 maxAllowance
    ); //                                                     The calling address has requested a quantity that would exceed the max allowance.
  
    error MaxSupplyTooHigh(); //                              Max supply must fit in a uint128.
  
    error MaxTokensPerWalletExceeded(); //                    The transfer would exceed the max tokens per wallet limit.
  
    error MaxTokensPerTxnExceeded(); //                       The transfer would exceed the max tokens per transaction limit.
  
    error MetadataIsLocked(); //                              The metadata on this contract is locked; it cannot be altered!
  
    error MetadropFactoryOnlyOncePerReveal(); //              This function can only be called (a) by the factory and, (b) just one time!
  
    error MetadropModulesOnly(); //                           Can only be called from a metadrop contract.
  
    error MetadropOracleCannotBeAddressZero(); //             The metadrop Oracle cannot be the zero address (address(0)).
  
    error MinETHCannotExceedMaxBuy(); //                      The min ETH amount cannot exceed the max buy amount.
  
    error MinETHCannotExceedMaxLiquidity(); //                The min ETH amount cannot exceed the max liquidity amount.
  
    error MinGasLimitNotSet(); //                             The minimum gas limit for LayerZero has not been set.
  
    error MintERC2309QuantityExceedsLimit(); //               The `quantity` minted with ERC2309 exceeds the safety limit.
  
    error MintingIsClosedForever(); //                        Minting is, as the error suggests, so over (and locked forever).
  
    error MintToZeroAddress(); //                             Cannot mint to the zero address.
  
    error MintZeroQuantity(); //                              The quantity of tokens minted must be more than zero.
  
    error NewBuyTaxBasisPointsExceedsMaximum(); //            Project owner trying to set the tax rate too high.
  
    error NewSellTaxBasisPointsExceedsMaximum(); //           Project owner trying to set the tax rate too high.
  
    error NoETHForLiquidityPair(); //                         No ETH has been provided for the liquidity pair.
  
    error TaxPeriodStillInForce(); //                         The minimum tax period has not yet expired.
  
    error NoPaymentDue(); //                                  No payment is due for this address.
  
    error NoRefundForCaller(); //                             Error thrown when the calling address has no refund owed.
  
    error NoStoredMessage(); //                               There is no stored message matching the passed parameters.
  
    error NothingToClaim(); //                                The calling address has nothing to claim.
  
    error NoTokenForLiquidityPair(); //                       There is no token to add to the LP.
  
    error OperationDidNotSucceed(); //                        The operation failed (vague much?).
  
    error OracleSignatureHasExpired(); //                     A signature has been provided but it is too old.
  
    error OwnableUnauthorizedAccount(); //                    The caller is not the pending owner.
  
    error OwnershipNotInitializedForExtraData(); //           The `extraData` cannot be set on an uninitialized ownership slot.
  
    error OwnerQueryForNonexistentToken(); //                 The token does not exist.
  
    error ParametersDoNotMatchSignedMessage(); //             The parameters passed with the signed message do not match the message itself.
  
    error ParamTooLargeStartDate(); //                        The passed parameter exceeds the var type max.
  
    error ParamTooLargeEndDate(); //                          The passed parameter exceeds the var type max.
  
    error ParamTooLargeMinETH(); //                           The passed parameter exceeds the var type max.
  
    error ParamTooLargePerAddressMax(); //                    The passed parameter exceeds the var type max.
  
    error ParamTooLargeVestingDays(); //                      The passed parameter exceeds the var type max.
  
    error ParamTooLargePoolSupply(); //                       The passed parameter exceeds the var type max.
  
    error ParamTooLargePoolMaxETH(); //                       The passed parameter exceeds the var type max.
  
    error ParamTooLargePoolPerTxnMinETH(); //                 The passed parameter exceeds the var type max.
  
    error ParamTooLargeContributionFee(); //                  The passed parameter exceeds the var type max.
  
    error ParamTooLargeMaxInitialBuy(); //                    The passed parameter exceeds the var type max.
  
    error ParamTooLargeMaxInitialLiquidity(); //              The passed parameter exceeds the var type max.
  
    error PassedConfigDoesNotMatchApproved(); //              The config provided on the call does not match the approved config.
  
    error PauseCutOffHasPassed(); //                          The time period in which we can pause has passed; this contract can no longer be paused.
  
    error PaymentMustCoverPerMintFee(); //                    The payment passed must at least cover the per mint fee for the quantity requested.
  
    error PermitDidNotSucceed(); //                           The safeERC20 permit failed.
  
    error PlatformAdminCannotBeAddressZero(); //              We cannot use the zero address (address(0)) as a platformAdmin.
  
    error PlatformTreasuryCannotBeAddressZero(); //           The treasury address cannot be set to the zero address.
  
    error PoolCannotBeManuallyStarted(); //                   This pool cannot be manually started.
  
    error PoolIsAboveMinimum(); //                            You required the pool to be below the minimum, and it is not
  
    error PoolIsBelowMinimum(); //                            You required the pool to be above the minimum, and it is not
  
    error PoolMustBeSeededWithETHForInitialLiquidity(); //    You must pass ETH for liquidity with this type of pool.
  
    error PoolPhaseIsNotOpen(); //                            The block.timestamp is either before the pool is open or after it is closed.
  
    error PoolPhaseIsNotFailed(); //                          The pool status must be failed.
  
    error PoolPhaseIsNotSucceeded(); //                       The pool status must be succeeded.
  
    error PoolVestingNotYetComplete(); //                     Tokens in the pool are not yet vested.
  
    error ProjectOwnerCannotBeAddressZero(); //               The project owner has to be a non zero address.
  
    error ProofInvalid(); //                                  The provided proof is not valid with the provided arguments.
  
    error QuantityExceedsRemainingCollectionSupply(); //      The requested quantity would breach the collection supply.
  
    error QuantityExceedsRemainingPhaseSupply(); //           The requested quantity would breach the phase supply.
  
    error QuantityExceedsMaxPossibleCollectionSupply(); //    The requested quantity would breach the maximum trackable supply
  
    error RecipientsAndAmountsMismatch(); //                  The number of recipients and amounts do not match.
  
    error ReferralIdAlreadyUsed(); //                         This referral ID has already been used; they are one use only.
  
    error RequestingMoreThanAvailableBalance(); //             The request exceeds the available balance.
  
    error RequestingMoreThanRemainingAllocation(
      uint256 previouslyMinted,
      uint256 requested,
      uint256 remainingAllocation
    ); //                                                     Number of tokens requested for this mint exceeds the remaining allocation (taking the
    //                                                        original allocation from the list and deducting minted tokens).
  
    error RouterCannotBeZeroAddress(); //                     The router address cannot be Zero.
  
    error RoyaltyFeeWillExceedSalePrice(); //                 The ERC2981 royalty specified will exceed the sale price.
  
    error ShareTotalCannotBeZero(); //                        The total of all the shares cannot be nothing.
  
    error SliceOutOfBounds(); //                              The bytes slice operation was out of bounds.
  
    error SliceOverflow(); //                                 The bytes slice operation overlowed.
  
    error SuperAdminCannotBeAddressZero(); //                 The superAdmin cannot be the sero address (address(0)).
  
    error SupplyTotalMismatch(); //                           The sum of the team supply and lp supply does not match.
  
    error SupportWindowIsNotOpen(); //                        The project owner has not requested support within the support request expiry window.
  
    error SwapThresholdTooLow(); // The select swap threshold is below the minimum.
  
    error TaxFreeAddressCannotBeAddressZero(); //             A tax free address cannot be address(0)
  
    error TemplateCannotBeAddressZero(); //                   The address for a template cannot be address zero (address(0)).
  
    error TemplateNotFound(); //                              There is no template that matches the passed template Id.
  
    error ThisMintIsClosed(); //                              It's over (well, this mint is, anyway).
  
    error TotalSharesMustMatchDenominator(); //               The total of all shares must equal the denominator value.
  
    error TransferAmountExceedsBalance(); //                  The transfer amount exceeds the accounts available balance.
  
    error TransferCallerNotOwnerNorApproved(); //             The caller must own the token or be an approved operator.
  
    error TransferFailed(); //                                The transfer has failed.
  
    error TransferFromIncorrectOwner(); //                    The token must be owned by `from`.
  
    error TransferToNonERC721ReceiverImplementer(); //        Cannot safely transfer to a contract that does not implement the ERC721Receiver interface.
  
    error TransferFromZeroAddress(); //                       Cannot transfer from the zero address. Indeed, this surely is impossible, and likely a waste to check??
  
    error TransferToZeroAddress(); //                         Cannot transfer to the zero address.
  
    error UnrecognisedVRFMode(); //                           Currently supported VRF modes are 0: chainlink and 1: arrng
  
    error UnrecognisedType(); //                              Pool type not found.
  
    error URIQueryForNonexistentToken(); //                   The token does not exist.
  
    error ValueExceedsMaximum(); //                           The value sent exceeds the maximum allowed (super useful explanation huh?).
  
    error VRFCoordinatorCannotBeAddressZero(); //             The VRF coordinator cannot be the zero address (address(0)).
  
    error VestedBalanceExceedsTotalBalance(); //              The vested balance cannot exceed the total balance.
  
    error LPTokensBalanceMismatch(); //                       The balace of LP tokens is not as expected.
  }
  
  // File contracts/ERC20Factory/ERC20Factory/IERC20FactoryByMetadrop.sol
  
  // Metadrop Contracts (v2.1.0)
  
  pragma solidity 0.8.21;
  
  /**
   * @dev Metadrop ERC-20 factory, interface
   */
  interface IERC20FactoryByMetadrop is
    IConfigStructures,
    IErrors,
    IERC20ConfigByMetadrop
  {
    event DriPoolAddressUpdated(address oldAddress, address newAddress);
  
    event ERC20Created(
      string metaId,
      address indexed deployer,
      address contractInstance,
      address driPoolInstance,
      string symbol,
      string name,
      bytes constructorArgs
    );
  
    event MachineAddressUpdated(address oldAddress, address newAddress);
  
    event OracleAddressUpdated(address oldAddress, address newAddress);
  
    event MessageValidityInSecondsUpdated(
      uint256 oldMessageValidityInSeconds,
      uint256 newMessageValidityInSeconds
    );
  
    event PlatformTreasuryUpdated(address oldAddress, address newAddress);
  
    /**
     * @dev function {initialiseMachineAddress}
     *
     * Initialise the machine template address. This needs to be separate from
     * the constructor as the machine needs the factory address on its constructor.
     *
     * This must ALWAYS be called as part of deployment.
     *
     * @param machineTemplate_ the machine address
     */
    function initialiseMachineAddress(address machineTemplate_) external;
  
    /**
     * @dev function {decommissionFactory} onlySuperAdmin
     *
     * Make this factory unusable for creating new ERC20s, forever
     *
     */
    function decommissionFactory() external;
  
    /**
     * @dev function {setMetadropOracleAddress} onlyPlatformAdmin
     *
     * Set the metadrop trusted oracle address
     *
     * @param metadropOracleAddress_ Trusted metadrop oracle address
     */
    function setMetadropOracleAddress(address metadropOracleAddress_) external;
  
    /**
     * @dev function {setMessageValidityInSeconds} onlyPlatformAdmin
     *
     * Set the validity period of signed messages
     *
     * @param messageValidityInSeconds_ Validity period in seconds for messages signed by the trusted oracle
     */
    function setMessageValidityInSeconds(
      uint256 messageValidityInSeconds_
    ) external;
  
    /**
     * @dev function {setPlatformTreasury} onlySuperAdmin
     *
     * Set the address that platform fees will be paid to / can be withdrawn to.
     * Note that this is restricted to the highest authority level, the super
     * admin. Platform admins can trigger a withdrawal to the treasury, but only
     * the default admin can set or alter the treasury address. It is recommended
     * that the default admin is highly secured and restrited e.g. a multi-sig.
     *
     * @param platformTreasury_ New treasury address
     */
    function setPlatformTreasury(address platformTreasury_) external;
  
    /**
     * @dev function {setMachineAddress} onlyPlatformAdmin
     *
     * Set a new machine template address
     *
     * @param newMachineAddress_ the new machine address
     */
    function setMachineAddress(address newMachineAddress_) external;
  
    /**
     * @dev function {setDriPoolAddress} onlyPlatformAdmin
     *
     * Set a new launch pool template address
     *
     * @param newDriPoolAddress_ the new launch pool address
     */
    function setDriPoolAddress(address newDriPoolAddress_) external;
  
    /**
     * @dev function {withdrawETH} onlyPlatformAdmin
     *
     * A withdraw function to allow ETH to be withdrawn to the treasury
     *
     * @param amount_ The amount to withdraw
     */
    function withdrawETH(uint256 amount_) external;
  
    /**
     * @dev function {withdrawERC20} onlyPlatformAdmin
     *
     * A withdraw function to allow ERC20s to be withdrawn to the treasury
     *
     * @param token_ The contract address of the token being withdrawn
     * @param amount_ The amount to withdraw
     */
    function withdrawERC20(IERC20 token_, uint256 amount_) external;
  
    /**
     * @dev function {createERC20}
     *
     * Create an ERC-20
     *
     * @param metaId_ The drop Id being approved
     * @param salt_ Salt for create2
     * @param erc20Config_ ERC20 configuration
     * @param signedMessage_ The signed message object
     * @param vaultFee_ The fee for the token vault
     * @param deploymentFee_ The fee for deployment, if any
     * @return deployedAddress_ The deployed ERC20 contract address
     */
    function createERC20(
      string calldata metaId_,
      bytes32 salt_,
      ERC20Config calldata erc20Config_,
      SignedDropMessageDetails calldata signedMessage_,
      uint256 vaultFee_,
      uint256 deploymentFee_
    ) external payable returns (address deployedAddress_);
  
    /**
     * @dev function {createConfigHash}
     *
     * Create the config hash
     *
     * @param metaId_ The drop Id being approved
     * @param salt_ Salt for create2
     * @param erc20Config_ ERC20 configuration
     * @param messageTimeStamp_ When the message for this config hash was signed
     * @param vaultFee_ The fee for the token vault
     * @param deploymentFee_ The fee for deployment, if any
     * @param deployer_ Address performing the deployment
     * @return configHash_ The bytes32 config hash
     */
    function createConfigHash(
      string calldata metaId_,
      bytes32 salt_,
      ERC20Config calldata erc20Config_,
      uint256 messageTimeStamp_,
      uint256 vaultFee_,
      uint256 deploymentFee_,
      address deployer_
    ) external pure returns (bytes32 configHash_);
  }
  
  // File contracts/ERC20Factory/ERC20Pools/IERC20DRIPoolByMetadrop.sol
  
  // Metadrop Contracts (v2.1.0)
  
  pragma solidity 0.8.21;
  
  interface IERC20DRIPoolByMetadrop is
    IConfigStructures,
    IERC20ConfigByMetadrop,
    IErrors
  {
    enum PhaseStatus {
      before,
      open,
      succeeded,
      failed
    }
  
    struct Participant {
      uint128 contribution;
      uint128 excessRefunded;
    }
  
    event DRIPoolCreatedAndInitialised();
  
    event AddToPool(address dripHolder, uint256 ethPooled, uint256 ethFee);
  
    event ClaimFromPool(
      address participant,
      uint256 dripTokenBurned,
      uint256 pooledTokenClaimed,
      uint256 pooledTokenBurnt,
      uint256 ethRefunded
    );
  
    event ExcessRefunded(address participant, uint256 ethRefunded);
  
    event RefundFromFailedPool(
      address participant,
      uint256 dripTokenBurned,
      uint256 ethRefunded
    );
  
    event InitialBuyMade(uint256 ethBuy);
  
    event UnexpectedTotalETHPooled(
      uint256 totalETHPooled,
      uint256 contractBalance,
      uint256 totalETHFundedToLPAndTokenBuy,
      uint256 totalExcessETHRefunded,
      uint256 projectSeedContributionETH,
      uint256 accumulatedFees
    );
  
    event PoolClosedSuccessfully(uint256 totalETHPooled, uint256 totalETHFee);
  
    /**
     * @dev {driType}
     *
     * Returns the type of this DRI pool
     */
    function driType() external view returns (DRIPoolType);
  
    /**
     * @dev {initialiseDRIP}
     *
     * Initalise configuration on a new minimal proxy clone
     *
     * @param poolParams_ bytes parameter object that will be decoded into configuration items.
     * @param name_ the name of the associated ERC20 token
     * @param symbol_ the symbol of the associated ERC20 token
     */
    function initialiseDRIP(
      bytes calldata poolParams_,
      string calldata name_,
      string calldata symbol_
    ) external;
  
    /**
     * @dev {supplyForLP}
     *
     * Convenience function to return the LP supply from the ERC-20 token contract.
     *
     * @return supplyForLP_ The total supply for LP creation.
     */
    function supplyForLP() external view returns (uint256 supplyForLP_);
  
    /**
     * @dev {poolPhaseStatus}
     *
     * Convenience function to return the pool status in string format.
     *
     * @return poolPhaseStatus_ The pool phase status as a string
     */
    function poolPhaseStatus()
      external
      view
      returns (string memory poolPhaseStatus_);
  
    /**
     * @dev {vestingEndDate}
     *
     * The vesting end date, being the end of the pool phase plus number of days vesting, if any.
     *
     * @return vestingEndDate_ The vesting end date as a timestamp
     */
    function vestingEndDate() external view returns (uint256 vestingEndDate_);
  
    /**
     * @dev Return if the pool total has exceeded the minimum:
     *
     * @return poolIsAboveMinimum_ If the pool is above the minimum (or not)
     */
    function poolIsAboveMinimum()
      external
      view
      returns (bool poolIsAboveMinimum_);
  
    /**
     * @dev Return if the pool is at the maximum.
     *
     * @return poolIsAtMaximum_ If the pool is at the maximum ETH.
     */
    function poolIsAtMaximum() external view returns (bool poolIsAtMaximum_);
  
    /**
     * @dev Return the total ETH pooled (whether in the balance of this contract
     * or supplied as LP / token buy already).
     *
     * Note that this INCLUDES any seed ETH from the project on create.
     *
     * @return totalETHPooled_ the total ETH pooled in this contract
     */
    function totalETHPooled() external view returns (uint256 totalETHPooled_);
  
    /**
     * @dev Return the total ETH contributed (whether in the balance of this contract
     * or supplied as LP already).
     *
     * Note that this EXCLUDES any seed ETH from the project on create.
     *
     * @return totalETHContributed_ the total ETH pooled in this contract
     */
    function totalETHContributed()
      external
      view
      returns (uint256 totalETHContributed_);
  
    /**
     * @dev Return the total ETH pooled that is in excess of requirements
     *
     * @return totalExcessETHPooled_ the total ETH pooled in this contract
     * that is not needed for the initial lp / buy
     */
    function totalExcessETHPooled()
      external
      view
      returns (uint256 totalExcessETHPooled_);
  
    /**
     * @dev Return the ETH pooled for this recipient
     *
     * @return participantETHPooled_ the total ETH pooled for this address
     */
    function participantETHPooled(
      address participant_
    ) external view returns (uint256 participantETHPooled_);
  
    /**
     * @dev Return the excess ETH already refunded for this recipient
     *
     * @return participantExcessETHRefunded_ the total excess ETH refunded for this participant
     */
    function participantExcessETHRefunded(
      address participant_
    ) external view returns (uint256 participantExcessETHRefunded_);
  
    /**
     * @dev Return the excess refund currently owing for the query address
     *
     * Note that this EXCLUDES any seed ETH from the project on create.
     *
     * @return participantExcessRefund_ the total ETH pooled in this contract
     */
    function participantExcessRefundAvailable(
      address participant_
    ) external view returns (uint256 participantExcessRefund_);
  
    /**
     * @dev Return if the max initial buy has been exceeded
     *
     * @return maxInitialBuyExceeded_
     */
    function maxInitialBuyExceeded()
      external
      view
      returns (bool maxInitialBuyExceeded_);
  
    /**
     * @dev Return if the max initial lp funding has been exceeded
     *
     * @return maxInitialLiquidityExceeded_
     */
    function maxInitialLiquidityExceeded()
      external
      view
      returns (bool maxInitialLiquidityExceeded_);
  
    /**
     * @dev {loadERC20AddressAndSeedETH}
     *
     * Load the target ERC-20 address. This is called by the factory in the same transaction as the clone
     * is instantiated
     *
     * @param createdERC20_ The ERC-20 address
     * @param poolCreator_ The creator of this pool
     */
    function loadERC20AddressAndSeedETH(
      address createdERC20_,
      address poolCreator_
    ) external payable;
  
    /**
     * @dev {startPool}
     *
     * The pool owner starts the pool manually.
     *
     * Note this can only be called by the owner IF the pool was setup with a manual
     * start date. See _setPoolDates
     *
     * @param signedMessage_ The signed message object
     * @param poolDuration_ The desired duration of the pool
     */
    function startPool(
      SignedDropMessageDetails calldata signedMessage_,
      uint256 poolDuration_
    ) external payable;
  
    /**
     * @dev {addToPool}
     *
     * A user calls this to contribute to the pool
     *
     * Note that we could have used the receive method for this, and processed any ETH send to the
     * contract as a contribution to the pool. We've opted for the clarity of a specific method,
     * with the recieve method reverting an unidentified ETH.
     *
     * @param signedMessage_ The signed message object
     */
    function addToPool(
      SignedDropMessageDetails calldata signedMessage_
    ) external payable;
  
    /**
     * @dev function {createAddToPoolMessageHash}
     *
     * Create the message hash
     *
     * @param sender_ The sender of the transcation
     * @param value_ The value of the transaction
     * @return messageHash_ The hash for the signed message
     */
    function createAddToPoolMessageHash(
      address sender_,
      uint256 value_
    ) external view returns (bytes32 messageHash_);
  
    /**
     * @dev function {createStartPoolMessageHash}
     *
     * Create the message hash
     *
     * @param poolDuration_ The duration of the pool
     * @return messageHash_ The hash for the signed message
     */
    function createStartPoolMessageHash(
      uint256 poolDuration_
    ) external view returns (bytes32 messageHash_);
  
    /**
     * @dev {claimFromPool}
     *
     * A user calls this to burn their DRIP and claim their ERC-20 tokens
     *
     */
    function claimFromPool() external;
  
    /**
     * @dev {refundExcess}
     *
     * Can be called at any time by a participant to claim and ETH refund of any
     * ETH that will not be used to either fund the pool or for an initial buy
     *
     */
    function refundExcess() external;
  
    /**
     * @dev {refundFromFailedPool}
     *
     * A user calls this to burn their DRIP and claim an ETH refund where the
     * minimum ETH pooled amount was not exceeded.
     *
     */
    function refundFromFailedPool() external;
  
    /**
     * @dev {supplyLiquidity}
     *
     * When the pool phase is over this can be called to supply the pooled ETH to
     * the token contract. There it will be forwarded along with the LP supply of
     * tokens to uniswap to create the funded pair
     *
     * Note that this function can be called by anyone. While clearly it is likely
     * that this will be the project team, having this method open to anyone ensures that
     * liquidity will not be trapped in this contract if the team as unable to perform
     * this action.
     *
     * This method behaves differently depending on the pool type:
     *
     * IN A FUNDING LP POOL:
     *
     * All of the ETH held on this contract is provided to fund the LP
     *
     * IN AN INITIAL BUY POOL:
     *
     * ONLY the project supplied ETH is used to fund the liquidity. The remaining ETH
     * on this contract will fall into two possible categories:
     *
     * 1) ETH used to perform an initial token purchase immediately after the funding of
     * the LP. This will be the total remaining ETH on this contract IF that amount is
     * below the maximum initial buy amount. Otherwise it will be the max initial buy amount and the
     * remaining ETH will remain for refunds.
     *
     * 2) If the ETH on this contract is above the max initial buy amount there will be a
     * proportion of ETH remaining on this contract for refunds.
     *
     * @param lockerFee_ The ETH fee required to lock LP tokens
     *
     */
    function supplyLiquidity(uint256 lockerFee_) external payable;
  
    /**
     * @dev function {rescueETH}
     *
     * A withdraw function to allow ETH to be rescued.
     *
     * Fallback safety method, only callable by the fee recipient.
     *
     * @param amount_ The amount to withdraw
     */
    function rescueETH(uint256 amount_) external;
  
    /**
     * @dev function {rescueERC20}
     *
     * A withdraw function to allow ERC20s to be rescued.
     *
     * Fallback safety method, only callable by the fee recipient.
     *
     * @param token_ The ERC20 contract
     * @param amount_ The amount to withdraw
     */
    function rescueERC20(address token_, uint256 amount_) external;
  }
  
  // File contracts/Global/Revert.sol
  
  // Metadrop Contracts (v2.1.0)
  
  /**
   *
   * @title Revert.sol. For efficient reverts
   *
   * @author metadrop
   *
   */
  
  pragma solidity 0.8.21;
  
  abstract contract Revert {
    /**
     * @dev For more efficient reverts.
     */
    function _revert(bytes4 errorSelector) internal pure {
      assembly {
        mstore(0x00, errorSelector)
        revert(0x00, 0x04)
      }
    }
  }
  
  // File contracts/Global/OZ/Ownable.sol
  
  // Metadrop Contracts (v2.1.0)
  // Metadrop based on OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
  
  pragma solidity 0.8.21;
  
  /**
   * @dev Contract module which provides a basic access control mechanism, where
   * there is an account (an owner) that can be granted exclusive access to
   * specific functions.
   *
   * By default, the owner account will be the one that deploys the contract. This
   * can later be changed with {transferOwnership}.
   *
   * This module is used through inheritance. It will make available the modifier
   * `onlyOwner`, which can be applied to your functions to restrict their use to
   * the owner.
   */
  abstract contract Ownable is IErrors, Revert, Context {
    address private _owner;
  
    event OwnershipTransferred(
      address indexed previousOwner,
      address indexed newOwner
    );
  
    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
      _transferOwnership(_msgSender());
    }
  
    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
      _checkOwner();
      _;
    }
  
    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
      return _owner;
    }
  
    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
      if (owner() != _msgSender()) {
        _revert(CallerIsNotTheOwner.selector);
      }
    }
  
    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
      _transferOwnership(address(0));
    }
  
    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
      if (newOwner == address(0)) {
        _revert(CannotSetNewOwnerToTheZeroAddress.selector);
      }
      _transferOwnership(newOwner);
    }
  
    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
      address oldOwner = _owner;
      _owner = newOwner;
      emit OwnershipTransferred(oldOwner, newOwner);
    }
  }
  
  // File contracts/Global/OZ/Ownable2Step.sol
  
  // Metadrop Contracts (v2.1.0)
  // Metadrop based on OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable2Step.sol)
  
  pragma solidity 0.8.21;
  
  /**
   * @dev Contract module which provides access control mechanism, where
   * there is an account (an owner) that can be granted exclusive access to
   * specific functions.
   *
   * The initial owner is specified at deployment time in the constructor for `Ownable`. This
   * can later be changed with {transferOwnership} and {acceptOwnership}.
   *
   * This module is used through inheritance. It will make available all functions
   * from parent (Ownable).
   */
  abstract contract Ownable2Step is Ownable {
    address private _pendingOwner;
  
    event OwnershipTransferStarted(
      address indexed previousOwner,
      address indexed newOwner
    );
  
    /**
     * @dev Returns the address of the pending owner.
     */
    function pendingOwner() public view virtual returns (address) {
      return _pendingOwner;
    }
  
    /**
     * @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
     * Can only be called by the current owner.
     */
    function transferOwnership(
      address newOwner
    ) public virtual override onlyOwner {
      _pendingOwner = newOwner;
      emit OwnershipTransferStarted(owner(), newOwner);
    }
  
    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual override {
      delete _pendingOwner;
      super._transferOwnership(newOwner);
    }
  
    /**
     * @dev The new owner accepts the ownership transfer.
     */
    function acceptOwnership() public virtual {
      address sender = _msgSender();
      if (pendingOwner() != sender) {
        _revert(OwnableUnauthorizedAccount.selector);
      }
      _transferOwnership(sender);
    }
  }
  
  // File @openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol@v4.9.3
  
  // OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/extensions/IERC20Permit.sol)
  
  pragma solidity ^0.8.0;
  
  /**
   * @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
   * https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
   *
   * Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
   * presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
   * need to send a transaction, and thus is not required to hold Ether at all.
   */
  interface IERC20Permit {
      /**
       * @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
       * given ``owner``'s signed approval.
       *
       * IMPORTANT: The same issues {IERC20-approve} has related to transaction
       * ordering also apply here.
       *
       * Emits an {Approval} event.
       *
       * Requirements:
       *
       * - `spender` cannot be the zero address.
       * - `deadline` must be a timestamp in the future.
       * - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
       * over the EIP712-formatted function arguments.
       * - the signature must use ``owner``'s current nonce (see {nonces}).
       *
       * For more information on the signature format, see the
       * https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
       * section].
       */
      function permit(
          address owner,
          address spender,
          uint256 value,
          uint256 deadline,
          uint8 v,
          bytes32 r,
          bytes32 s
      ) external;
  
      /**
       * @dev Returns the current nonce for `owner`. This value must be
       * included whenever a signature is generated for {permit}.
       *
       * Every successful call to {permit} increases ``owner``'s nonce by one. This
       * prevents a signature from being used multiple times.
       */
      function nonces(address owner) external view returns (uint256);
  
      /**
       * @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
       */
      // solhint-disable-next-line func-name-mixedcase
      function DOMAIN_SEPARATOR() external view returns (bytes32);
  }
  
  // File @openzeppelin/contracts/utils/Address.sol@v4.9.3
  
  // OpenZeppelin Contracts (last updated v4.9.0) (utils/Address.sol)
  
  pragma solidity ^0.8.1;
  
  /**
   * @dev Collection of functions related to the address type
   */
  library Address {
      /**
       * @dev Returns true if `account` is a contract.
       *
       * [IMPORTANT]
       * ====
       * It is unsafe to assume that an address for which this function returns
       * false is an externally-owned account (EOA) and not a contract.
       *
       * Among others, `isContract` will return false for the following
       * types of addresses:
       *
       *  - an externally-owned account
       *  - a contract in construction
       *  - an address where a contract will be created
       *  - an address where a contract lived, but was destroyed
       *
       * Furthermore, `isContract` will also return true if the target contract within
       * the same transaction is already scheduled for destruction by `SELFDESTRUCT`,
       * which only has an effect at the end of a transaction.
       * ====
       *
       * [IMPORTANT]
       * ====
       * You shouldn't rely on `isContract` to protect against flash loan attacks!
       *
       * Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
       * like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
       * constructor.
       * ====
       */
      function isContract(address account) internal view returns (bool) {
          // This method relies on extcodesize/address.code.length, which returns 0
          // for contracts in construction, since the code is only stored at the end
          // of the constructor execution.
  
          return account.code.length > 0;
      }
  
      /**
       * @dev Replacement for Solidity's `transfer`: sends `amount` wei to
       * `recipient`, forwarding all available gas and reverting on errors.
       *
       * https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
       * of certain opcodes, possibly making contracts go over the 2300 gas limit
       * imposed by `transfer`, making them unable to receive funds via
       * `transfer`. {sendValue} removes this limitation.
       *
       * https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
       *
       * IMPORTANT: because control is transferred to `recipient`, care must be
       * taken to not create reentrancy vulnerabilities. Consider using
       * {ReentrancyGuard} or the
       * https://solidity.readthedocs.io/en/v0.8.0/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
       */
      function sendValue(address payable recipient, uint256 amount) internal {
          require(address(this).balance >= amount, "Address: insufficient balance");
  
          (bool success, ) = recipient.call{value: amount}("");
          require(success, "Address: unable to send value, recipient may have reverted");
      }
  
      /**
       * @dev Performs a Solidity function call using a low level `call`. A
       * plain `call` is an unsafe replacement for a function call: use this
       * function instead.
       *
       * If `target` reverts with a revert reason, it is bubbled up by this
       * function (like regular Solidity function calls).
       *
       * Returns the raw returned data. To convert to the expected return value,
       * use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
       *
       * Requirements:
       *
       * - `target` must be a contract.
       * - calling `target` with `data` must not revert.
       *
       * _Available since v3.1._
       */
      function functionCall(address target, bytes memory data) internal returns (bytes memory) {
          return functionCallWithValue(target, data, 0, "Address: low-level call failed");
      }
  
      /**
       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
       * `errorMessage` as a fallback revert reason when `target` reverts.
       *
       * _Available since v3.1._
       */
      function functionCall(
          address target,
          bytes memory data,
          string memory errorMessage
      ) internal returns (bytes memory) {
          return functionCallWithValue(target, data, 0, errorMessage);
      }
  
      /**
       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
       * but also transferring `value` wei to `target`.
       *
       * Requirements:
       *
       * - the calling contract must have an ETH balance of at least `value`.
       * - the called Solidity function must be `payable`.
       *
       * _Available since v3.1._
       */
      function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
          return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
      }
  
      /**
       * @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
       * with `errorMessage` as a fallback revert reason when `target` reverts.
       *
       * _Available since v3.1._
       */
      function functionCallWithValue(
          address target,
          bytes memory data,
          uint256 value,
          string memory errorMessage
      ) internal returns (bytes memory) {
          require(address(this).balance >= value, "Address: insufficient balance for call");
          (bool success, bytes memory returndata) = target.call{value: value}(data);
          return verifyCallResultFromTarget(target, success, returndata, errorMessage);
      }
  
      /**
       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
       * but performing a static call.
       *
       * _Available since v3.3._
       */
      function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
          return functionStaticCall(target, data, "Address: low-level static call failed");
      }
  
      /**
       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
       * but performing a static call.
       *
       * _Available since v3.3._
       */
      function functionStaticCall(
          address target,
          bytes memory data,
          string memory errorMessage
      ) internal view returns (bytes memory) {
          (bool success, bytes memory returndata) = target.staticcall(data);
          return verifyCallResultFromTarget(target, success, returndata, errorMessage);
      }
  
      /**
       * @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
       * but performing a delegate call.
       *
       * _Available since v3.4._
       */
      function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
          return functionDelegateCall(target, data, "Address: low-level delegate call failed");
      }
  
      /**
       * @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
       * but performing a delegate call.
       *
       * _Available since v3.4._
       */
      function functionDelegateCall(
          address target,
          bytes memory data,
          string memory errorMessage
      ) internal returns (bytes memory) {
          (bool success, bytes memory returndata) = target.delegatecall(data);
          return verifyCallResultFromTarget(target, success, returndata, errorMessage);
      }
  
      /**
       * @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
       * the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
       *
       * _Available since v4.8._
       */
      function verifyCallResultFromTarget(
          address target,
          bool success,
          bytes memory returndata,
          string memory errorMessage
      ) internal view returns (bytes memory) {
          if (success) {
              if (returndata.length == 0) {
                  // only check isContract if the call was successful and the return data is empty
                  // otherwise we already know that it was a contract
                  require(isContract(target), "Address: call to non-contract");
              }
              return returndata;
          } else {
              _revert(returndata, errorMessage);
          }
      }
  
      /**
       * @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
       * revert reason or using the provided one.
       *
       * _Available since v4.3._
       */
      function verifyCallResult(
          bool success,
          bytes memory returndata,
          string memory errorMessage
      ) internal pure returns (bytes memory) {
          if (success) {
              return returndata;
          } else {
              _revert(returndata, errorMessage);
          }
      }
  
      function _revert(bytes memory returndata, string memory errorMessage) private pure {
          // Look for revert reason and bubble it up if present
          if (returndata.length > 0) {
              // The easiest way to bubble the revert reason is using memory via assembly
              /// @solidity memory-safe-assembly
              assembly {
                  let returndata_size := mload(returndata)
                  revert(add(32, returndata), returndata_size)
              }
          } else {
              revert(errorMessage);
          }
      }
  }
  
  // File contracts/Global/OZ/SafeERC20.sol
  
  // Metadrop Contracts (v2.1.0)
  // Metadrop based on OpenZeppelin Contracts (last updated v4.9.0) (token/ERC20/utils/SafeERC20.sol)
  
  pragma solidity 0.8.21;
  
  /**
   * @title SafeERC20
   * @dev Wrappers around ERC20 operations that throw on failure (when the token
   * contract returns false). Tokens that return no value (and instead revert or
   * throw on failure) are also supported, non-reverting calls are assumed to be
   * successful.
   * To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
   * which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
   */
  library SafeERC20 {
    using Address for address;
  
    /**
     * @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeTransfer(IERC20 token, address to, uint256 value) internal {
      _callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
    }
  
    /**
     * @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
     * calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
     */
    function safeTransferFrom(
      IERC20 token,
      address from,
      address to,
      uint256 value
    ) internal {
      _callOptionalReturn(
        token,
        abi.encodeCall(token.transferFrom, (from, to, value))
      );
    }
  
    /**
     * @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeIncreaseAllowance(
      IERC20 token,
      address spender,
      uint256 value
    ) internal {
      uint256 oldAllowance = token.allowance(address(this), spender);
      forceApprove(token, spender, oldAllowance + value);
    }
  
    /**
     * @dev Decrease the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful.
     */
    function safeDecreaseAllowance(
      IERC20 token,
      address spender,
      uint256 value
    ) internal {
      unchecked {
        uint256 oldAllowance = token.allowance(address(this), spender);
        if (oldAllowance < value) {
          revert IErrors.DecreasedAllowanceBelowZero();
        }
        forceApprove(token, spender, oldAllowance - value);
      }
    }
  
    /**
     * @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
     * non-reverting calls are assumed to be successful. Compatible with tokens that require the approval to be set to
     * 0 before setting it to a non-zero value.
     */
    function forceApprove(IERC20 token, address spender, uint256 value) internal {
      bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
  
      if (!_callOptionalReturnBool(token, approvalCall)) {
        _callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
        _callOptionalReturn(token, approvalCall);
      }
    }
  
    /**
     * @dev Use a ERC-2612 signature to set the `owner` approval toward `spender` on `token`.
     * Revert on invalid signature.
     */
    function safePermit(
      IERC20Permit token,
      address owner,
      address spender,
      uint256 value,
      uint256 deadline,
      uint8 v,
      bytes32 r,
      bytes32 s
    ) internal {
      uint256 nonceBefore = token.nonces(owner);
      token.permit(owner, spender, value, deadline, v, r, s);
      uint256 nonceAfter = token.nonces(owner);
      if (nonceAfter != (nonceBefore + 1)) {
        revert IErrors.PermitDidNotSucceed();
      }
    }
  
    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     */
    function _callOptionalReturn(IERC20 token, bytes memory data) private {
      // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
      // we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
      // the target address contains contract code and also asserts for success in the low-level call.
  
      bytes memory returndata = address(token).functionCall(data, "call fail");
      if ((returndata.length != 0) && !abi.decode(returndata, (bool))) {
        revert IErrors.OperationDidNotSucceed();
      }
    }
  
    /**
     * @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
     * on the return value: the return value is optional (but if data is returned, it must not be false).
     * @param token The token targeted by the call.
     * @param data The call data (encoded using abi.encode or one of its variants).
     *
     * This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
     */
    function _callOptionalReturnBool(
      IERC20 token,
      bytes memory data
    ) private returns (bool) {
      // We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
      // we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
      // and not revert is the subcall reverts.
  
      (bool success, bytes memory returndata) = address(token).call(data);
      return
        success &&
        (returndata.length == 0 || abi.decode(returndata, (bool))) &&
        address(token).code.length > 0;
    }
  }
  
  // File contracts/lib/erc20/ERC20ByMetadropParamDecoder.sol
  
  // Metadrop Contracts (v2.1.0)
  
  pragma solidity 0.8.21;
  
  library ERC20ByMetadropParamDecoder {
    /**
     * @dev function {decodeBaseParams}
     *
     * Decode NFT Parameters
     *
     * @param encodedBaseParams_ The base params encoded into a bytes array
     */
    function decodeBaseParams(
      bytes memory encodedBaseParams_
    ) pure public returns (string memory name, string memory symbol, address[] memory recipients, uint256[] memory amounts){
      bytes memory distribution;
  
          // Decoding the initial parameters including the distribution bytes array.
          (name, symbol, , , distribution) = abi.decode(encodedBaseParams_, (string, string, bool, bool, bytes));
  
          // Decoding the distribution into recipients and amounts.
          (recipients, amounts) = abi.decode(distribution, (address[], uint256[]));
  
          return (name, symbol, recipients, amounts);
    }
  }
  
  // File contracts/vendor/sablier/ISablierV2LockupLinear.sol
  
  pragma solidity >=0.8.19;
  
  type UD60x18 is uint256;
  
  struct Broker {
      address account;
      UD60x18 fee;
  }
  library LockupLinear {
    struct CreateWithDurations {
            address sender;
            address recipient;
            uint128 totalAmount;
            IERC20 asset;
            bool cancelable;
            bool transferable;
            Durations durations;
            Broker broker;
        }
  
          /// @notice Struct encapsulating the cliff duration and the total duration.
      /// @param cliff The cliff duration in seconds.
      /// @param total The total duration in seconds.
      struct Durations {
          uint40 cliff;
          uint40 total;
      }
  
  }
  
  interface ISablierV2LockupLinear {
        function createWithDurations(LockupLinear.CreateWithDurations calldata params)
      external
      returns (uint256 streamId);
  }
  
  // File contracts/lib/erc20/ERC20ByMetadropSablierV2LinearLockup.sol
  
  // Metadrop Contracts (v2.1.0)
  
  pragma solidity 0.8.21;
  
  library ERC20ByMetadropSablierV2LinearLockup {
    using SafeERC20 for IERC20;
  
    function createWithDurations(
      address lpOwner_,
      address uniswapV2Pair_,
      address tokenVault_,
      uint256 lpTokenAmount_,
      uint40 lpTokenLockupDuration_
    ) public returns (uint256 streamId) {
       // Setup lock parameters.
      LockupLinear.CreateWithDurations memory params;
      params.sender = msg.sender; // The sender will be able to cancel the stream
      params.recipient = lpOwner_; // The recipient of the streamed assets
      params.totalAmount = uint128(
        lpTokenAmount_
      ); // Total amount is the amount inclusive of all fees.
      params.asset = IERC20(uniswapV2Pair_); // The streaming asset
      params.cancelable = false; // Whether the stream will be cancelable or not
      params.durations = LockupLinear.Durations({
        cliff: lpTokenLockupDuration_ - 1, // Cliff duration (must be less than total duration or will revert)
        total: lpTokenLockupDuration_ // Total duration
      });
  
      // Create the LockupLinear stream using a function that sets the start time to `block.timestamp`
      streamId = ISablierV2LockupLinear(tokenVault_).createWithDurations(params);
    }
  }
  
  // File contracts/ThirdParty/WETH/IWETH.sol
  
  pragma solidity 0.8.21;
  
  interface IWETH is IERC20 {
    function deposit() external payable;
  
    function withdraw(uint256 wad) external;
  }
  
  // File contracts/ERC20Factory/ERC20/ERC20ByMetadrop.sol
  
  // Metadrop Contracts (v2.1.0)
  
  pragma solidity 0.8.21;
  
  /**
   * @dev Metadrop core ERC-20 contract
   *
   * @dev Implementation of the {IERC20} interface.
   *
   */
  contract ERC20ByMetadrop is Context, IERC20ByMetadrop, Ownable2Step {
    bytes32 public constant x_META_ID_HASH =
      0x3a9ab45fc48593a087223536bc2589df3f0d0f5fe4687a7c6423f92582a6719b;
  
    using EnumerableSet for EnumerableSet.AddressSet;
    using EnumerableSet for EnumerableSet.Bytes32Set;
    using SafeERC20 for IERC20;
  
    uint256 public constant x_VERSION = 100020001000000000;
    uint256 internal constant BP_DENOM = 10000;
    uint256 internal constant ROUND_DEC = 100000000000;
    uint256 internal constant CALL_GAS_LIMIT = 50000;
    uint256 internal constant MAX_AUTOSWAP_THRESHOLD_MULTIPLE = 20;
    uint256 internal constant MIN_AUTOSWAP_THRESHOLD_BP = 1;
    uint256 internal constant MAX_AUTOSWAPS_PER_BLOCK = 1;
    uint256 internal constant MAX_BUYS_PER_ORIGIN_PER_BLOCK = 1;
  
    uint256 public immutable lpSupply;
  
    uint256 public immutable botProtectionDurationInSeconds;
    uint256 public immutable metadropTaxPeriodInDays;
    uint256 public immutable metadropBuyTaxProportionBasisPoints;
    uint256 public immutable metadropSellTaxProportionBasisPoints;
    uint256 public immutable metadropMinBuyTaxBasisPoints;
    uint256 public immutable metadropMinSellTaxBasisPoints;
    uint256 public immutable autoBurnDurationInBlocks;
    uint256 public immutable autoBurnBasisPoints;
    address public immutable metadropTaxRecipient;
    address public immutable uniswapV2Pair;
    address public immutable driPool;
    address public immutable lpOwner;
    address public immutable metadropFactory;
  
    bool internal immutable _tokenHasTax;
    address internal immutable _tokenVault;
    IUniswapV2Router02 internal immutable _uniswapRouter;
  
    /** @dev {Storage Slot 1} Vars read as part of transfers packed to a single
     * slot for warm reads.
     *   Slot 1:
     *      120
     *       32
     *       32
     *   16 * 4
     *        8
     *   ------
     *      256
     *   ------ */
    uint120 private _totalSupply;
    uint32 public fundedDate;
    uint32 public fundedBlock;
    uint16 public projectBuyTaxBasisPoints;
    uint16 public projectSellTaxBasisPoints;
    uint16 public metadropBuyTaxBasisPoints;
    uint16 public metadropSellTaxBasisPoints;
    /** @dev {_autoSwapInProgress} We start with {_autoSwapInProgress} ON, as we don't want to
     * call autoswap when processing initial liquidity from this address. We turn this OFF when
     * liquidity has been loaded, and use this bool to control processing during auto-swaps
     * from that point onwards. */
    bool private _autoSwapInProgress = true;
  
    /** @dev {Storage Slot 2} Vars read as part of transfers packed to a single
     * slot for warm reads.
     *   Slot 1:
     *      120
     *      112
     *       16
     *        8
     *   ------
     *      256
     *   ------ */
    uint120 public maxTokensPerWallet;
    uint112 public maxTokensPerTransaction;
    uint16 public swapThresholdBasisPoints;
    bool public initialBuyRelevantAndNotYetCompleted;
  
    /** @dev {Storage Slot 3} Not read / written in transfers (unless autoswap taking place):
     *      160
     *       88
     *        8
     *   ------
     *      256
     *   ------ */
    address public projectTaxRecipient;
    uint88 public lpLockupInDays;
    bool public burnLPTokens;
  
    /** @dev {Storage Slot 4} Potentially written in transfers:
     *   Slot 3:
     *      128
     *      128
     *   ------
     *      256
     *   ------ */
    uint128 public projectTaxPendingSwap;
    uint128 public metadropTaxPendingSwap;
  
    /** @dev {Storage Slot 5 to n} Not read as part of transfers etc. */
    string private _name;
    string private _symbol;
  
    /** @dev {_balances} Addresses balances */
    mapping(address => uint256) private _balances;
  
    /** @dev {_allowances} Addresses allocance details */
    mapping(address => mapping(address => uint256)) private _allowances;
  
    /** @dev {_autoswapForBlock} Limit autoswaps per block */
    mapping(uint256 => uint256) private _autoswapForBlock;
  
    /** @dev {_originBuysPerBlock} Number of buys from this txn.origin by block */
    mapping(address => mapping(uint256 => uint256)) private _originBuysPerBlock;
  
    /** @dev {_liquidityPools} Enumerable set for liquidity pool addresses */
    EnumerableSet.AddressSet private _liquidityPools;
  
    /** @dev {_unlimited} Enumerable set for addresses where limits do not apply */
    EnumerableSet.AddressSet private _unlimited;
  
    /**
     * @dev {constructor}
     *
     * @param integrationAddresses_ The project owner, uniswap router, vault contract (currently Sablier), metadrop factory and pool template.
     * @param baseParams_ configuration of this ERC20.
     * @param supplyParams_ Supply configuration of this ERC20.
     * @param taxParams_  Tax configuration of this ERC20
     * @param taxParams_  Launch pool configuration of this ERC20
     */
    constructor(
      address[5] memory integrationAddresses_,
      bytes memory baseParams_,
      bytes memory supplyParams_,
      bytes memory taxParams_,
      bytes memory poolParams_
    ) {
      _transferOwnership(integrationAddresses_[0]);
      address[] memory supplyRecipients;
      uint256[] memory supplyAmounts;
  
      (
        _name,
        _symbol,
        supplyRecipients,
        supplyAmounts
      ) = ERC20ByMetadropParamDecoder.decodeBaseParams(baseParams_);
      uint256 distributedSupply = _processDistribution(
        supplyRecipients,
        supplyAmounts
      );
      _uniswapRouter = IUniswapV2Router02(integrationAddresses_[1]);
      _tokenVault = integrationAddresses_[2];
      metadropFactory = (integrationAddresses_[3]);
      ERC20SupplyParameters memory supplyParams = abi.decode(
        supplyParams_,
        (ERC20SupplyParameters)
      );
  
      ERC20TaxParameters memory taxParams = abi.decode(
        taxParams_,
        (ERC20TaxParameters)
      );
  
      // If we have a DRI Pool record the address and also set the variable
      // initialBuyRelevantAndNotYetCompleted based on the DRI pool type. For
      // initial buy type DRI pools we do NOT tax or take autoburn from the very
      // first buy:
      if (integrationAddresses_[4] != address(0)) {
        driPool = integrationAddresses_[4];
        if (
          IERC20DRIPoolByMetadrop(integrationAddresses_[4]).driType() ==
          DRIPoolType.initialBuy
        ) {
          initialBuyRelevantAndNotYetCompleted = true;
        } else {
          initialBuyRelevantAndNotYetCompleted = false;
        }
      } else {
        initialBuyRelevantAndNotYetCompleted = false;
      }
  
      ERC20PoolParameters memory poolParams;
  
      if (integrationAddresses_[4] != address(0)) {
        poolParams = abi.decode(poolParams_, (ERC20PoolParameters));
        _unlimited.add(integrationAddresses_[4]);
      }
  
      _processSupplyParams(supplyParams, poolParams, distributedSupply);
      // projectSupplyRecipient = supplyParams.projectSupplyRecipient;
      lpSupply = supplyParams.lpSupply * (10 ** decimals());
      // projectSupply = supplyParams.projectSupply * (10 ** decimals());
      botProtectionDurationInSeconds = supplyParams
        .botProtectionDurationInSeconds;
      lpOwner = supplyParams.projectLPOwner;
  
      _tokenHasTax = _processTaxParams(taxParams);
  
      metadropTaxPeriodInDays = taxParams.metadropTaxPeriodInDays;
      metadropTaxRecipient = taxParams.metadropTaxRecipient;
      metadropBuyTaxProportionBasisPoints = taxParams
        .metadropBuyTaxProportionBasisPoints;
      metadropSellTaxProportionBasisPoints = taxParams
        .metadropSellTaxProportionBasisPoints;
      metadropMinBuyTaxBasisPoints = uint16(
        taxParams.metadropMinBuyTaxBasisPoints
      );
      metadropMinSellTaxBasisPoints = uint16(
        taxParams.metadropMinSellTaxBasisPoints
      );
      autoBurnDurationInBlocks = taxParams.autoBurnDurationInBlocks;
      autoBurnBasisPoints = taxParams.autoBurnBasisPoints;
  
      _mintBalances(lpSupply, poolParams.poolSupply * (10 ** decimals()));
  
      uniswapV2Pair = _createPair();
    }
  
    /**
     * @dev {onlyOwnerFactoryOrPool}
     *
     * Throws if called by any account other than the owner, factory or pool.
     */
    modifier onlyOwnerFactoryOrPool() {
      if (
        metadropFactory != _msgSender() &&
        owner() != _msgSender() &&
        driPool != _msgSender()
      ) {
        _revert(CallerIsNotFactoryProjectOwnerOrPool.selector);
      }
      if (owner() == _msgSender() && driPool != address(0)) {
        _revert(CannotManuallyFundLPWhenUsingADRIPool.selector);
      }
  
      _;
    }
  
    /**
     * @dev {notDuringAutoswap}
     *
     * Throws if called during an autoswap
     */
    modifier notDuringAutoswap() {
      if (_autoSwapInProgress) {
        _revert(CannotPerformDuringAutoswap.selector);
      }
      _;
    }
  
    /**
     * @dev function {_processDistribution}
     *
     * Mints and set unlimited to the initial mint recipients
     *
     * @param recipients_ The distribution addresses
     * @param amounts_ The distribution amounts
     *
     */
    function _processDistribution(
      address[] memory recipients_,
      uint256[] memory amounts_
    ) internal returns (uint256 distributedSupply) {
      if (recipients_.length != amounts_.length) {
        _revert(RecipientsAndAmountsMismatch.selector);
      }
      distributedSupply = 0;
      for (uint256 i = 0; i < recipients_.length; i++) {
        _unlimited.add(recipients_[i]);
        _mint(recipients_[i], amounts_[i] * (10 ** decimals()));
        distributedSupply += amounts_[i];
      }
      return distributedSupply;
    }
  
    /**
     * @dev function {_processSupplyParams}
     *
     * Process provided supply params
     *
     * @param erc20SupplyParameters_ The supply params
     * @param erc20PoolParameters_ The pool params
     * @param distributedSupply_ The distributed supply
     */
    function _processSupplyParams(
      ERC20SupplyParameters memory erc20SupplyParameters_,
      ERC20PoolParameters memory erc20PoolParameters_,
      uint256 distributedSupply_
    ) internal {
      if (
        erc20SupplyParameters_.maxSupply !=
        (erc20SupplyParameters_.lpSupply +
          distributedSupply_ +
          erc20PoolParameters_.poolSupply)
      ) {
        _revert(SupplyTotalMismatch.selector);
      }
  
      if (erc20SupplyParameters_.maxSupply > type(uint120).max) {
        _revert(MaxSupplyTooHigh.selector);
      }
  
      if (erc20SupplyParameters_.lpLockupInDays > type(uint88).max) {
        _revert(LPLockUpMustFitUint88.selector);
      }
  
      if (
        erc20SupplyParameters_.botProtectionDurationInSeconds > type(uint128).max
      ) {
        _revert(botProtectionDurationInSecondsMustFitUint128.selector);
      }
  
      if (erc20SupplyParameters_.maxTokensPerWallet > type(uint120).max) {
        _revert(LimitTooHigh.selector);
      }
  
      if (erc20SupplyParameters_.maxTokensPerTxn > type(uint120).max) {
        _revert(LimitTooHigh.selector);
      }
  
      maxTokensPerWallet = uint120(
        erc20SupplyParameters_.maxTokensPerWallet * (10 ** decimals())
      );
      maxTokensPerTransaction = uint112(
        erc20SupplyParameters_.maxTokensPerTxn * (10 ** decimals())
      );
      lpLockupInDays = uint88(erc20SupplyParameters_.lpLockupInDays);
      burnLPTokens = erc20SupplyParameters_.burnLPTokens;
  
      _unlimited.add(address(this));
      _unlimited.add(address(0));
    }
  
    /**
     * @dev function {_processTaxParams}
     *
     * Process provided tax params
     *
     * @param erc20TaxParameters_ The tax params
     */
    function _processTaxParams(
      ERC20TaxParameters memory erc20TaxParameters_
    ) internal returns (bool tokenHasTax_) {
      /**
       * @dev We use the immutable var {_tokenHasTax} to avoid unneccesary storage writes and reads. If this
       * token does NOT have tax applied then there is no need to store or read these parameters, and we can
       * avoid this simply by checking the immutable var. Pass back the value for this var from this method.
       */
      if (
        erc20TaxParameters_.projectBuyTaxBasisPoints == 0 &&
        erc20TaxParameters_.projectSellTaxBasisPoints == 0 &&
        erc20TaxParameters_.metadropBuyTaxBasisPoints == 0 &&
        erc20TaxParameters_.metadropSellTaxBasisPoints == 0
      ) {
        return false;
      } else {
        // Validate that the sum of all buy deductions does not equal or exceed
        // 10,000 basis points (i.e. 100%).
        if (
          (erc20TaxParameters_.projectBuyTaxBasisPoints +
            erc20TaxParameters_.metadropBuyTaxBasisPoints +
            erc20TaxParameters_.autoBurnBasisPoints) >= BP_DENOM
        ) {
          _revert(DeductionsOnBuyExceedOrEqualOneHundredPercent.selector);
        }
  
        projectBuyTaxBasisPoints = uint16(
          erc20TaxParameters_.projectBuyTaxBasisPoints
        );
        projectSellTaxBasisPoints = uint16(
          erc20TaxParameters_.projectSellTaxBasisPoints
        );
        metadropBuyTaxBasisPoints = uint16(
          erc20TaxParameters_.metadropBuyTaxBasisPoints
        );
        metadropSellTaxBasisPoints = uint16(
          erc20TaxParameters_.metadropSellTaxBasisPoints
        );
  
        if (
          erc20TaxParameters_.taxSwapThresholdBasisPoints <
          MIN_AUTOSWAP_THRESHOLD_BP
        ) {
          _revert(SwapThresholdTooLow.selector);
        }
  
        swapThresholdBasisPoints = uint16(
          erc20TaxParameters_.taxSwapThresholdBasisPoints
        );
  
        projectTaxRecipient = erc20TaxParameters_.projectTaxRecipient;
        return true;
      }
    }
  
    /**
     * @dev function {_mintBalances}
     *
     * Mint initial balances
     *
     * @param lpMint_ The number of tokens for liquidity
     * @param poolMint_ The number of tokens for the launch pool
     */
    function _mintBalances(uint256 lpMint_, uint256 poolMint_) internal {
      if (lpMint_ > 0) {
        _mint(address(this), lpMint_);
      }
  
      if (poolMint_ > 0) {
        _mint(driPool, poolMint_);
      }
    }
  
    /**
     * @dev function {_createPair}
     *
     * Create the uniswap pair
     *
     * @return uniswapV2Pair_ The pair address
     */
    function _createPair() internal returns (address uniswapV2Pair_) {
      if (_totalSupply > 0) {
        uniswapV2Pair_ = IUniswapV2Factory(_uniswapRouter.factory()).createPair(
          address(this),
          _uniswapRouter.WETH()
        );
  
        _liquidityPools.add(uniswapV2Pair_);
        emit LiquidityPoolCreated(uniswapV2Pair_);
      }
      _unlimited.add(address(_uniswapRouter));
      _unlimited.add(uniswapV2Pair_);
      return (uniswapV2Pair_);
    }
  
    /**
     * @dev function {addInitialLiquidity}
     *
     * Add initial liquidity to the uniswap pair
     *
     * @param vaultFee_ The vault fee in wei. This must match the required fee from the external vault contract.
     * @param lpLockupInDaysOverride_ The number of days to lock liquidity NOTE you can pass 0 to use the stored value.
     * This value is an override, and will override a stored value which is LOWER that it. If the value you are passing is
     * LOWER than the stored value the stored value will not be reduced.
     *
     * Example usage 1: When creating the coin the lpLockupInDays is set to 0. This means that on this call the
     * user can set the lockup to any value they like, as all integer values greater than zero will be used to override
     * that set in storage.
     *
     * Example usage 2: When using a DRI Pool the lockup period is set on this contract and the pool need not know anything
     * about this setting. The pool can pass back a 0 on this call and know that the existing value stored on this contract
     * will be used.
     * @param burnLPTokensOverride_ If the LP tokens should be burned (otherwise they are locked). This is an override field
     * that can ONLY be used to override a held value of FALSE with a new value of TRUE.
     *
     * Example usage 1: When creating the coin the user didn't add liquidity, or specify that the LP tokens were to be burned.
     * So burnLPTokens is held as FALSE. When they add liquidity they want to lock tokens, so they pass this in as FALSE again,
     * and it remains FALSE.
     *
     * Example usage 2: As above, but when later adding liquidity the user wants to burn the LP. So the stored value is FALSE
     * and the user passes TRUE into this method. The TRUE overrides the held value of FALSE and the tokens are burned.
     *
     * Example uusage 3: The user is using a DRI pool and they have specified on the coin creation that the LP tokens are to
     * be burned. This contract therefore holds TRUE for burnLPTokens. The DRI pool does not need to know what the user has
     * selected. It can safely pass back FALSE to this method call and the stored value of TRUE will remain, resulting in the
     * LP tokens being burned.
     */
    function addInitialLiquidity(
      uint256 vaultFee_,
      uint256 lpLockupInDaysOverride_,
      bool burnLPTokensOverride_
    ) external payable onlyOwnerFactoryOrPool {
      uint256 ethForLiquidity;
  
      if ((burnLPTokens == false) && (burnLPTokensOverride_ == true)) {
        burnLPTokens = true;
      }
  
      if (burnLPTokens) {
        if (msg.value == 0) {
          _revert(NoETHForLiquidityPair.selector);
        }
        ethForLiquidity = msg.value;
      } else {
        if (vaultFee_ >= msg.value) {
          // The amount of ETH MUST exceed the vault fee, otherwise what liquidity are we adding?
          _revert(NoETHForLiquidityPair.selector);
        }
        ethForLiquidity = msg.value - vaultFee_;
      }
  
      if (lpLockupInDaysOverride_ > lpLockupInDays) {
        lpLockupInDays = uint88(lpLockupInDaysOverride_);
      }
  
      _addInitialLiquidity(ethForLiquidity, vaultFee_);
    }
  
    /**
     * @dev function {_addInitialLiquidity}
     *
     * Add initial liquidity to the uniswap pair (internal function that does processing)
     *
     * @param ethAmount_ The amount of ETH passed into the call
     * @param vaultFee_ The vault fee in wei. This must match the required fee from the external vault contract.
     */
    function _addInitialLiquidity(
      uint256 ethAmount_,
      uint256 vaultFee_
    ) internal {
      // Funded date is the date of first funding. We can only add initial liquidity once. If this date is set,
      // we cannot proceed
      if (fundedDate != 0) {
        _revert(InitialLiquidityAlreadyAdded.selector);
      }
  
      fundedDate = uint32(block.timestamp);
      fundedBlock = uint32(block.number);
  
      // Can only do this if this contract holds tokens:
      if (balanceOf(address(this)) == 0) {
        _revert(NoTokenForLiquidityPair.selector);
      }
  
      // Approve the uniswap router for an inifinite amount (max uint256)
      // This means that we don't need to worry about later incrememtal
      // approvals on tax swaps, as the uniswap router allowance will never
      // be decreased (see code in decreaseAllowance for reference)
      _approve(address(this), address(_uniswapRouter), type(uint256).max);
  
      // Add the liquidity:
      (uint256 amountA, uint256 amountB, uint256 lpTokens) = _uniswapRouter
        .addLiquidityETH{value: ethAmount_}(
        address(this),
        balanceOf(address(this)),
        0,
        0,
        address(this),
        block.timestamp
      );
  
      emit InitialLiquidityAdded(amountA, amountB, lpTokens);
  
      // We now set this to false so that future transactions can be eligibile for autoswaps
      _autoSwapInProgress = false;
  
      // Are we locking, or burning?
      if (burnLPTokens) {
        _burnLiquidity(lpTokens);
      } else {
        // The LP token amount to lock should be equal to the LP token balance in the contract. If it's not, something is wrong.
        if (lpTokens != IERC20(uniswapV2Pair).balanceOf(address(this))) {
          _revert(LPTokensBalanceMismatch.selector);
        }
  
        // Lock the liquidity:
        _addLiquidityToVault(vaultFee_, lpTokens);
      }
    }
  
    /**
     * @dev function {_addLiquidityToVault}
     *
     * Lock initial liquidity on vault contract
     *
     * @param vaultFee_ The vault fee in wei. This must match the required fee from the external vault contract.
     * @param lpTokens_ The amount of LP tokens to be locked
     */
    function _addLiquidityToVault(uint256 vaultFee_, uint256 lpTokens_) internal {
      // Approve the vault contract to spend the LP tokens.
      IERC20(uniswapV2Pair).approve(_tokenVault, lpTokens_);
  
      uint256 streamId = ERC20ByMetadropSablierV2LinearLockup.createWithDurations(
        lpOwner,
        uniswapV2Pair,
        _tokenVault,
        lpTokens_,
        uint40(lpLockupInDays * 1 days)
      );
  
      emit LiquidityLocked(lpTokens_, lpLockupInDays, streamId);
    }
  
    /**
     * @dev function {_burnLiquidity}
     *
     * Burn LP tokens
     *
     * @param lpTokens_ The amount of LP tokens to be locked
     */
    function _burnLiquidity(uint256 lpTokens_) internal {
      IERC20(uniswapV2Pair).transfer(address(0), lpTokens_);
  
      emit LiquidityBurned(lpTokens_);
    }
  
    /**
     * @dev function {isLiquidityPool}
     *
     * Return if an address is a liquidity pool
     *
     * @param queryAddress_ The address being queried
     * @return bool The address is / isn't a liquidity pool
     */
    function isLiquidityPool(address queryAddress_) public view returns (bool) {
      /** @dev We check the uniswapV2Pair address first as this is an immutable variable and therefore does not need
       * to be fetched from storage, saving gas if this address IS the uniswapV2Pool. We also add this address
       * to the enumerated set for ease of reference (for example it is returned in the getter), and it does
       * not add gas to any other calls, that still complete in 0(1) time.
       */
      return (queryAddress_ == uniswapV2Pair ||
        _liquidityPools.contains(queryAddress_));
    }
  
    /**
     * @dev function {liquidityPools}
     *
     * Returns a list of all liquidity pools
     *
     * @return liquidityPools_ a list of all liquidity pools
     */
    function liquidityPools()
      external
      view
      returns (address[] memory liquidityPools_)
    {
      return (_liquidityPools.values());
    }
  
    /**
     * @dev function {addLiquidityPool} onlyOwner
     *
     * Allows the manager to add a liquidity pool to the pool enumerable set
     *
     * @param newLiquidityPool_ The address of the new liquidity pool
     */
    function addLiquidityPool(address newLiquidityPool_) public onlyOwner {
      // Don't allow calls that didn't pass an address:
      if (newLiquidityPool_ == address(0)) {
        _revert(LiquidityPoolCannotBeAddressZero.selector);
      }
      // Only allow smart contract addresses to be added, as only these can be pools:
      if (newLiquidityPool_.code.length == 0) {
        _revert(LiquidityPoolMustBeAContractAddress.selector);
      }
      // Add this to the enumerated list:
      _liquidityPools.add(newLiquidityPool_);
      emit LiquidityPoolAdded(newLiquidityPool_);
    }
  
    /**
     * @dev function {removeLiquidityPool} onlyOwner
     *
     * Allows the manager to remove a liquidity pool
     *
     * @param removedLiquidityPool_ The address of the old removed liquidity pool
     */
    function removeLiquidityPool(
      address removedLiquidityPool_
    ) external onlyOwner {
      // Remove this from the enumerated list:
      _liquidityPools.remove(removedLiquidityPool_);
      emit LiquidityPoolRemoved(removedLiquidityPool_);
    }
  
    /**
     * @dev function {isUnlimited}
     *
     * Return if an address is unlimited (is not subject to per txn and per wallet limits)
     *
     * @param queryAddress_ The address being queried
     * @return bool The address is / isn't unlimited
     */
    function isUnlimited(address queryAddress_) public view returns (bool) {
      return (_unlimited.contains(queryAddress_));
    }
  
    /**
     * @dev function {unlimitedAddresses}
     *
     * Returns a list of all unlimited addresses
     *
     * @return unlimitedAddresses_ a list of all unlimited addresses
     */
    function unlimitedAddresses()
      external
      view
      returns (address[] memory unlimitedAddresses_)
    {
      return (_unlimited.values());
    }
  
    /**
     * @dev function {addUnlimited} onlyOwner
     *
     * Allows the manager to add an unlimited address
     *
     * @param newUnlimited_ The address of the new unlimited address
     */
    function addUnlimited(address newUnlimited_) external onlyOwner {
      // Add this to the enumerated list:
      _unlimited.add(newUnlimited_);
      emit UnlimitedAddressAdded(newUnlimited_);
    }
  
    /**
     * @dev function {removeUnlimited} onlyOwner
     *
     * Allows the manager to remove an unlimited address
     *
     * @param removedUnlimited_ The address of the old removed unlimited address
     */
    function removeUnlimited(address removedUnlimited_) external onlyOwner {
      // Remove this from the enumerated list:
      _unlimited.remove(removedUnlimited_);
      emit UnlimitedAddressRemoved(removedUnlimited_);
    }
  
    /**
     * @dev function {setProjectTaxRecipient} onlyOwner
     *
     * Allows the manager to set the project tax recipient address
     *
     * @param projectTaxRecipient_ New recipient address
     */
    function setProjectTaxRecipient(
      address projectTaxRecipient_
    ) external onlyOwner {
      projectTaxRecipient = projectTaxRecipient_;
      emit ProjectTaxRecipientUpdated(projectTaxRecipient_);
    }
  
    /**
     * @dev function {setSwapThresholdBasisPoints} onlyOwner
     *
     * Allows the manager to set the autoswap threshold
     *
     * @param swapThresholdBasisPoints_ New swap threshold in basis points
     */
    function setSwapThresholdBasisPoints(
      uint16 swapThresholdBasisPoints_
    ) external onlyOwner {
      if (swapThresholdBasisPoints < MIN_AUTOSWAP_THRESHOLD_BP) {
        _revert(SwapThresholdTooLow.selector);
      }
      uint256 oldswapThresholdBasisPoints = swapThresholdBasisPoints;
      swapThresholdBasisPoints = swapThresholdBasisPoints_;
      emit AutoSwapThresholdUpdated(
        oldswapThresholdBasisPoints,
        swapThresholdBasisPoints_
      );
    }
  
    /**
     * @dev function {setProjectTaxRates} onlyOwner
     *
     * Change the tax rates, subject to only ever decreasing
     *
     * @param newProjectBuyTaxBasisPoints_ The new buy tax rate
     * @param newProjectSellTaxBasisPoints_ The new sell tax rate
     */
    function setProjectTaxRates(
      uint16 newProjectBuyTaxBasisPoints_,
      uint16 newProjectSellTaxBasisPoints_
    ) external onlyOwner {
      uint16 oldBuyTaxBasisPoints = projectBuyTaxBasisPoints;
      uint16 oldSellTaxBasisPoints = projectSellTaxBasisPoints;
      // Cannot increase, down only
      if (newProjectBuyTaxBasisPoints_ > oldBuyTaxBasisPoints) {
        _revert(CanOnlyReduce.selector);
      }
      // Cannot increase, down only
      if (newProjectSellTaxBasisPoints_ > oldSellTaxBasisPoints) {
        _revert(CanOnlyReduce.selector);
      }
      projectBuyTaxBasisPoints = newProjectBuyTaxBasisPoints_;
      projectSellTaxBasisPoints = newProjectSellTaxBasisPoints_;
  
      // We set the metadrop tax rates off of the project tax rates:
      //
      // 1) If the project tax rate is zero then the metadrop tax rate is zero
      // 2) If the project tax rate is not zero the metadrop tax rate is the
      //    greater of:
      //    a) The metadrop tax proportion basis points of the project rate
      //    b) the base metadrop tax rate.
      //
      // Examples:
      //
      // A) The project buy tax rate is zero and the sell tax rate is 3%. The metadrop
      // tax proportion basis points is 1000, meaning the metadrop proportion is 10% of the
      // project tax rate. The base metadrop tax rate is 50 basis points i.e. 0.5%.
      //
      // * Metadrop buy tax = 0% (as the project buy tax is zero)
      // * Metadrop sell tax = 0.5%. 10% of the project sell tax is 0.3%. As this is below
      // the base level of 0.5% we set the metadrop tax to 0.5%
      //
      // B) The project buy tax rate is 4% and the sell tax rate is 20%. The metadrop tax
      // proportion basis points is 1000, meaning the metadrop proportion is 10% of the
      // project tax rate. The base metadrop tax rate is 50 basis points i.e. 0.5%.
      //
      // * Metadrop buy tax = 0.5%. 10% of the project rate would be 0.4%, so we use the base rate)
      // * Metadrop sell tax = 2%. 10% of the project rate is 2%, which is higher than the
      //   base rate of 0.5%.
  
      uint16 oldMetadropBuyTaxBasisPoints = metadropBuyTaxBasisPoints;
      uint16 oldMetadropSellTaxBasisPoints = metadropSellTaxBasisPoints;
  
      // Process the buy tax rate first:
      if (newProjectBuyTaxBasisPoints_ == 0) {
        metadropBuyTaxBasisPoints = 0;
      } else {
        uint256 derivedMetadropBuyTaxRate = (newProjectBuyTaxBasisPoints_ *
          metadropBuyTaxProportionBasisPoints) / BP_DENOM;
        if (derivedMetadropBuyTaxRate < metadropMinBuyTaxBasisPoints) {
          metadropBuyTaxBasisPoints = uint16(metadropMinBuyTaxBasisPoints);
        } else {
          metadropBuyTaxBasisPoints = uint16(derivedMetadropBuyTaxRate);
        }
      }
  
      // And now the sell tax rate:
      if (newProjectSellTaxBasisPoints_ == 0) {
        metadropSellTaxBasisPoints = 0;
      } else {
        uint256 derivedMetadropSellTaxRate = (newProjectSellTaxBasisPoints_ *
          metadropSellTaxProportionBasisPoints) / BP_DENOM;
        if (derivedMetadropSellTaxRate < metadropMinSellTaxBasisPoints) {
          metadropSellTaxBasisPoints = uint16(metadropMinSellTaxBasisPoints);
        } else {
          metadropSellTaxBasisPoints = uint16(derivedMetadropSellTaxRate);
        }
      }
  
      // Emit a message if there has been a change:
      if (
        oldMetadropBuyTaxBasisPoints != metadropBuyTaxBasisPoints ||
        oldMetadropSellTaxBasisPoints != metadropSellTaxBasisPoints
      ) {
        emit MetadropTaxBasisPointsChanged(
          oldMetadropBuyTaxBasisPoints,
          metadropBuyTaxBasisPoints,
          oldMetadropSellTaxBasisPoints,
          metadropSellTaxBasisPoints
        );
      }
  
      emit ProjectTaxBasisPointsChanged(
        oldBuyTaxBasisPoints,
        newProjectBuyTaxBasisPoints_,
        oldSellTaxBasisPoints,
        newProjectSellTaxBasisPoints_
      );
    }
  
    /**
     * @dev function {setLimits} onlyOwner
     *
     * Change the limits on transactions and holdings
     *
     * @param newMaxTokensPerTransaction_ The new per txn limit
     * @param newMaxTokensPerWallet_ The new tokens per wallet limit
     */
    function setLimits(
      uint256 newMaxTokensPerTransaction_,
      uint256 newMaxTokensPerWallet_
    ) external onlyOwner {
      if (newMaxTokensPerWallet_ > type(uint120).max) {
        _revert(LimitTooHigh.selector);
      }
  
      if (newMaxTokensPerTransaction_ > type(uint120).max) {
        _revert(LimitTooHigh.selector);
      }
  
      uint256 oldMaxTokensPerTransaction = maxTokensPerTransaction;
      uint256 oldMaxTokensPerWallet = maxTokensPerWallet;
      // Limit can only be increased:
      if (
        (oldMaxTokensPerTransaction == 0 && newMaxTokensPerTransaction_ != 0) ||
        (oldMaxTokensPerWallet == 0 && newMaxTokensPerWallet_ != 0)
      ) {
        _revert(LimitsCanOnlyBeRaised.selector);
      }
      if (
        ((newMaxTokensPerTransaction_ != 0) &&
          newMaxTokensPerTransaction_ < oldMaxTokensPerTransaction) ||
        ((newMaxTokensPerWallet_ != 0) &&
          newMaxTokensPerWallet_ < oldMaxTokensPerWallet)
      ) {
        _revert(LimitsCanOnlyBeRaised.selector);
      }
  
      maxTokensPerTransaction = uint112(newMaxTokensPerTransaction_);
      maxTokensPerWallet = uint120(newMaxTokensPerWallet_);
  
      emit LimitsUpdated(
        oldMaxTokensPerTransaction,
        newMaxTokensPerTransaction_,
        oldMaxTokensPerWallet,
        newMaxTokensPerWallet_
      );
    }
  
    /**
     * @dev function {limitsEnforced}
     *
     * Return if limits are enforced on this contract
     *
     * @return bool : they are / aren't
     */
    function limitsEnforced() public view returns (bool) {
      // Limits are not enforced if
      // this is renounced AND after the protection end date
      // OR prior to LP funding:
      // The second clause of 'fundedDate == 0' isn't strictly needed, since with a funded
      // date of 0 we would always expect the block.timestamp to be less than 0 plus
      // the botProtectionDurationInSeconds. But, to cover the miniscule chance of a user
      // selecting a truly enormous bot protection period, such that when added to 0 it
      // is more than the current block.timestamp, we have included this second clause. There
      // is no permanent gas overhead (the logic will be returning from the first clause after
      // the bot protection period has expired). During the bot protection period there is a minor
      // gas overhead from evaluating the fundedDate == 0 (which will be false), but this is minimal.
      if (
        (owner() == address(0) &&
          block.timestamp > fundedDate + botProtectionDurationInSeconds) ||
        fundedDate == 0
      ) {
        return false;
      } else {
        // LP has been funded AND we are within the protection period:
        return true;
      }
    }
  
    /**
     * @dev Returns the name of the token.
     */
    function name() public view virtual override returns (string memory) {
      return _name;
    }
  
    /**
     * @dev Returns the symbol of the token, usually a shorter version of the
     * name.
     */
    function symbol() public view virtual override returns (string memory) {
      return _symbol;
    }
  
    /**
     * @dev Returns the number of decimals used to get its user representation.
     * For example, if `decimals` equals `2`, a balance of `505` tokens should
     * be displayed to a user as `5.05` (`505 / 10 ** 2`).
     *
     * Tokens usually opt for a value of 18, imitating the relationship between
     * Ether and Wei. This is the default value returned by this function, unless
     * it's overridden.
     *
     * NOTE: This information is only used for _display_ purposes: it in
     * no way affects any of the arithmetic of the contract, including
     * {IERC20-balanceOf} and {IERC20-transfer}.
     */
    function decimals() public view virtual override returns (uint8) {
      return 18;
    }
  
    /**
     * @dev See {IERC20-totalSupply}.
     */
    function totalSupply() public view virtual override returns (uint256) {
      return _totalSupply;
    }
  
    /**
     * @dev getMetadropBuyTaxBasisPoints
     *
     * Return the metadrop buy tax basis points given the timed expiry
     */
    function getMetadropBuyTaxBasisPoints() public view returns (uint256) {
      // If we are outside the metadrop tax period this is ZERO
      if (block.timestamp > (fundedDate + (metadropTaxPeriodInDays * 1 days))) {
        return 0;
      } else {
        return metadropBuyTaxBasisPoints;
      }
    }
  
    /**
     * @dev getMetadropSellTaxBasisPoints
     *
     * Return the metadrop sell tax basis points given the timed expiry
     */
    function getMetadropSellTaxBasisPoints() public view returns (uint256) {
      // If we are outside the metadrop tax period this is ZERO
      if (block.timestamp > (fundedDate + (metadropTaxPeriodInDays * 1 days))) {
        return 0;
      } else {
        return metadropSellTaxBasisPoints;
      }
    }
  
    /**
     * @dev totalBuyTaxBasisPoints
     *
     * Provide easy to view tax total:
     */
    function totalBuyTaxBasisPoints() public view returns (uint256) {
      return projectBuyTaxBasisPoints + getMetadropBuyTaxBasisPoints();
    }
  
    /**
     * @dev totalSellTaxBasisPoints
     *
     * Provide easy to view tax total:
     */
    function totalSellTaxBasisPoints() public view returns (uint256) {
      return projectSellTaxBasisPoints + getMetadropSellTaxBasisPoints();
    }
  
    /**
     * @dev See {IERC20-balanceOf}.
     */
    function balanceOf(
      address account
    ) public view virtual override returns (uint256) {
      return _balances[account];
    }
  
    /**
     * @dev See {IERC20-transfer}.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - the caller must have a balance of at least `amount`.
     */
    function transfer(
      address to,
      uint256 amount
    ) public virtual override(IERC20) returns (bool) {
      address owner = _msgSender();
      _transfer(
        owner,
        to,
        amount,
        (isLiquidityPool(owner) || isLiquidityPool(to))
      );
      return true;
    }
  
    /**
     * @dev See {IERC20-allowance}.
     */
    function allowance(
      address owner,
      address spender
    ) public view virtual override returns (uint256) {
      return _allowances[owner][spender];
    }
  
    /**
     * @dev See {IERC20-approve}.
     *
     * NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
     * `transferFrom`. This is semantically equivalent to an infinite approval.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function approve(
      address spender,
      uint256 amount
    ) public virtual override returns (bool) {
      address owner = _msgSender();
      _approve(owner, spender, amount);
      return true;
    }
  
    /**
     * @dev See {IERC20-transferFrom}.
     *
     * Emits an {Approval} event indicating the updated allowance. This is not
     * required by the EIP. See the note at the beginning of {ERC20}.
     *
     * NOTE: Does not update the allowance if the current allowance
     * is the maximum `uint256`.
     *
     * Requirements:
     *
     * - `from` and `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     * - the caller must have allowance for ``from``'s tokens of at least
     * `amount`.
     */
    function transferFrom(
      address from,
      address to,
      uint256 amount
    ) public virtual override returns (bool) {
      address spender = _msgSender();
      _spendAllowance(from, spender, amount);
      _transfer(from, to, amount, (isLiquidityPool(from) || isLiquidityPool(to)));
      return true;
    }
  
    /**
     * @dev Atomically increases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     */
    function increaseAllowance(
      address spender,
      uint256 addedValue
    ) public virtual returns (bool) {
      address owner = _msgSender();
      _approve(owner, spender, allowance(owner, spender) + addedValue);
      return true;
    }
  
    /**
     * @dev Atomically decreases the allowance granted to `spender` by the caller.
     *
     * This is an alternative to {approve} that can be used as a mitigation for
     * problems described in {IERC20-approve}.
     *
     * Emits an {Approval} event indicating the updated allowance.
     *
     * Requirements:
     *
     * - `spender` cannot be the zero address.
     * - `spender` must have allowance for the caller of at least
     * `subtractedValue`.
     */
    function decreaseAllowance(
      address spender,
      uint256 subtractedValue
    ) public virtual returns (bool) {
      address owner = _msgSender();
      uint256 currentAllowance = allowance(owner, spender);
      if (currentAllowance < subtractedValue) {
        _revert(AllowanceDecreasedBelowZero.selector);
      }
      unchecked {
        _approve(owner, spender, currentAllowance - subtractedValue);
      }
  
      return true;
    }
  
    /**
     * @dev Moves `amount` of tokens from `from` to `to`.
     *
     * This internal function is equivalent to {transfer}, and can be used to
     * e.g. implement automatic token fees, slashing mechanisms, etc.
     *
     * Emits a {Transfer} event.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `from` must have a balance of at least `amount`.
     */
    function _transfer(
      address from,
      address to,
      uint256 amount,
      bool applyTax
    ) internal virtual {
      _beforeTokenTransfer(from, to, amount);
  
      // Perform pre-tax validation (e.g. amount doesn't exceed balance, max txn amount)
      uint256 fromBalance = _pretaxValidationAndLimits(from, to, amount);
  
      // Perform autoswap if eligible
      _autoSwap(from, to);
  
      uint256 amountMinusDeductions;
  
      // We limit the number of buys per tx.origin per block:
      if (isLiquidityPool(from)) {
        if (_blockMaxBuysPerOriginExceeded()) {
          _revert(MaxBuysPerBlockExceeded.selector);
        }
        _originBuysPerBlock[tx.origin][block.number] += 1;
      }
  
      // The first by from a liquidity pool is relevant for tokens that use an initial buy
      // type DRI pool, as the very first buy does not have tax or autoburn applied. In all
      // cases where this is NOT the initial buy, or where the initial buy is not relevant,
      // the processing is the same:
      if (_intialBuyTreatmentApplies(from)) {
        initialBuyRelevantAndNotYetCompleted = false;
        amountMinusDeductions = amount;
      } else {
        // Process taxes
        amountMinusDeductions = _taxProcessing(applyTax, to, from, amount);
  
        // Process autoburn
        amountMinusDeductions = _autoburnProcessing(
          from,
          amount,
          amountMinusDeductions
        );
      }
  
      // Perform post-tax validation (e.g. total balance after post-tax amount applied)
      _posttaxValidationAndLimits(from, to, amountMinusDeductions);
  
      _balances[from] = fromBalance - amount;
      _balances[to] += amountMinusDeductions;
  
      emit Transfer(from, to, amountMinusDeductions);
  
      _afterTokenTransfer(from, to, amount);
    }
  
    /**
     * @dev function {_blockMaxBuysPerOriginExceeded}
     *
     * Has the max buy per origin per block been exceeded
     *
     * @return bool this does / doesn't exceed the maximum
     */
    function _blockMaxBuysPerOriginExceeded() internal view returns (bool) {
      return
        _originBuysPerBlock[tx.origin][block.number] >=
        MAX_BUYS_PER_ORIGIN_PER_BLOCK;
    }
  
    /**
     * @dev function {_intialBuyTreatmentApplies}
     *
     * Does a tax free initial buy apply to this contract?
     *
     * @param from_ the from address on the transfer
     * @return bool tax free treatment does / doesn't apply
     */
    function _intialBuyTreatmentApplies(
      address from_
    ) internal view returns (bool) {
      return initialBuyRelevantAndNotYetCompleted && isLiquidityPool(from_);
    }
  
    /**
     * @dev function {_pretaxValidationAndLimits}
     *
     * Perform validation on pre-tax amounts
     *
     * @param from_ From address for the transaction
     * @param to_ To address for the transaction
     * @param amount_ Amount of the transaction
     */
    function _pretaxValidationAndLimits(
      address from_,
      address to_,
      uint256 amount_
    ) internal view returns (uint256 fromBalance_) {
      // This can't be a transfer to the liquidity pool before the funding date
      // UNLESS the from address is this contract. This ensures that the initial
      // LP funding transaction is from this contract using the supply of tokens
      // designated for the LP pool, and therefore the initial price in the pool
      // is being set as expected.
      //
      // This protects from, for example, tokens from a team minted supply being
      // paired with ETH and added to the pool, setting the initial price, BEFORE
      // the initial liquidity is added through this contract.
      if (to_ == uniswapV2Pair && from_ != address(this) && fundedDate == 0) {
        _revert(InitialLiquidityNotYetAdded.selector);
      }
  
      if (from_ == address(0)) {
        _revert(TransferFromZeroAddress.selector);
      }
  
      if (to_ == address(0)) {
        _revert(TransferToZeroAddress.selector);
      }
  
      fromBalance_ = _balances[from_];
  
      if (fromBalance_ < amount_) {
        _revert(TransferAmountExceedsBalance.selector);
      }
  
      if (
        limitsEnforced() &&
        (maxTokensPerTransaction != 0) &&
        ((isLiquidityPool(from_) && !isUnlimited(to_)) ||
          (isLiquidityPool(to_) && !isUnlimited(from_)))
      ) {
        // Liquidity pools aren't always going to round cleanly. This can (and does)
        // mean that a limit of 5,000 tokens (for example) will trigger on a transfer
        // of 5,000 tokens, as the transfer is actually for 5,000.00000000000000213.
        // While 4,999 will work fine, it isn't hugely user friendly. So we buffer
        // the limit with rounding decimals, which in all cases are considerably less
        // than one whole token:
        uint256 roundedLimited;
  
        unchecked {
          roundedLimited = maxTokensPerTransaction + ROUND_DEC;
        }
  
        if (amount_ > roundedLimited) {
          _revert(MaxTokensPerTxnExceeded.selector);
        }
      }
  
      return (fromBalance_);
    }
  
    /**
     * @dev function {_posttaxValidationAndLimits}
     *
     * Perform validation on post-tax amounts
     *
     * @param to_ To address for the transaction
     * @param amount_ Amount of the transaction
     */
    function _posttaxValidationAndLimits(
      address from_,
      address to_,
      uint256 amount_
    ) internal view {
      if (
        limitsEnforced() &&
        (maxTokensPerWallet != 0) &&
        !isUnlimited(to_) &&
        // If this is a buy (from a liquidity pool), we apply if the to_
        // address isn't noted as unlimited:
        (isLiquidityPool(from_) && !isUnlimited(to_))
      ) {
        // Liquidity pools aren't always going to round cleanly. This can (and does)
        // mean that a limit of 5,000 tokens (for example) will trigger on a max holding
        // of 5,000 tokens, as the transfer to achieve that is actually for
        // 5,000.00000000000000213. While 4,999 will work fine, it isn't hugely user friendly.
        // So we buffer the limit with rounding decimals, which in all cases are considerably
        // less than one whole token:
        uint256 roundedLimited;
  
        unchecked {
          roundedLimited = maxTokensPerWallet + ROUND_DEC;
        }
  
        if ((amount_ + balanceOf(to_) > roundedLimited)) {
          _revert(MaxTokensPerWalletExceeded.selector);
        }
      }
    }
  
    /**
     * @dev function {_taxProcessing}
     *
     * Perform tax processing
     *
     * @param applyTax_ Do we apply tax to this transaction?
     * @param to_ The reciever of the token
     * @param from_ The sender of the token
     * @param sentAmount_ The amount being send
     * @return amountLessTax_ The amount that will be recieved, i.e. the send amount minus tax
     */
    function _taxProcessing(
      bool applyTax_,
      address to_,
      address from_,
      uint256 sentAmount_
    ) internal returns (uint256 amountLessTax_) {
      amountLessTax_ = sentAmount_;
      if (_tokenHasTax && applyTax_ && !_autoSwapInProgress) {
        uint256 tax;
  
        // on sell
        if (isLiquidityPool(to_) && totalSellTaxBasisPoints() > 0) {
          if (projectSellTaxBasisPoints > 0) {
            uint256 projectTax = ((sentAmount_ * projectSellTaxBasisPoints) /
              BP_DENOM);
            projectTaxPendingSwap += uint128(projectTax);
            tax += projectTax;
          }
          uint256 metadropSellTax = getMetadropSellTaxBasisPoints();
          if (metadropSellTax > 0) {
            uint256 metadropTax = ((sentAmount_ * metadropSellTax) / BP_DENOM);
            metadropTaxPendingSwap += uint128(metadropTax);
            tax += metadropTax;
          }
        }
        // on buy
        else if (isLiquidityPool(from_) && totalBuyTaxBasisPoints() > 0) {
          if (projectBuyTaxBasisPoints > 0) {
            uint256 projectTax = ((sentAmount_ * projectBuyTaxBasisPoints) /
              BP_DENOM);
            projectTaxPendingSwap += uint128(projectTax);
            tax += projectTax;
          }
          uint256 metadropBuyTax = getMetadropBuyTaxBasisPoints();
          if (metadropBuyTax > 0) {
            uint256 metadropTax = ((sentAmount_ * metadropBuyTax) / BP_DENOM);
            metadropTaxPendingSwap += uint128(metadropTax);
            tax += metadropTax;
          }
        }
  
        if (tax > 0) {
          _balances[address(this)] += tax;
          emit Transfer(from_, address(this), tax);
          amountLessTax_ -= tax;
        }
      }
      return (amountLessTax_);
    }
  
    /**
     * @dev function {_autoburnProcessing}
     *
     * Perform autoburn processing
     *
     * @param from_ The sender of the token
     * @param originalSentAmount_ The original amount being sent, before any deductions (if appropriate)
     * @param currentRecipientAmount_ The amount the recipient is currently due to receive
     * @return amountLessBurn_ The amount that will be recieved, i.e. the currentRecipientAmount_
     * minus the burn applied here
     */
    function _autoburnProcessing(
      address from_,
      uint256 originalSentAmount_,
      uint256 currentRecipientAmount_
    ) internal returns (uint256 amountLessBurn_) {
      amountLessBurn_ = currentRecipientAmount_;
      // Perform autoBurn processing, if appropriate:
      if (
        autoBurnDurationInBlocks != 0 &&
        autoBurnBasisPoints != 0 &&
        !_autoSwapInProgress &&
        isLiquidityPool(from_)
      ) {
        uint256 blocksElapsed = block.number - fundedBlock;
        if (blocksElapsed < autoBurnDurationInBlocks) {
          // Get the blocks remaining in the autoburn period. The more blocks
          // remaining, the higher the proportion of the burn we apply:
          uint256 burnBlocksRemaining = autoBurnDurationInBlocks - blocksElapsed;
          // Calculate the linear burn basis point per remaining block. For example, if our
          // burn basis points = 1500 (15%) and we are burning for three blocks then this
          // will be 1500 / 3 = 500 (5%):
          uint256 linearBurnPerRemainingBlock = autoBurnBasisPoints /
            autoBurnDurationInBlocks;
          // Finally, determine the burn basis points for this block by multiplying the per remaining
          // block burn % by the number of blocks remaining. To follow our example, in the 0th
          // block since funding there are three blocks remaining in the burn period, therefore
          // 500 * 3 = 1500 (15%). Two blocks after funding we have one block remaining in the burn
          // period, and therefore are burning 500 * 1 = 500 (5%). Three blocks after funding we do not
          // reach this point in the logic, as the blocksElapsed is 3 and needs to be UNDER 3 to enter
          // this code.
          uint256 burnBasisPointsForThisBlock = burnBlocksRemaining *
            linearBurnPerRemainingBlock;
  
          // This is eligible for burn. Send the basis points amount of
          // the originalSentAmount_ to the zero address:
          uint256 burnAmount = ((originalSentAmount_ *
            burnBasisPointsForThisBlock) / BP_DENOM);
  
          _burn(from_, burnAmount);
          amountLessBurn_ -= burnAmount;
        }
      }
      return (amountLessBurn_);
    }
  
    /**
     * @dev totalTaxPendingSwap
     *
     * Return the total tax awaiting swap:
     */
    function totalTaxPendingSwap() public view returns (uint256) {
      return projectTaxPendingSwap + metadropTaxPendingSwap;
    }
  
    /**
     * @dev function {_autoSwap}
     *
     * Automate the swap of accumulated tax fees to native token
     *
     * @param from_ The sender of the token
     * @param to_ The recipient of the token
     */
    function _autoSwap(address from_, address to_) internal {
      if (_tokenHasTax) {
        uint256 totalTaxBalance = totalTaxPendingSwap();
        uint256 swapBalance = totalTaxBalance;
  
        uint256 swapThresholdInTokens = (_totalSupply *
          swapThresholdBasisPoints) / BP_DENOM;
  
        if (_eligibleForSwap(from_, to_, swapBalance, swapThresholdInTokens)) {
          // Store that a swap back is in progress:
          _autoSwapInProgress = true;
          // Increment the swaps per block counter:
          _autoswapForBlock[block.number] += 1;
          // Check if we need to reduce the amount of tokens for this swap:
          if (
            swapBalance > swapThresholdInTokens * MAX_AUTOSWAP_THRESHOLD_MULTIPLE
          ) {
            swapBalance = swapThresholdInTokens * MAX_AUTOSWAP_THRESHOLD_MULTIPLE;
          }
          // Perform the auto swap to native token:
          _swapTaxForNative(swapBalance, totalTaxBalance);
          // Flag that the autoswap is complete:
          _autoSwapInProgress = false;
        }
      }
    }
  
    /**
     * @dev function {_eligibleForSwap}
     *
     * Is the current transfer eligible for autoswap
     *
     * @param from_ The sender of the token
     * @param to_ The recipient of the token
     * @param taxBalance_ The current accumulated tax balance
     * @param swapThresholdInTokens_ The swap threshold as a token amount
     */
    function _eligibleForSwap(
      address from_,
      address to_,
      uint256 taxBalance_,
      uint256 swapThresholdInTokens_
    ) internal view returns (bool) {
      return (taxBalance_ >= swapThresholdInTokens_ &&
        !_autoSwapInProgress &&
        !isLiquidityPool(from_) &&
        from_ != address(_uniswapRouter) &&
        to_ != address(_uniswapRouter) &&
        from_ != address(driPool) &&
        _autoswapForBlock[block.number] < MAX_AUTOSWAPS_PER_BLOCK);
    }
  
    /**
     * @dev function {_swapTaxForNative}
     *
     * Swap tokens taken as tax for native token
     *
     * @param swapBalance_ The current accumulated tax balance to swap
     * @param totalTaxBalance_ The current accumulated total tax balance
     */
    function _swapTaxForNative(
      uint256 swapBalance_,
      uint256 totalTaxBalance_
    ) internal {
      uint256 preSwapBalance = address(this).balance;
  
      address[] memory path = new address[](2);
      path[0] = address(this);
      path[1] = _uniswapRouter.WETH();
  
      // Wrap external calls in try / catch to handle errors
      try
        _uniswapRouter.swapExactTokensForETHSupportingFeeOnTransferTokens(
          swapBalance_,
          0,
          path,
          address(this),
          block.timestamp + 600
        )
      {
        uint256 postSwapBalance = address(this).balance;
  
        uint256 balanceToDistribute = postSwapBalance - preSwapBalance;
  
        uint256 projectBalanceToDistribute = (balanceToDistribute *
          projectTaxPendingSwap) / totalTaxBalance_;
  
        uint256 metadropBalanceToDistribute = (balanceToDistribute *
          metadropTaxPendingSwap) / totalTaxBalance_;
  
        // We will not have swapped all tax tokens IF the amount was greater than the max auto swap.
        // We therefore cannot just set the pending swap counters to 0. Instead, in this scenario,
        // we must reduce them in proportion to the swap amount vs the remaining balance + swap
        // amount.
        //
        // For example:
        //  * swap Balance is 250
        //  * contract balance is 385.
        //  * projectTaxPendingSwap is 300
        //  * metadropTaxPendingSwap is 85.
        //
        // The new total for the projectTaxPendingSwap is:
        //   = 300 - ((300 * 250) / 385)
        //   = 300 - 194
        //   = 106
        // The new total for the metadropTaxPendingSwap is:
        //   = 85 - ((85 * 250) / 385)
        //   = 85 - 55
        //   = 30
        //
  
        if (swapBalance_ < totalTaxBalance_) {
          // Calculate the project tax spending swap reduction amount:
          uint256 projectTaxPendingSwapReduction = (projectTaxPendingSwap *
            swapBalance_) / totalTaxBalance_;
          projectTaxPendingSwap -= uint128(projectTaxPendingSwapReduction);
  
          // The metadrop tax pending swap reduction is therefore the total swap amount minus the
          // project tax spending swap reduction:
          metadropTaxPendingSwap -= uint128(
            swapBalance_ - projectTaxPendingSwapReduction
          );
        } else {
          (projectTaxPendingSwap, metadropTaxPendingSwap) = (0, 0);
        }
  
        // Distribute to treasuries:
        bool success;
        address weth;
        uint256 gas;
  
        if (projectBalanceToDistribute > 0) {
          // If no gas limit was provided or provided gas limit greater than gas left, just use the remaining gas.
          gas = (CALL_GAS_LIMIT == 0 || CALL_GAS_LIMIT > gasleft())
            ? gasleft()
            : CALL_GAS_LIMIT;
  
          // We limit the gas passed so that a called address cannot cause a block out of gas error:
          (success, ) = projectTaxRecipient.call{
            value: projectBalanceToDistribute,
            gas: gas
          }("");
  
          // If the ETH transfer fails, wrap the ETH and send it as WETH. We do this so that a called
          // address cannot cause this transfer to fail, either intentionally or by mistake:
          if (!success) {
            if (weth == address(0)) {
              weth = _uniswapRouter.WETH();
            }
  
            try IWETH(weth).deposit{value: projectBalanceToDistribute}() {
              try
                IERC20(address(weth)).transfer(
                  projectTaxRecipient,
                  projectBalanceToDistribute
                )
              {} catch {
                // Dont allow a failed external call (in this case to WETH) to stop a transfer.
                // Emit that this has occured and continue.
                emit ExternalCallError(1);
              }
            } catch {
              // Dont allow a failed external call (in this case to WETH) to stop a transfer.
              // Emit that this has occured and continue.
              emit ExternalCallError(2);
            }
          }
        }
  
        if (metadropBalanceToDistribute > 0) {
          // If no gas limit was provided or provided gas limit greater than gas left, just use the remaining gas.
          gas = (CALL_GAS_LIMIT == 0 || CALL_GAS_LIMIT > gasleft())
            ? gasleft()
            : CALL_GAS_LIMIT;
  
          (success, ) = metadropTaxRecipient.call{
            value: metadropBalanceToDistribute,
            gas: gas
          }("");
  
          // If the ETH transfer fails, wrap the ETH and send it as WETH. We do this so that a called
          // address cannot cause this transfer to fail, either intentionally or by mistake:
          if (!success) {
            if (weth == address(0)) {
              weth = _uniswapRouter.WETH();
            }
            try IWETH(weth).deposit{value: metadropBalanceToDistribute}() {
              try
                IERC20(address(weth)).transfer(
                  metadropTaxRecipient,
                  metadropBalanceToDistribute
                )
              {} catch {
                // Dont allow a failed external call (in this case to WETH) to stop a transfer.
                // Emit that this has occured and continue.
                emit ExternalCallError(3);
              }
            } catch {
              // Dont allow a failed external call (in this case to WETH) to stop a transfer.
              // Emit that this has occured and continue.
              emit ExternalCallError(4);
            }
          }
        }
      } catch {
        // Dont allow a failed external call (in this case to uniswap) to stop a transfer.
        // Emit that this has occured and continue.
        emit ExternalCallError(5);
      }
    }
  
    /**
     * @dev distributeTaxTokens
     *
     * Allows the distribution of tax tokens to the designated recipient(s)
     *
     * As part of standard processing the tax token balance being above the threshold
     * will trigger an autoswap to ETH and distribution of this ETH to the designated
     * recipients. This is automatic and there is no need for user involvement.
     *
     * As part of this swap there are a number of calculations performed, particularly
     * if the tax balance is above MAX_AUTOSWAP_THRESHOLD_MULTIPLE.
     *
     * Testing indicates that these calculations are safe. But given the data / code
     * interactions it remains possible that some edge case set of scenarios may cause
     * an issue with these calculations.
     *
     * This method is therefore provided as a 'fallback' option to safely distribute
     * accumulated taxes from the contract, with a direct transfer of the ERC20 tokens
     * themselves.
     */
    function distributeTaxTokens() external notDuringAutoswap {
      if (projectTaxPendingSwap > 0) {
        uint256 projectDistribution = projectTaxPendingSwap;
        projectTaxPendingSwap = 0;
        _transfer(address(this), projectTaxRecipient, projectDistribution, false);
      }
  
      if (metadropTaxPendingSwap > 0) {
        uint256 metadropDistribution = metadropTaxPendingSwap;
        metadropTaxPendingSwap = 0;
        _transfer(
          address(this),
          metadropTaxRecipient,
          metadropDistribution,
          false
        );
      }
    }
  
    /**
     * @dev function {rescueETH}
     *
     * A withdraw function to allow ETH to be rescued.
     *
     * This contract should never hold ETH. The only envisaged scenario where
     * it might hold ETH is a failed autoswap where the uniswap swap has completed,
     * the recipient of ETH reverts, the contract then wraps to WETH and the
     * wrap to WETH fails.
     *
     * This feels unlikely. But, for safety, we include this method.
     *
     * @param amount_ The amount to withdraw
     */
    function rescueETH(uint256 amount_) external notDuringAutoswap {
      (bool success, ) = projectTaxRecipient.call{value: amount_}("");
      if (!success) {
        _revert(TransferFailed.selector);
      }
    }
  
    /**
     * @dev function {rescueERC20}
     *
     * A withdraw function to allow ERC20s (except address(this)) to be rescued.
     *
     * This contract should never hold ERC20s other than tax tokens. The only envisaged
     * scenario where it might hold an ERC20 is a failed autoswap where the uniswap swap
     * has completed, the recipient of ETH reverts, the contract then wraps to WETH, the
     * wrap to WETH succeeds, BUT then the transfer of WETH fails.
     *
     * This feels even less likely than the scenario where ETH is held on the contract.
     * But, for safety, we include this method.
     *
     * @param token_ The ERC20 contract
     * @param amount_ The amount to withdraw
     */
    function rescueERC20(
      address token_,
      uint256 amount_
    ) external notDuringAutoswap {
      if (token_ == address(this)) {
        _revert(CannotWithdrawThisToken.selector);
      }
      IERC20(token_).safeTransfer(projectTaxRecipient, amount_);
    }
  
    /**
     * @dev function {rescueExcessToken}
     *
     * A withdraw function to allow ERC20s from this address that are above
     * the accrued tax balance to be rescued.
     */
    function rescueExcessToken(uint256 amount_) external notDuringAutoswap {
      // Cannot perform this before the token has been funded:
      if (fundedDate == 0) {
        _revert(CannotPerformPriorToFunding.selector);
      }
  
      uint256 excessToken = balanceOf(address(this)) - totalTaxPendingSwap();
  
      if (amount_ > excessToken) {
        _revert(AmountExceedsAvailable.selector);
      }
  
      IERC20(address(this)).safeTransfer(projectTaxRecipient, amount_);
    }
  
    /** @dev Creates `amount` tokens and assigns them to `account`, increasing
     * the total supply.
     *
     * Emits a {Transfer} event with `from` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function _mint(address account, uint256 amount) internal virtual {
      if (account == address(0)) {
        _revert(MintToZeroAddress.selector);
      }
  
      _beforeTokenTransfer(address(0), account, amount);
  
      _totalSupply += uint120(amount);
      unchecked {
        // Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
        _balances[account] += amount;
      }
      emit Transfer(address(0), account, amount);
  
      _afterTokenTransfer(address(0), account, amount);
    }
  
    /**
     * @dev Destroys `amount` tokens from `account`, reducing the
     * total supply.
     *
     * Emits a {Transfer} event with `to` set to the zero address.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     * - `account` must have at least `amount` tokens.
     */
    function _burn(address account, uint256 amount) internal virtual {
      if (account == address(0)) {
        _revert(BurnFromTheZeroAddress.selector);
      }
  
      _beforeTokenTransfer(account, address(0), amount);
  
      uint256 accountBalance = _balances[account];
      if (accountBalance < amount) {
        _revert(BurnExceedsBalance.selector);
      }
  
      unchecked {
        _balances[account] = accountBalance - amount;
        // Overflow not possible: amount <= accountBalance <= totalSupply.
        _totalSupply -= uint120(amount);
      }
  
      emit Transfer(account, address(0), amount);
  
      _afterTokenTransfer(account, address(0), amount);
    }
  
    /**
     * @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
     *
     * This internal function is equivalent to `approve`, and can be used to
     * e.g. set automatic allowances for certain subsystems, etc.
     *
     * Emits an {Approval} event.
     *
     * Requirements:
     *
     * - `owner` cannot be the zero address.
     * - `spender` cannot be the zero address.
     */
    function _approve(
      address owner,
      address spender,
      uint256 amount
    ) internal virtual {
      if (owner == address(0)) {
        _revert(ApproveFromTheZeroAddress.selector);
      }
  
      if (spender == address(0)) {
        _revert(ApproveToTheZeroAddress.selector);
      }
  
      _allowances[owner][spender] = amount;
      emit Approval(owner, spender, amount);
    }
  
    /**
     * @dev Updates `owner` s allowance for `spender` based on spent `amount`.
     *
     * Does not update the allowance amount in case of infinite allowance.
     * Revert if not enough allowance is available.
     *
     * Might emit an {Approval} event.
     */
    function _spendAllowance(
      address owner,
      address spender,
      uint256 amount
    ) internal virtual {
      uint256 currentAllowance = allowance(owner, spender);
      if (currentAllowance != type(uint256).max) {
        if (currentAllowance < amount) {
          _revert(InsufficientAllowance.selector);
        }
  
        unchecked {
          _approve(owner, spender, currentAllowance - amount);
        }
      }
    }
  
    /**
     * @dev Destroys a `value` amount of tokens from the caller.
     *
     * See {ERC20-_burn}.
     */
    function burn(uint256 value) public virtual {
      _burn(_msgSender(), value);
    }
  
    /**
     * @dev Destroys a `value` amount of tokens from `account`, deducting from
     * the caller's allowance.
     *
     * See {ERC20-_burn} and {ERC20-allowance}.
     *
     * Requirements:
     *
     * - the caller must have allowance for ``accounts``'s tokens of at least
     * `value`.
     */
    function burnFrom(address account, uint256 value) public virtual {
      _spendAllowance(account, _msgSender(), value);
      _burn(account, value);
    }
  
    /**
     * @dev Hook that is called before any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * will be transferred to `to`.
     * - when `from` is zero, `amount` tokens will be minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens will be burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _beforeTokenTransfer(
      address from,
      address to,
      uint256 amount
    ) internal virtual {}
  
    /**
     * @dev Hook that is called after any transfer of tokens. This includes
     * minting and burning.
     *
     * Calling conditions:
     *
     * - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
     * has been transferred to `to`.
     * - when `from` is zero, `amount` tokens have been minted for `to`.
     * - when `to` is zero, `amount` of ``from``'s tokens have been burned.
     * - `from` and `to` are never both zero.
     *
     * To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
     */
    function _afterTokenTransfer(
      address from,
      address to,
      uint256 amount
    ) internal virtual {}
  
    receive() external payable {}
  }
  
  // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  // @@                                                                                                @@
  // @@   Metadrop has no affiliation with and does not endorse this token or its creators in any      @@
  // @@   way, unless otherwise stated. For all terms and conditions associated with tokens launched   @@
  // @@   using Metadrop software, refer to the terms published at metadrop[dot]com/legal.             @@
  // @@                                                                                                @@
  // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@