ETH Price: $3,324.07 (+6.77%)
 

Overview

Max Total Supply

185,201,226,334,568,637.309637 ERC20 ***

Holders

0

Transfers

-
0

Market

Price

$0.00 @ 0.000000 ETH

Onchain Market Cap

-

Circulating Supply Market Cap

-

Other Info

Token Contract (WITH 6 Decimals)

Loading...
Loading
Loading...
Loading
Loading...
Loading

Click here to update the token information / general information

Contract Source Code Verified (Exact Match)

Contract Name:
UniswapV2DynamicERC20

Compiler Version
v0.8.24+commit.e11b9ed9

Optimization Enabled:
Yes with 200 runs

Other Settings:
paris EvmVersion
// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {
    IERC20Metadata
} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

import {
    IUniswapV2DynamicPriceRouter
} from "../router/IUniswapV2DynamicPriceRouter.sol";
import {DynamicERC20} from "../abstract/DynamicERC20.sol";

/*
 ____                 _            _   __  __ _       _   
|  _ \ _ __ ___   __| |_   _  ___| |_|  \/  (_)_ __ | |_ 
| |_) | '__/ _ \ / _` | | | |/ __| __| |\/| | | '_ \| __|
|  __/| | | (_) | (_| | |_| | (__| |_| |  | | | | | | |_ 
|_|   |_|  \___/ \__,_|\__,_|\___|\__|_|  |_|_|_| |_|\__|
 
 NFT based payment system to mint products onchain with one-time payments and 
 recurring permissionless subscriptions.

 https://productmint.io
*/

/**
 * @title UniswapV2DynamicERC20
 * @notice A dynamic ERC20 token that uses Uniswap V2 to get the current swap price.
 * @dev A UniswapV2DynamicERC20 cannot be minted, burned, or transferred
 *
 * Used within the ProductMint system to act as a proxy for the base token against the quote token.
 * The base token is used to charge for payment.
 * The quote token is used for price targeting.
 * A dynamic price router is used to get the current swap price at a dex such as Uniswap.
 *
 * For example, assume the base token is WETH and the quote token is USDC.
 * An organization can use the DynamicERC20 to create a pricing model that targets a price of 100 USDC.
 * Then, when a user purchases a product, 100 USDC worth of WETH will be transferred to the organization.
 */
contract UniswapV2DynamicERC20 is DynamicERC20, Ownable2Step {
    constructor(
        string memory _name,
        string memory _symbol,
        address _baseToken,
        address _quoteToken,
        address _dynamicPriceRouter,
        address[] memory _baseToQuotePath,
        address[] memory _quoteToBasePath
    )
        DynamicERC20(
            _name,
            _symbol,
            _baseToken,
            _quoteToken,
            _dynamicPriceRouter
        )
        Ownable(_msgSender())
    {
        _setBaseToQuotePath(_baseToQuotePath);
        _setQuoteToBasePath(_quoteToBasePath);
    }

    /**
     * IDynamicERC20
     */

    function getBaseTokenPrice() external view returns (uint256) {
        return _getQuoteTokenAmount(10 ** IERC20Metadata(baseToken).decimals());
    }

    function balanceOfQuote(address _account) external view returns (uint256) {
        return _getQuoteTokenAmount(IERC20(baseToken).balanceOf(_account));
    }

    function allowanceQuote(
        address owner,
        address spender
    ) external view returns (uint256) {
        return
            _getQuoteTokenAmount(IERC20(baseToken).allowance(owner, spender));
    }

    function getBaseTokenAmount(
        uint256 quoteTokenAmount
    ) external view returns (address, uint256) {
        return (baseToken, _getBaseTokenAmount(quoteTokenAmount));
    }

    function getQuoteTokenAmount(
        uint256 baseTokenAmount
    ) external view returns (address, uint256) {
        return (quoteToken, _getQuoteTokenAmount(baseTokenAmount));
    }

    function _getBaseTokenAmount(
        uint256 amount
    ) internal view returns (uint256) {
        if (amount == 0) return 0;
        return
            IUniswapV2DynamicPriceRouter(dynamicPriceRouter)
                .getPriceFeesRemoved(amount, quoteToBasePath);
    }

    function _getQuoteTokenAmount(
        uint256 amount
    ) internal view returns (uint256) {
        if (amount == 0) return 0;
        return
            IUniswapV2DynamicPriceRouter(dynamicPriceRouter)
                .getPriceFeesRemoved(amount, baseToQuotePath);
    }

    /**
     * Base to quote path
     */

    /**
     * @notice Emitted when the base to quote path is set
     * @param dynamicERC20 The address of the current dynamic ERC20 contract
     * @param baseToken The address of the base token
     * @param quoteToken The address of the quote token
     * @param baseToQuotePath The path used to convert the base token to the quote token
     */
    event UniswapV2BaseToQuotePathSet(
        address indexed dynamicERC20,
        address indexed baseToken,
        address indexed quoteToken,
        address[] baseToQuotePath
    );

    function setBaseToQuotePath(
        address[] calldata _baseToQuotePath
    ) external onlyOwner {
        _setBaseToQuotePath(_baseToQuotePath);
    }

    function _setBaseToQuotePath(address[] memory _baseToQuotePath) internal {
        _checkBaseToQuotePath(_baseToQuotePath);
        _checkPriceValid(_baseToQuotePath);

        baseToQuotePath = _baseToQuotePath;

        emit UniswapV2BaseToQuotePathSet(
            address(this),
            baseToken,
            quoteToken,
            _baseToQuotePath
        );
    }

    /**
     * Quote to base path
     */

    /**
     * @notice Emitted when the quote to base path is set
     * @param dynamicERC20 The address of the current dynamic ERC20 contract
     * @param quoteToBasePath The path used to convert the quote token to the base token
     */
    event UniswapV2QuoteToBasePathSet(
        address indexed dynamicERC20,
        address indexed baseToken,
        address indexed quoteToken,
        address[] quoteToBasePath
    );

    function setQuoteToBasePath(
        address[] calldata _quoteToBasePath
    ) external onlyOwner {
        _setQuoteToBasePath(_quoteToBasePath);
    }

    function _setQuoteToBasePath(address[] memory _quoteToBasePath) internal {
        _checkQuoteToBasePath(_quoteToBasePath);
        _checkPriceValid(_quoteToBasePath);

        quoteToBasePath = _quoteToBasePath;

        emit UniswapV2QuoteToBasePathSet(
            address(this),
            baseToken,
            quoteToken,
            _quoteToBasePath
        );
    }

    /**
     * Path updates
     */

    /**
     * @dev Error when attempting to set an invalid path
     */
    error InvalidPath(address[] _path);

    function _checkPriceValid(address[] memory _path) internal view {
        try
            IUniswapV2DynamicPriceRouter(dynamicPriceRouter)
                .getPriceFeesRemoved(
                    10 ** IERC20Metadata(_path[0]).decimals(),
                    _path
                )
        {} catch {
            revert InvalidPath(_path);
        }
    }

    /**
     * Dynamic price router updates
     */

    function setDynamicPriceRouter(
        address _dynamicPriceRouter
    ) external onlyOwner {
        _setDynamicPriceRouter(_dynamicPriceRouter);
    }

    function _setDynamicPriceRouter(
        address _dynamicPriceRouter
    ) internal override {
        require(
            IERC165(_dynamicPriceRouter).supportsInterface(
                type(IUniswapV2DynamicPriceRouter).interfaceId
            ),
            "Does not implement IUniswapV2DynamicPriceRouter"
        );

        super._setDynamicPriceRouter(_dynamicPriceRouter);
    }
}

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

pragma solidity ^0.8.20;

import {IAccessControl} from "./IAccessControl.sol";
import {Context} from "../utils/Context.sol";
import {ERC165} from "../utils/introspection/ERC165.sol";

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

    mapping(bytes32 role => RoleData) private _roles;

    bytes32 public constant DEFAULT_ADMIN_ROLE = 0x00;

    /**
     * @dev Modifier that checks that an account has a specific role. Reverts
     * with an {AccessControlUnauthorizedAccount} error including the required role.
     */
    modifier onlyRole(bytes32 role) {
        _checkRole(role);
        _;
    }

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

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

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `_msgSender()`
     * is missing `role`. Overriding this function changes the behavior of the {onlyRole} modifier.
     */
    function _checkRole(bytes32 role) internal view virtual {
        _checkRole(role, _msgSender());
    }

    /**
     * @dev Reverts with an {AccessControlUnauthorizedAccount} error if `account`
     * is missing `role`.
     */
    function _checkRole(bytes32 role, address account) internal view virtual {
        if (!hasRole(role, account)) {
            revert AccessControlUnauthorizedAccount(account, role);
        }
    }

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

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

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

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

        _revokeRole(role, callerConfirmation);
    }

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

    /**
     * @dev Attempts to grant `role` to `account` and returns a boolean indicating if `role` was granted.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleGranted} event.
     */
    function _grantRole(bytes32 role, address account) internal virtual returns (bool) {
        if (!hasRole(role, account)) {
            _roles[role].hasRole[account] = true;
            emit RoleGranted(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }

    /**
     * @dev Attempts to revoke `role` to `account` and returns a boolean indicating if `role` was revoked.
     *
     * Internal function without access restriction.
     *
     * May emit a {RoleRevoked} event.
     */
    function _revokeRole(bytes32 role, address account) internal virtual returns (bool) {
        if (hasRole(role, account)) {
            _roles[role].hasRole[account] = false;
            emit RoleRevoked(role, account, _msgSender());
            return true;
        } else {
            return false;
        }
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/IAccessControl.sol)

pragma solidity ^0.8.20;

/**
 * @dev External interface of AccessControl declared to support ERC-165 detection.
 */
interface IAccessControl {
    /**
     * @dev The `account` is missing a role.
     */
    error AccessControlUnauthorizedAccount(address account, bytes32 neededRole);

    /**
     * @dev The caller of a function is not the expected one.
     *
     * NOTE: Don't confuse with {AccessControlUnauthorizedAccount}.
     */
    error AccessControlBadConfirmation();

    /**
     * @dev Emitted when `newAdminRole` is set as ``role``'s admin role, replacing `previousAdminRole`
     *
     * `DEFAULT_ADMIN_ROLE` is the starting admin for all roles, despite
     * {RoleAdminChanged} not being emitted signaling this.
     */
    event RoleAdminChanged(bytes32 indexed role, bytes32 indexed previousAdminRole, bytes32 indexed newAdminRole);

    /**
     * @dev Emitted when `account` is granted `role`.
     *
     * `sender` is the account that originated the contract call. This account bears the admin role (for the granted role).
     * Expected in cases where the role was granted using the internal {AccessControl-_grantRole}.
     */
    event RoleGranted(bytes32 indexed role, address indexed account, address indexed sender);

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

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

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

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

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

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

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

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @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.
 *
 * The initial owner is set to the address provided by the deployer. 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 Context {
    address private _owner;

    /**
     * @dev The caller account is not authorized to perform an operation.
     */
    error OwnableUnauthorizedAccount(address account);

    /**
     * @dev The owner is not a valid owner account. (eg. `address(0)`)
     */
    error OwnableInvalidOwner(address owner);

    event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);

    /**
     * @dev Initializes the contract setting the address provided by the deployer as the initial owner.
     */
    constructor(address initialOwner) {
        if (initialOwner == address(0)) {
            revert OwnableInvalidOwner(address(0));
        }
        _transferOwnership(initialOwner);
    }

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

    /**
     * @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 OwnableInvalidOwner(address(0));
        }
        _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);
    }
}

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (access/Ownable2Step.sol)

pragma solidity ^0.8.20;

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

/**
 * @dev Contract module which provides access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * This extension of the {Ownable} contract includes a two-step mechanism to transfer
 * ownership, where the new owner must call {acceptOwnership} in order to replace the
 * old one. This can help prevent common mistakes, such as transfers of ownership to
 * incorrect accounts, or to contracts that are unable to interact with the
 * permission system.
 *
 * 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.
     *
     * Setting `newOwner` to the zero address is allowed; this can be used to cancel an initiated ownership transfer.
     */
    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(sender);
        }
        _transferOwnership(sender);
    }
}

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

pragma solidity ^0.8.20;

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

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

pragma solidity ^0.8.20;

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

/**
 * @dev Interface for the optional metadata functions from the ERC-20 standard.
 */
interface IERC20Metadata is IERC20 {
    /**
     * @dev Returns the name of the token.
     */
    function name() external view returns (string memory);

    /**
     * @dev Returns the symbol of the token.
     */
    function symbol() external view returns (string memory);

    /**
     * @dev Returns the decimals places of the token.
     */
    function decimals() external view returns (uint8);
}

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

pragma solidity ^0.8.20;

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

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

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

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

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

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

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

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

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

pragma solidity ^0.8.20;

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

/**
 * @dev Required interface of an ERC-721 compliant contract.
 */
interface IERC721 is IERC165 {
    /**
     * @dev Emitted when `tokenId` token is transferred from `from` to `to`.
     */
    event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables `approved` to manage the `tokenId` token.
     */
    event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);

    /**
     * @dev Emitted when `owner` enables or disables (`approved`) `operator` to manage all of its assets.
     */
    event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

    /**
     * @dev Returns the number of tokens in ``owner``'s account.
     */
    function balanceOf(address owner) external view returns (uint256 balance);

    /**
     * @dev Returns the owner of the `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function ownerOf(uint256 tokenId) external view returns (address owner);

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the ERC-721 protocol to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must have been allowed to move this token by either {approve} or
     *   {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon
     *   a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Transfers `tokenId` token from `from` to `to`.
     *
     * WARNING: Note that the caller is responsible to confirm that the recipient is capable of receiving ERC-721
     * or else they may be permanently lost. Usage of {safeTransferFrom} prevents loss, though the caller must
     * understand this adds an external call which potentially creates a reentrancy vulnerability.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must be owned by `from`.
     * - If the caller is not `from`, it must be approved to move this token by either {approve} or {setApprovalForAll}.
     *
     * Emits a {Transfer} event.
     */
    function transferFrom(address from, address to, uint256 tokenId) external;

    /**
     * @dev Gives permission to `to` to transfer `tokenId` token to another account.
     * The approval is cleared when the token is transferred.
     *
     * Only a single account can be approved at a time, so approving the zero address clears previous approvals.
     *
     * Requirements:
     *
     * - The caller must own the token or be an approved operator.
     * - `tokenId` must exist.
     *
     * Emits an {Approval} event.
     */
    function approve(address to, uint256 tokenId) external;

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the address zero.
     *
     * Emits an {ApprovalForAll} event.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns the account approved for `tokenId` token.
     *
     * Requirements:
     *
     * - `tokenId` must exist.
     */
    function getApproved(uint256 tokenId) external view returns (address operator);

    /**
     * @dev Returns if the `operator` is allowed to manage all of the assets of `owner`.
     *
     * See {setApprovalForAll}
     */
    function isApprovedForAll(address owner, address operator) external view returns (bool);
}

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

pragma solidity ^0.8.20;

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

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

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

pragma solidity ^0.8.20;

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

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

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

pragma solidity ^0.8.20;

/**
 * @dev Interface of the ERC-165 standard, as defined in the
 * https://eips.ethereum.org/EIPS/eip-165[ERC].
 *
 * Implementers can declare support of contract interfaces, which can then be
 * queried by others ({ERC165Checker}).
 *
 * For an implementation, see {ERC165}.
 */
interface IERC165 {
    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30 000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);
}

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

pragma solidity ^0.8.20;

import {Context} from "../utils/Context.sol";

/**
 * @dev Contract module which allows children to implement an emergency stop
 * mechanism that can be triggered by an authorized account.
 *
 * This module is used through inheritance. It will make available the
 * modifiers `whenNotPaused` and `whenPaused`, which can be applied to
 * the functions of your contract. Note that they will not be pausable by
 * simply including this module, only once the modifiers are put in place.
 */
abstract contract Pausable is Context {
    bool private _paused;

    /**
     * @dev Emitted when the pause is triggered by `account`.
     */
    event Paused(address account);

    /**
     * @dev Emitted when the pause is lifted by `account`.
     */
    event Unpaused(address account);

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

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

    /**
     * @dev Initializes the contract in unpaused state.
     */
    constructor() {
        _paused = false;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is not paused.
     *
     * Requirements:
     *
     * - The contract must not be paused.
     */
    modifier whenNotPaused() {
        _requireNotPaused();
        _;
    }

    /**
     * @dev Modifier to make a function callable only when the contract is paused.
     *
     * Requirements:
     *
     * - The contract must be paused.
     */
    modifier whenPaused() {
        _requirePaused();
        _;
    }

    /**
     * @dev Returns true if the contract is paused, and false otherwise.
     */
    function paused() public view virtual returns (bool) {
        return _paused;
    }

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

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

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

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

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

pragma solidity ^0.8.20;

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

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

    uint256 private _status;

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

    constructor() {
        _status = NOT_ENTERED;
    }

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

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

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

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

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

// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/structs/EnumerableSet.sol)
// This file was procedurally generated from scripts/generate/templates/EnumerableSet.js.

pragma solidity ^0.8.20;

/**
 * @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 is the index of the value in the `values` array plus 1.
        // Position 0 is used to mean a value is not in the set.
        mapping(bytes32 value => uint256) _positions;
    }

    /**
     * @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._positions[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 cache the value's position to prevent multiple reads from the same storage slot
        uint256 position = set._positions[value];

        if (position != 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 valueIndex = position - 1;
            uint256 lastIndex = set._values.length - 1;

            if (valueIndex != lastIndex) {
                bytes32 lastValue = set._values[lastIndex];

                // Move the lastValue to the index where the value to delete is
                set._values[valueIndex] = lastValue;
                // Update the tracked position of the lastValue (that was just moved)
                set._positions[lastValue] = position;
            }

            // Delete the slot where the moved value was stored
            set._values.pop();

            // Delete the tracked position for the deleted slot
            delete set._positions[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._positions[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;

        assembly ("memory-safe") {
            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;

        assembly ("memory-safe") {
            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;

        assembly ("memory-safe") {
            result := store
        }

        return result;
    }
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ERC165} from "@openzeppelin/contracts/utils/introspection/ERC165.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {
    IERC20Metadata
} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

import {IDynamicERC20} from "../tokens/IDynamicERC20.sol";
import {IDynamicPriceRouter} from "../router/IDynamicPriceRouter.sol";

/**
 * @title DynamicERC20
 * @notice DynamicERC20 is an abstract contract that implements the IDynamicERC20 interface.
 * It is used to create dynamic ERC20 tokens that can be used to pay for products.
 *
 * This contract should be used to create Dynamic tokens using dex routers.
 */
abstract contract DynamicERC20 is
    ERC165,
    IERC20,
    IERC20Metadata,
    IDynamicERC20
{
    // Name for the token
    string private _name;

    // Symbol for the token
    string private _symbol;

    // Token used for payment
    address public immutable baseToken;

    // Token used for price targeting
    address public immutable quoteToken;

    // Path used to convert the base token to the quote token
    address[] internal baseToQuotePath;

    // Path used to convert the quote token to the base token
    address[] internal quoteToBasePath;

    // Dynamic price router to interact with the dex
    address public dynamicPriceRouter;

    constructor(
        string memory name_,
        string memory symbol_,
        address _baseToken,
        address _quoteToken,
        address _dynamicPriceRouter
    ) {
        require(_baseToken != address(0), "Base token cannot be zero address");
        require(
            _quoteToken != address(0),
            "Quote token cannot be zero address"
        );
        require(
            _baseToken != _quoteToken,
            "Base and quote token cannot be the same"
        );

        _name = name_;
        _symbol = symbol_;

        baseToken = _baseToken;
        quoteToken = _quoteToken;

        _setDynamicPriceRouter(_dynamicPriceRouter);
    }

    /**
     * IDynamicERC20
     */

    function routerName() external view virtual returns (string memory) {
        return IDynamicPriceRouter(dynamicPriceRouter).ROUTER_NAME();
    }

    function getBaseToQuotePath()
        external
        view
        virtual
        returns (address[] memory)
    {
        return baseToQuotePath;
    }

    function getQuoteToBasePath()
        external
        view
        virtual
        returns (address[] memory)
    {
        return quoteToBasePath;
    }

    /**
     * IERC20Metadata
     */

    function name() external view virtual returns (string memory) {
        return _name;
    }

    function symbol() external view virtual returns (string memory) {
        return _symbol;
    }

    function decimals() external view virtual returns (uint8) {
        return IERC20Metadata(quoteToken).decimals();
    }

    /**
     * IERC20
     */

    error TransferNotAllowed();
    error ApproveNotAllowed();

    function totalSupply() external view virtual returns (uint256) {
        return IERC20(baseToken).totalSupply();
    }

    function balanceOf(
        address account
    ) external view virtual returns (uint256) {
        return IERC20(baseToken).balanceOf(account);
    }

    /**
     * @dev Not allowed to transfer the token.
     */
    function transfer(address, uint256) external pure returns (bool) {
        revert TransferNotAllowed();
    }

    function allowance(
        address owner,
        address spender
    ) external view virtual returns (uint256) {
        return IERC20(baseToken).allowance(owner, spender);
    }

    /**
     * @dev Not allowed to approve the token.
     */
    function approve(address, uint256) external pure returns (bool) {
        revert ApproveNotAllowed();
    }

    /**
     * @dev Not allowed to transfer the token.
     */
    function transferFrom(
        address,
        address,
        uint256
    ) external pure returns (bool) {
        revert TransferNotAllowed();
    }

    /**
     * Dynamic price router updates
     */

    /**
     * @notice Emitted when the dynamic price router is set
     * @param dynamicERC20 The address of the current dynamic ERC20 contract
     * @param dynamicPriceRouter The address of the dynamic price router
     */
    event DynamicPriceRouterSet(
        address indexed dynamicERC20,
        address indexed dynamicPriceRouter
    );

    function _setDynamicPriceRouter(
        address _dynamicPriceRouter
    ) internal virtual {
        dynamicPriceRouter = _dynamicPriceRouter;

        emit DynamicPriceRouterSet(address(this), _dynamicPriceRouter);
    }

    /**
     * Checks
     */

    function _checkBaseToQuotePath(address[] memory _path) internal virtual {
        require(_path.length > 1, "Path must have at least 2 tokens");
        require(_path[0] == baseToken, "Base token must be first in path");
        require(
            _path[_path.length - 1] == quoteToken,
            "Quote token must be last in path"
        );
    }

    function _checkQuoteToBasePath(address[] memory _path) internal virtual {
        require(_path.length > 1, "Path must have at least 2 tokens");
        require(_path[0] == quoteToken, "Quote token must be first in path");
        require(
            _path[_path.length - 1] == baseToken,
            "Base token must be last in path"
        );
    }

    /**
     * ERC165
     */

    function supportsInterface(
        bytes4 interfaceId
    ) public view virtual override returns (bool) {
        return
            interfaceId == type(IDynamicERC20).interfaceId ||
            interfaceId == type(IERC20).interfaceId ||
            interfaceId == type(IERC20Metadata).interfaceId ||
            super.supportsInterface(interfaceId);
    }
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

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

import {IDynamicERC20} from "../tokens/IDynamicERC20.sol";
import {IDynamicPriceRegistry} from "../registry/IDynamicPriceRegistry.sol";

/**
 * @title DynamicPriceEnabled
 * @author ProductMint
 * @notice Abstract contract that enables dynamic price functionality to translate purchase prices
 * for dynamic tokens to base tokens and quote tokens.
 */
abstract contract DynamicPriceEnabled {
    // The registry to check if a token is a DynamicERC20
    IDynamicPriceRegistry public dynamicPriceRegistry;

    /**
     * @notice Emitted when the dynamic price registry is updated
     * @param _dynamicPriceRegistry The address of the new dynamic price registry
     */
    event DynamicPriceRegistryUpdated(address indexed _dynamicPriceRegistry);

    constructor(address _dynamicPriceRegistry) {
        _setDynamicPriceRegistry(_dynamicPriceRegistry);
    }

    /**
     * @dev Translate the purchase price of a dynamic token to the base token
     */
    function _translateBaseTokenPurchasePrice(
        address token,
        uint256 amount
    ) internal virtual returns (address, uint256) {
        if (dynamicPriceRegistry.isTokenRegistered(token)) {
            return IDynamicERC20(token).getBaseTokenAmount(amount);
        }
        return (token, amount);
    }

    /**
     * @dev Set the dynamic price registry
     */
    function _setDynamicPriceRegistry(
        address _dynamicPriceRegistry
    ) internal virtual {
        require(
            IERC165(_dynamicPriceRegistry).supportsInterface(
                type(IDynamicPriceRegistry).interfaceId
            ),
            "IDynamicPriceRegistry not supported"
        );

        dynamicPriceRegistry = IDynamicPriceRegistry(_dynamicPriceRegistry);

        emit DynamicPriceRegistryUpdated(_dynamicPriceRegistry);
    }
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

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

import {IPermissionRegistry} from "../registry/IPermissionRegistry.sol";
import {PermissionUtils} from "../libs/PermissionUtils.sol";

/**
 * @title PermissionChecker
 * @notice Assert that a permission exists for an organization and owner using the permission registry.
 */
abstract contract PermissionChecker {
    using PermissionUtils for string;

    // @notice Revert if the owner does not have the permission
    error PermissionNotFound(address _owner, bytes32 _id);

    IPermissionRegistry public permissionRegistry;

    constructor(address _registry) {
        _setPermissionRegistry(_registry);
    }

    /**
     * @dev Assert a permission
     */

    /**
     * @notice Assert a permission
     * @dev Reverts if the owner does not have the permission or the permission is inactive
     * @param _permission The permission to check
     * @param _orgId The organization ID
     * @param _owner The owner to check
     */
    function _checkPermission(
        bytes32 _permission,
        uint256 _orgId,
        address _owner
    ) internal view virtual {
        if (
            !permissionRegistry.hasOwnerPermission(_orgId, _owner, _permission)
        ) {
            revert PermissionNotFound(_owner, _permission);
        }
    }

    /**
     * @notice Assert a permission by name
     * @dev Reverts if the owner does not have the permission or the permission is inactive
     * @param _permissionName The permission name to check
     * @param _orgId The organization ID
     * @param _owner The owner to check
     */
    function _checkPermissionName(
        string memory _permissionName,
        uint256 _orgId,
        address _owner
    ) internal view virtual {
        _checkPermission(_permissionName.id(), _orgId, _owner);
    }

    /**
     * @dev Set the permission registry
     */

    /**
     * @notice Set the permission registry
     * @dev Reverts if the registry does not support the IPermissionRegistry interface
     * @param _registry The address of the new permission registry
     */
    function _setPermissionRegistry(address _registry) internal virtual {
        require(
            IERC165(_registry).supportsInterface(
                type(IPermissionRegistry).interfaceId
            ),
            "Invalid permission registry"
        );
        permissionRegistry = IPermissionRegistry(_registry);
    }
}

// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.24;

import {Context} from "@openzeppelin/contracts/utils/Context.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";

import {IContractRegistry} from "../registry/IContractRegistry.sol";
import {IOrganizationAdmin} from "../admin/IOrganizationAdmin.sol";

/**
 * @title RegistryEnabled
 * @notice A base contract for contracts that use the central registry to interact with other contracts within the ProductMint system.
 */
abstract contract RegistryEnabled is Context {
    /**
     * @notice The registry contract
     */
    IContractRegistry public registry;

    uint256[50] private __gap;

    constructor(address _registry) {
        registry = IContractRegistry(_registry);
    }

    // Registry

    modifier onlyRegistry(address expectedContract) {
        _checkRegistry(expectedContract);
        _;
    }

    function _checkRegistry(address expectedContract) internal view {
        require(_msgSender() == expectedContract, "Caller not authorized");
    }

    // Org Admin

    modifier onlyOrgAdmin(uint256 organizationId) {
        _checkOrgAdmin(organizationId);
        _;
    }

    function _isOrgAdmin(uint256 organizationId) internal view returns (bool) {
        return _isOrgAdminAddress(organizationId, _msgSender());
    }

    function _isOrgAdminAddress(
        uint256 organizationId,
        address orgAdmin
    ) internal view returns (bool) {
        return
            _isOrgOwnerAddress(organizationId, orgAdmin) ||
            IOrganizationAdmin(registry.orgAdmin()).isAdmin(
                organizationId,
                orgAdmin
            );
    }

    function _checkOrgAdmin(uint256 organizationId) internal view {
        require(
            _isOrgAdmin(organizationId),
            "Not an admin of the organization"
        );
    }

    // Product Pass NFT

    modifier onlyPassOwner(uint256 _productPassId) {
        _checkPassOwner(_productPassId);
        _;
    }

    function _checkPassOwner(uint256 _productPassId) internal view {
        require(
            _isPassOwner(_productPassId),
            "Not the owner of the ProductPassNFT"
        );
    }

    function _isPassOwner(uint256 _productPassId) internal view returns (bool) {
        return _passOwner(_productPassId) == _msgSender();
    }

    function _passOwner(
        uint256 _productPassId
    ) internal view returns (address) {
        return IERC721(registry.productPassNFT()).ownerOf(_productPassId);
    }

    // Organization NFT

    modifier onlyOrgOwner(uint256 organizationId) {
        _checkOrgOwner(organizationId);
        _;
    }

    function _checkOrgOwner(uint256 organizationId) internal view {
        require(
            _isOrgOwner(organizationId),
            "Not the owner of the OrganizationNFT"
        );
    }

    function _isOrgOwner(uint256 organizationId) internal view returns (bool) {
        return _isOrgOwnerAddress(organizationId, _msgSender());
    }

    function _isOrgOwnerAddress(
        uint256 organizationId,
        address orgOwner
    ) internal view returns (bool) {
        return _orgOwner(organizationId) == orgOwner;
    }

    function _orgOwner(uint256 organizationId) internal view returns (address) {
        return IERC721(registry.organizationNFT()).ownerOf(organizationId);
    }
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

interface IOrganizationAdmin {
    /**
     * Organization Admins
     */

    /**
     * @notice Emitted when an admin is added or removed from an organization.
     * @param orgId The ID of the organization.
     * @param admin The address of the admin.
     * @param status The status of the admin. True if added, false if removed.
     */
    event OrgAdminUpdate(
        uint256 indexed orgId,
        address indexed admin,
        bool status
    );

    /**
     * @notice Adds an admin to the organization.
     * Admins are great for delegating the responsibilities of the organization.
     * @dev Only the owner of the organization can add admins.
     * @param organizationId The ID of the organization.
     * @param admin The address of the admin to add.
     */
    function addAdmin(uint256 organizationId, address admin) external;

    /**
     * @notice Removes an admin from the organization.
     * @param organizationId The ID of the organization.
     * @param admin The address of the admin to remove.
     */
    function removeAdmin(uint256 organizationId, address admin) external;

    /**
     * @notice Removes all admins from the organization.
     * @param organizationId The ID of the organization.
     */
    function removeAllAdmins(uint256 organizationId) external;

    /**
     * @notice Returns all admins for an organization.
     * @param organizationId The ID of the organization.
     * @return admins The addresses of the admins for the organization.
     */
    function getAdmins(
        uint256 organizationId
    ) external view returns (address[] memory);

    /**
     * @notice Checks if an address is an admin for an organization.
     * @param organizationId The ID of the organization.
     * @param admin The address of the admin to check.
     * @return status True if the address is an admin, false otherwise.
     */
    function isAdmin(
        uint256 organizationId,
        address admin
    ) external view returns (bool);
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

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

interface IPricingCalculator {
    /**
     * Checkout Cost
     */

    /**
     * @notice The parameters to calculate the checkout total cost
     * @dev Uses the same validation as the purchase manager so it will revert if the parameters are not valid
     * @param organizationId The id of the organization
     * @param productPassOwner The address of the product pass owner
     * @param productIds The ids of the products
     * @param pricingIds The ids of the pricing
     * @param quantities The quantities of the pricing
     * @param discountIds The ids of the discounts
     * @param couponId The id of the coupon
     */
    struct CheckoutTotalCostParams {
        uint256 organizationId;
        address productPassOwner;
        uint256[] productIds;
        uint256[] pricingIds;
        uint256[] quantities;
        uint256[] discountIds;
        uint256 couponId;
    }

    /**
     * @notice The result of the checkout total cost calculation
     * @param pricingIds The ids of the pricing
     * @param token The token of the pricing
     * @param costs The costs for each pricing id
     * @param couponCost The cost after applying the coupon
     * @param couponDiscount The discount percentage applied by the coupon in basis points
     * @param couponSavings The savings from the coupon
     * @param permanentCost The cost after applying the permanent discounts
     * @param permanentDiscount The discount percentage applied by the permanent discounts in basis points
     * @param permanentSavings The savings from the permanent discounts
     * @param subTotalCost The sub total cost of the checkout before applying the coupon and permanent discounts
     * @param checkoutTotalCost The total cost of the checkout after applying the coupon and permanent discounts
     */
    struct CheckoutTotalCost {
        uint256[] pricingIds;
        address token;
        uint256[] costs;
        uint256 couponCost;
        uint256 couponDiscount;
        uint256 couponSavings;
        uint256 permanentCost;
        uint256 permanentDiscount;
        uint256 permanentSavings;
        uint256 subTotalCost;
        uint256 checkoutTotalCost;
    }

    /**
     * @notice Get the checkout total cost
     * @param params The parameters for the checkout total cost calculation
     * @return checkout The result of the checkout total cost calculation
     */
    function getCheckoutTotalCost(
        CheckoutTotalCostParams memory params
    ) external view returns (CheckoutTotalCost memory checkout);

    /**
     * Pricing Total Cost
     */

    /**
     * @notice Get the total cost for a pricing id
     * @param pricingId The id of the pricing
     * @param quantity The quantity of the pricing. Ignored if not tiered or usage based pricing.
     * @return The total cost of the pricing
     */
    function getPricingTotalCost(
        uint256 pricingId,
        uint256 quantity
    ) external view returns (uint256);

    /**
     * @notice Get the initial purchase cost for a list of pricing ids
     * @param pricingIds The ids of the pricing
     * @param quantities The quantities of the pricing. Ignored if not tiered or usage based pricing.
     * @return cost The initial purchase cost of the pricing
     */
    function getInitialPurchaseCost(
        uint256[] memory pricingIds,
        uint256[] memory quantities
    ) external view returns (uint256 cost);

    /**
     * @notice Get the total cost for a pricing model
     * @param chargeStyle The charge style of the pricing
     * @param tiers The tiers of the pricing
     * @param flatPrice The flat price of the pricing
     * @param quantity The quantity of the pricing. Ignored if not tiered or usage based pricing.
     * @return The total cost of the pricing
     */
    function getTotalCost(
        PricingUtils.ChargeStyle chargeStyle,
        PricingUtils.PricingTier[] memory tiers,
        uint256 flatPrice,
        uint256 quantity
    ) external pure returns (uint256);

    /**
     * @notice Get the total cost for a pricing model
     * @param tiers The tiers of the pricing
     * @param quantity The quantity of the pricing.
     * @return The total cost of the pricing
     */
    function totalVolumeCost(
        PricingUtils.PricingTier[] memory tiers,
        uint256 quantity
    ) external pure returns (uint256);

    /**
     * @notice Get the total cost for a pricing model
     * @param tiers The tiers of the pricing
     * @param quantity The quantity of the pricing.
     * @return The total cost of the pricing
     */
    function totalGraduatedCost(
        PricingUtils.PricingTier[] memory tiers,
        uint256 quantity
    ) external pure returns (uint256);

    /**
     * Change Subscription Pricing
     */

    /**
     * @notice Reverts if the charge style is not valid when changing subscription pricing
     */
    error InvalidChargeStyle();

    /**
     * @notice Reverts if the charge style is not usage based when changing subscription pricing for another usage based pricing
     * @dev You cannot change to a non-usage based pricing model from a usage based pricing model and vice versa
     */
    error UsageBasedChargeStyleInconsistency();

    /**
     * @notice Reverts if the charge style is not tiered when changing subscription pricing for another tiered pricing
     * @dev You cannot change to a non-tiered pricing model from a tiered pricing model and vice versa
     */
    error TieredChargeStyleInconsistency();

    /**
     * @notice Get the new end date, token, and amount to change a subscription pricing
     * @dev Reverts if the charge style or date range is not valid
     * @param oldPricingId The id of the old pricing
     * @param newPricingId The id of the new pricing
     * @param currentStartDate The start date of the current subscription
     * @param currentEndDate The end date of the current subscription
     * @param quantity The quantity of the pricing
     * @return newEndDate The new end date of the subscription
     * @return token The token of the pricing
     * @return amount The amount of the pricing
     */
    function getChangeSubscriptionCost(
        uint256 oldPricingId,
        uint256 newPricingId,
        uint256 currentStartDate,
        uint256 currentEndDate,
        uint256 quantity
    ) external view returns (uint256 newEndDate, address token, uint256 amount);

    /**
     * Change Tiered Subscription Unit Quantity
     */

    /**
     * @notice Reverts if the old quantity is the same as the new quantity
     */
    error UnitQuantityIsTheSame();

    /**
     * @notice Get the new end date, token, and amount to change a tiered subscription unit quantity
     * @dev Reverts if the charge style or date range is not valid
     * @param pricingId The id of the pricing
     * @param currentStartDate The start date of the current subscription
     * @param currentEndDate The end date of the current subscription
     * @param oldQuantity The old quantity of the pricing
     * @param newQuantity The new quantity of the pricing
     * @return The pricing token and amount to charge for the change in quantity
     */
    function getChangeUnitQuantityCost(
        uint256 pricingId,
        uint256 currentStartDate,
        uint256 currentEndDate,
        uint256 oldQuantity,
        uint256 newQuantity
    ) external view returns (address, uint256);
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

interface IPaymentEscrow {
    /**
     * Roles
     */

    /**
     * @notice Get the fee setter role for setting fees
     * @return Fee setter role
     */
    function FEE_SETTER_ROLE() external view returns (bytes32);

    /**
     * @notice Get the fee exempt role for setting fee exemptions for organizations
     * @return Fee exempt role
     */
    function FEE_EXEMPT_ROLE() external view returns (bytes32);

    /**
     * @notice Get the fee enabled role for setting if fees are enabled
     * @return Fee enabled role
     */
    function FEE_ENABLED_ROLE() external view returns (bytes32);

    /**
     * @notice Get the fee withdraw role for withdrawing fees required to withdraw fees
     * @return Fee withdraw role
     */
    function FEE_WITHDRAW_ROLE() external view returns (bytes32);

    /**
     * @notice Get the whitelist role for whitelisting ERC20 tokens
     * @return Whitelist role
     */
    function WHITELIST_ROLE() external view returns (bytes32);

    /**
     * @notice Get the revoke charge role for revoking organization charge ability
     * @dev It is important to allow the org charging to be revoked in case control over the org is lost
     * @return Revoke charge role
     */
    function REVOKE_CHARGE_ROLE() external view returns (bytes32);

    /**
     * Payment Processing
     */

    /**
     * @notice Emitted when a transfer is recorded that transfers funds to an org.
     * @param orgId Organization ID
     * @param from Payer (User wallet)
     * @param token Token address. Address(0) for native tokens.
     * @param totalAmount Total amount transferred to the escrow contract
     * @param orgAmount Amount transferred to the org minus fees
     */
    event TransferAmount(
        uint256 indexed orgId,
        address indexed from,
        address indexed token,
        uint256 totalAmount,
        uint256 orgAmount
    );

    /**
     * @notice Transfer directly from the user wallet to the contract and set the balance for the organization
     * @dev Only callable by the purchase manager
     * @param orgId Organization ID
     * @param from Payer (User wallet)
     * @param token Token address. Address(0) for native tokens.
     * @param amount Amount to transfer
     */
    function transferDirect(
        uint256 orgId,
        address payable from,
        address token,
        uint256 amount
    ) external payable;

    /**
     * Token Whitelisting
     */

    /**
     * @notice Emitted when a token is whitelisted.
     * @param token Token address
     * @param isWhitelisted True if the token is whitelisted, false otherwise
     */
    event WhitelistedTokenSet(address indexed token, bool isWhitelisted);

    /**
     * @notice Get if a token is whitelisted.
     * @param token Token address
     * @return True if the token is whitelisted, false otherwise
     */
    function whitelistedTokens(address token) external view returns (bool);

    /**
     * @notice Set if a token is whitelisted.
     * @dev Only whitelisted tokens can be used to purchase products
     * @param token Token address
     * @param isWhitelisted True if the token is whitelisted, false otherwise
     */
    function setWhitelistedToken(address token, bool isWhitelisted) external;

    /**
     * Balance Management
     */

    /**
     * @notice Emitted when an organization balance is withdrawn.
     * @param orgId Organization ID
     * @param token Token address. Address(0) for native tokens.
     * @param amount Amount withdrawn
     */
    event OrgBalanceWithdrawn(
        uint256 indexed orgId,
        address indexed token,
        uint256 amount
    );

    /**
     * @notice Get the balance for a specific token for an organization
     * @param orgId Organization ID
     * @param token Token address. Address(0) for native tokens.
     * @return Balance
     */
    function orgBalances(
        uint256 orgId,
        address token
    ) external view returns (uint256);

    /**
     * @notice Get the total balance for a specific token for all organizations combined
     * @param token Token address. Address(0) for native tokens.
     * @return Balance
     */
    function totalBalances(address token) external view returns (uint256);

    /**
     * @notice Withdraw an organization balance. Org balances are only available to the organization token owner.
     * Organization balances can be withdrawn by the owner at any time. They can never be restricted. Your revenue is yours to keep.
     * @param orgId Organization ID
     * @param token Token address. Address(0) for native tokens.
     * @param amount Amount to withdraw
     */
    function withdrawOrgBalance(
        uint256 orgId,
        address token,
        uint256 amount
    ) external;

    /**
     * Fee Management
     */

    /**
     * @notice Emitted when fees are toggled on or off.
     * @param isEnabled True if fees are enabled, false otherwise
     */
    event FeeEnabled(bool isEnabled);

    /**
     * @notice Emitted when a fee is set for a specific token.
     * @param token Token address. Address(0) for native tokens.
     * @param newFee Fee amount in basis points (100 = 1%, 1000 = 10%, etc.)
     */
    event FeeSet(address indexed token, uint256 newFee);

    /**
     * @notice Emitted when the exotic fee is set.
     * @param fee Fee amount in basis points (100 = 1%, 1000 = 10%, etc.)
     */
    event ExoticFeeSet(uint256 fee);

    /**
     * @notice Emitted when a fee exemption is set for an organization.
     * @param orgId Organization ID
     * @param isExempt True if the organization is fee exempt, false otherwise
     */
    event FeeExemptSet(uint256 indexed orgId, bool isExempt);

    /**
     * @notice Emitted when a fee is withdrawn.
     * @param token Token address. Address(0) for native tokens.
     * @param amount Amount withdrawn
     */
    event FeeWithdraw(address indexed token, uint256 amount);

    /**
     * @notice Get the fee denominator
     * @return Fee denominator
     */
    function FEE_DENOMINATOR() external view returns (uint256);

    /**
     * @notice Check if fees are enabled
     * @return True if fees are enabled, false otherwise
     */
    function isFeeEnabled() external view returns (bool);

    /**
     * @notice Get the fee for a specific token
     * @param token Token address. Address(0) for native tokens.
     * @return Fee amount in basis points (100 = 1%, 1000 = 10%, etc.)
     */
    function fees(address token) external view returns (uint256);

    /**
     * @notice Get the exotic fee. This fee is applied to any ERC20 token that does not have a specific fee set.
     * @return Fee amount in basis points (100 = 1%, 1000 = 10%, etc.)
     */
    function exoticFee() external view returns (uint256);

    /**
     * @notice Set if fees are enabled
     * @param _isFeeEnabled True if fees are enabled, false otherwise
     */
    function setFeeEnabled(bool _isFeeEnabled) external;

    /**
     * @notice Set the exotic fee.
     * @param newFee Fee amount in basis points (100 = 1%, 1000 = 10%, etc.)
     */
    function setExoticFee(uint256 newFee) external;

    /**
     * @notice Check if an organization is fee exempt
     * @param orgId Organization token ID
     * @return True if the organization token ID is fee exempt, false otherwise
     */
    function feeExempt(uint256 orgId) external view returns (bool);

    /**
     * @notice Set if an organization is fee exempt
     * @param orgId Organization token ID
     * @param isExempt True if the organization token ID is fee exempt, false otherwise
     */
    function setFeeExempt(uint256 orgId, bool isExempt) external;

    /**
     * @notice Calculate the fee for a specific token
     * @param orgId The organization ID to calculate the fee for
     * @param token Token address. Address(0) for native tokens.
     * @param amount Amount to calculate the fee for
     * @return Fee amount in basis points (100 = 1%, 1000 = 10%, etc.)
     */
    function calculateFee(
        uint256 orgId,
        address token,
        uint256 amount
    ) external view returns (uint256);

    /**
     * @notice Withdraw the fee for a specific token
     * @param token Token address. Address(0) for native tokens.
     * @return balance Total amount of fees withdrawn
     */
    function withdrawFee(address token) external returns (uint256 balance);

    /**
     * @notice Get the fee balance for a specific token
     * @param token Token address. Address(0) for native tokens.
     * @return Fee balance
     */
    function getFeeBalance(address token) external view returns (uint256);

    /**
     * Fee Reducer
     */

    /**
     * @notice Get the fee reducer
     * @return Fee reducer contract address
     */
    function feeReducer() external view returns (address);

    /**
     * @notice Emitted when the fee reducer contract is set
     * @param feeReducer Fee reducer address
     */
    event FeeReducerSet(address feeReducer);

    /**
     * @notice Set the fee reducer
     * @dev Must implement the IFeeReducer interface
     * @param _feeReducer Fee reducer address
     */
    function setFeeReducer(address _feeReducer) external;

    /**
     * Revoking Organization Charge Ability
     */

    /**
     * @notice Emitted when an organization's payment charge ability is updated.
     * @param orgId Organization ID
     * @param isRevoked True if the organization's payment charge ability is revoked, false otherwise
     */
    event OrgChargeAbilityUpdate(uint256 indexed orgId, bool isRevoked);

    /**
     * @notice Get if an organization's payment charge ability is revoked
     * @param orgId Organization ID
     * @return True if the organization's payment charge ability is revoked, false otherwise
     */
    function revokedOrgs(uint256 orgId) external view returns (bool);

    /**
     * @notice Revoke an organization's payment charge ability
     * @dev WARNING: Once an organization's payment charge ability is revoked, it cannot be restored without reaching out to the team.
     * If you accidentally revoke an organization's payment charge ability, please reach out to the team to restore it.
     * This will not prevent funds from being withdrawn but it will prevent your customers from being able to purchase products
     * preventing them from having their wallet funds used.
     * @param orgId Organization ID
     */
    function revokeOrgChargeAbility(uint256 orgId) external;

    /**
     * @notice Restore an organization's payment charge ability
     * @param orgId Organization ID
     */
    function restoreOrgChargeAbility(uint256 orgId) external;
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

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

interface ISubscriptionEscrow {
    /**
     * @notice Subscription struct relating to a product on a product pass.
     * @param orgId Organization token ID
     * @param pricingId Pricing model ID
     * @param startDate Start date of the subscription
     * @param endDate End date of the subscription
     * @param timeRemaining Time remaining in the subscription
     * @param isCancelled Whether the subscription has been cancelled
     * @param isPaused Whether the subscription has been paused
     */
    struct Subscription {
        uint256 orgId;
        uint256 pricingId;
        uint256 startDate;
        uint256 endDate;
        uint256 timeRemaining;
        bool isCancelled;
        bool isPaused;
    }

    /**
     * @notice Statuses derived from subscription state.
     * @param ACTIVE Activated and product access should be granted.
     * @param CANCELLED Will not renew at the end of the current period but product access should remain granted.
     * @param PAST_DUE Attempted to renew at the end of the current period but failed. Product access should be revoked.
     * @param PAUSED Deactivated and product access should be revoked.
     */
    enum SubscriptionStatus {
        ACTIVE,
        CANCELLED,
        PAST_DUE,
        PAUSED
    }

    /**
     * @notice Get a subscription with its status.
     * @param productPassId The ID of the product pass.
     * @param productId The ID of the product.
     * @return subscription The subscription details.
     * @return status The status of the subscription.
     */
    function getSubscription(
        uint256 productPassId,
        uint256 productId
    ) external view returns (Subscription memory, SubscriptionStatus);

    /**
     * @notice Get multiple subscriptions with their statuses.
     * @param productPassId The ID of the product pass.
     * @param productIds The IDs of the products.
     * @return _subs The subscription details.
     * @return _statuses The statuses of the subscriptions.
     */
    function getSubscriptionBatch(
        uint256 productPassId,
        uint256[] calldata productIds
    )
        external
        view
        returns (
            Subscription[] memory _subs,
            SubscriptionStatus[] memory _statuses
        );

    /**
     * @notice Get the product IDs for a product pass that have subscriptions.
     * @param productPassId The ID of the product pass.
     * @return productIds The product IDs.
     */
    function getPassSubs(
        uint256 productPassId
    ) external view returns (uint256[] memory);

    /**
     * @notice Emitted when a subscription cycle is updated.
     * @param organizationId The ID of the organization.
     * @param productPassId The ID of the product pass.
     * @param productId The ID of the product.
     * @param status The status of the subscription.
     * @param startDate The start date of the subscription.
     * @param endDate The end date of the subscription.
     */
    event SubscriptionCycleUpdated(
        uint256 indexed organizationId,
        uint256 indexed productPassId,
        uint256 indexed productId,
        SubscriptionStatus status,
        uint256 startDate,
        uint256 endDate
    );

    /**
     * Creation
     */

    /**
     * @notice Reverts when a subscription already exists for the product pass and product.
     * @param orgId The ID of the organization.
     * @param productPassId The ID of the product pass.
     * @param productId The ID of the product.
     */
    error SubscriptionAlreadyExists(
        uint256 orgId,
        uint256 productPassId,
        uint256 productId
    );

    /**
     * @notice Reverts when the organization is not pausable when trying to create a subscription in a paused state.
     */
    error OrganizationIsNotPausable();

    /**
     * @notice Creates a new subscription for a product pass and link it to the products.
     * @param _organizationId The ID of the organization.
     * @param _productPassId The ID of the product pass.
     * @param _productIds The IDs of the products.
     * @param _pricingIds The IDs of the pricing models.
     * @param _cycleDurations The durations of the cycles.
     * @param _unitQuantities The unit quantities of the products.
     * @param _pause Whether the subscription is paused.
     */
    function createSubscriptions(
        uint256 _organizationId,
        uint256 _productPassId,
        uint256[] calldata _productIds,
        uint256[] calldata _pricingIds,
        uint256[] calldata _cycleDurations,
        uint256[] calldata _unitQuantities,
        bool _pause
    ) external;

    /**
     * Renewal
     */

    /**
     * @notice Reverts when the charge style is invalid during a renewal.
     * @param chargeStyle The charge style of the pricing model.
     */
    error InvalidChargeStyle(PricingUtils.ChargeStyle chargeStyle);

    /**
     * @notice Reverts when the unit quantity is invalid.
     * @param orgId The ID of the organization.
     * @param productPassId The ID of the product pass.
     * @param productId The ID of the product.
     */
    error InvalidUnitQuantity(
        uint256 orgId,
        uint256 productPassId,
        uint256 productId
    );

    /**
     * @notice Renews an existing subscription.
     * @param productPassId The ID of the product pass.
     * @param productId The ID of the product.
     * @return orgId The ID of the organization.
     * @return token The token used to pay for the renewal.
     * @return price The price of the renewal.
     */
    function renewSubscription(
        uint256 productPassId,
        uint256 productId
    ) external returns (uint256 orgId, address token, uint256 price);

    /**
     * @notice Returns the cost of renewing a specific subscription.
     * @param productPassId The ID of the product pass.
     * @param productId The ID of the product.
     * @return orgId The ID of the organization.
     * @return token The token used to pay for the renewal.
     * @return price The price of the renewal.
     */
    function getRenewalCost(
        uint256 productPassId,
        uint256 productId
    ) external view returns (uint256 orgId, address token, uint256 price);

    /**
     * @notice Returns the cost of renewing multiple subscriptions on a product pass.
     * @param productPassId The ID of the product pass.
     * @param productIds The IDs of the products.
     * @return orgId The ID of the organization that the subscriptions belong to.
     * @return tokens The tokens used to pay for the renewals.
     * @return prices The prices of the renewals.
     */
    function getRenewalCostBatch(
        uint256 productPassId,
        uint256[] calldata productIds
    )
        external
        view
        returns (
            uint256 orgId,
            address[] memory tokens,
            uint256[] memory prices
        );

    /**
     * Change Pricing
     */

    /**
     * @notice Emitted when a subscription pricing is changed.
     * @param organizationId The ID of the organization.
     * @param productPassId The ID of the product pass.
     * @param productId The ID of the product.
     * @param newPricingId The ID of the new pricing model.
     */
    event SubscriptionPricingChanged(
        uint256 indexed organizationId,
        uint256 indexed productPassId,
        uint256 indexed productId,
        uint256 newPricingId
    );

    /**
     * @notice Emitted when the owner change pricing is set.
     * @dev Only the org admin can set this.
     * @param organizationId The ID of the organization.
     * @param canChange Whether the owner can change the pricing.
     */
    event OwnerChangePricingSet(uint256 indexed organizationId, bool canChange);

    /**
     * @notice Changes the pricing of a subscription.
     * @param productPassId The ID of the product pass.
     * @param productId The ID of the product.
     * @param newPricingId The ID of the new pricing model.
     * @param isPassOwner Whether the pass owner is changing the pricing.
     * @return token The token of the pricing model.
     * @return amount The amount to charge for the pricing model change.
     */
    function changeSubscriptionPricing(
        uint256 productPassId,
        uint256 productId,
        uint256 newPricingId,
        bool isPassOwner
    ) external returns (address token, uint256 amount);

    /**
     * @notice Returns whether the product pass owner can change the pricing.
     * If not, then only the org admin can change the pricing.
     * @param orgId The ID of the organization.
     * @return canChange Whether the owner can change the pricing.
     */
    function ownerChangePricing(uint256 orgId) external view returns (bool);

    /**
     * @notice Sets whether the product pass owner can change the pricing.
     * @param orgId The ID of the organization.
     * @param canChange Whether the owner can change the pricing.
     */
    function setOwnerChangePricing(uint256 orgId, bool canChange) external;

    /**
     * Activation
     */

    /**
     * @notice Emitted when the subscription pausable is set.
     * @param organizationId The ID of the organization.
     * @param pausable Whether subscriptions can be paused.
     */
    event SubscriptionPausableSet(
        uint256 indexed organizationId,
        bool pausable
    );

    /**
     * @notice Reverts when the pause state is invalid. It must be updated.
     */
    error InvalidPauseState();

    /**
     * @notice Pauses a subscription.
     * @param productPassId The ID of the product pass.
     * @param productId The ID of the product.
     * @param pause Whether to pause the subscription.
     * @return isPastDue Whether the subscription is past due.
     */
    function pauseSubscription(
        uint256 productPassId,
        uint256 productId,
        bool pause
    ) external returns (bool);

    /**
     * @notice Returns whether subscriptions can be paused.
     * @param orgId The ID of the organization.
     * @return pausable Whether subscriptions can be paused.
     */
    function subscriptionsPauseable(uint256 orgId) external view returns (bool);

    /**
     * @notice Sets whether subscriptions can be paused.
     * @dev Only the org admin can set this.
     * @param orgId The ID of the organization.
     * @param _pausable Whether subscriptions can be paused.
     */
    function setSubscriptionsPausable(uint256 orgId, bool _pausable) external;

    /**
     * Unit Quantities
     */

    /**
     * @notice The unit quantity for a product pass used for tiered charge styles.
     * @param quantity The quantity of units for tiered charge styles.
     * @param maxQuantity The maximum quantity of units that have been purchased for the current cycle. Resets at the end of each cycle.
     */
    struct UnitQuantity {
        uint256 orgId;
        uint256 quantity;
        uint256 maxQuantity;
    }

    /**
     * Emitted when a unit quantity is set for a product pass.
     * @param productPassId The ID of the product pass.
     * @param productId The ID of the product.
     * @param quantity The quantity of the unit.
     * @param maxQuantity The maximum quantity of units that have been purchased for the current cycle. Resets at the end of each cycle.
     */
    event UnitQuantitySet(
        uint256 indexed productPassId,
        uint256 indexed productId,
        uint256 quantity,
        uint256 maxQuantity
    );

    /**
     * @notice Returns the current quantity for a product pass.
     * @param productPassId The ID of the product pass.
     * @param productId The ID of the product.
     * @return quantity The quantity of the unit.
     */
    function getUnitQuantity(
        uint256 productPassId,
        uint256 productId
    ) external view returns (uint256);

    /**
     * @notice Returns the full unit quantity for a product pass.
     * @param productPassId The ID of the product pass.
     * @param productId The ID of the product.
     * @return unitQuantity The full unit quantity struct.
     */
    function getUnitQuantityFull(
        uint256 productPassId,
        uint256 productId
    ) external view returns (UnitQuantity memory);

    /**
     * @notice Changes the unit quantity for a product pass.
     * @dev Only the purchase manager can call this.
     * @param productPassId The ID of the product pass.
     * @param productId The ID of the product.
     * @param quantity The quantity of the unit.
     * @return orgId The ID of the organization which the subscription belongs to.
     * @return token The token of the pricing model.
     * @return amount The amount to charge for the unit quantity change.
     */
    function changeSubscriptionUnitQuantity(
        uint256 productPassId,
        uint256 productId,
        uint256 quantity
    ) external returns (uint256 orgId, address token, uint256 amount);

    /**
     * Cancellation
     */

    /**
     * @notice Cancels a subscription.
     * @param productPassId The ID of the product pass.
     * @param productId The ID of the product.
     * @param cancel Whether to cancel the subscription.
     * @return isPastDue True if the subscription is past due, else false.
     */
    function cancelSubscription(
        uint256 productPassId,
        uint256 productId,
        bool cancel
    ) external returns (bool);
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

/**
 * @title PermissionUtils
 * @notice Utility library for working with permissions
 */
library PermissionUtils {
    /**
     * @notice Generate a permission ID from a name
     * @param _name The name of the permission
     * @return The ID of the permission
     */
    function id(string memory _name) internal pure returns (bytes32) {
        return keccak256(abi.encode(_name));
    }

    /**
     * @dev Initial default pass permissions for each organization and pass owner
     * that were originally deployed with the permissions system.
     */

    /**
     * @notice Can the org spend tokens from the owner wallet
     */
    string constant PASS_WALLET_SPEND = "pass.wallet.spend";

    /**
     * @notice Purchase additional products for an existing product pass
     */
    string constant PASS_PURCHASE_ADDITIONAL = "pass.purchase.additional";

    /**
     * @notice Renew an existing expired subscription
     */
    string constant PASS_SUBSCRIPTION_RENEWAL = "pass.subscription.renewal";

    /**
     * @notice Change the pricing model for an existing subscription
     */
    string constant PASS_SUBSCRIPTION_PRICING = "pass.subscription.pricing";

    /**
     * @notice Change the unit quantity for an existing TIERED subscription
     */
    string constant PASS_SUBSCRIPTION_QUANTITY = "pass.subscription.quantity";
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

/**
 * @notice Utility library for managing pricing configurations.
 */
library PricingUtils {
    /**
     * @notice A pricing configuration option for a product.
     * @param orgId The organization ID that the pricing configuration belongs to.
     * @param chargeStyle How the product should be charged.
     * @param chargeFrequency How often the subscription is charged.
     * @param tiers The tiers of pricing used in TIER and USAGE_BASED models.
     * @param token The ERC20 token that is used for all tiers. address(0) means use the native token of the chain.
     * @param flatPrice The price of the product if the pricing model is FLAT_RATE or chargeStyle is ONE_TIME.
     * @param usageMeterId The usage meter ID that is used to record the usage of the product. 0 if not used.
     * @param isActive If true, then the pricing configuration can be used in new purchases, else it is not available for purchase.
     * @param isRestricted If true, then the pricing configuration is restricted to wallets that have been granted restricted access.
     */
    struct Pricing {
        uint256 orgId;
        ChargeStyle chargeStyle;
        ChargeFrequency chargeFrequency;
        PricingTier[] tiers;
        address token;
        uint256 flatPrice;
        uint256 usageMeterId;
        bool isActive;
        bool isRestricted;
    }

    /**
     * @notice Returns true if the pricing is usage based.
     * @param pricing The pricing configuration.
     * @return true if the pricing is usage based, false otherwise.
     */
    function isUsage(Pricing memory pricing) internal pure returns (bool) {
        return
            pricing.chargeStyle == ChargeStyle.USAGE_BASED_VOLUME ||
            pricing.chargeStyle == ChargeStyle.USAGE_BASED_GRADUATED;
    }

    /**
     * @notice Returns true if the pricing is tiered.
     * @param pricing The pricing configuration.
     * @return true if the pricing is tiered, false otherwise.
     */
    function isTiered(Pricing memory pricing) internal pure returns (bool) {
        return
            pricing.chargeStyle == ChargeStyle.TIERED_VOLUME ||
            pricing.chargeStyle == ChargeStyle.TIERED_GRADUATED;
    }

    /**
     * @notice ChargeStyle is how the product should be charged.
     * @param ONE_TIME The product is charged once for a one-time purchase.
     * @param FLAT_RATE The price for a recurring subscription.
     * @param TIERED_VOLUME The price is different for different quantity of units.
     *        Final tier reached is used for all units. Billed at the start of the period.
     * @param TIERED_GRADUATED The price is different for different quantity of units.
     *        Tiers apply progressively. Billed at the start of the period.
     * @param USAGE_BASED_VOLUME The price is based on the final usage recorded in the billing period.
     *        Final tier reached is used. Billed at the end of the period.
     * @param USAGE_BASED_GRADUATED The price is based on the usage recorded in the billing period.
     *        Tiers apply progressively. Billed at the end of the period.
     */
    enum ChargeStyle {
        ONE_TIME,
        FLAT_RATE,
        TIERED_VOLUME,
        TIERED_GRADUATED,
        USAGE_BASED_VOLUME,
        USAGE_BASED_GRADUATED
    }

    /**
     * Subscription based products
     */

    /**
     * @notice ChargeFrequency is how often the subscription is charged.
     * @param DAILY Every 1 day
     * @param WEEKLY Every 7 days
     * @param MONTHLY Every 30 days
     * @param QUARTERLY Every 90 days
     * @param YEARLY Every 365 days
     */
    enum ChargeFrequency {
        DAILY,
        WEEKLY,
        MONTHLY,
        QUARTERLY,
        YEARLY
    }

    /**
     * @notice PricingTier is a tier of pricing.
     * @param lowerBound The lower number of units for the tier.
     * @param upperBound The upper number of units for the tier.
     * @param pricePerUnit The price per unit.
     * @param priceFlatRate The flat rate that is added to the price.
     */
    struct PricingTier {
        uint256 lowerBound;
        uint256 upperBound;
        uint256 pricePerUnit;
        uint256 priceFlatRate;
    }
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

interface IPurchaseManager {
    /**
     * @notice Get the total number of product pass tokens minted.
     * @return The total number of product pass tokens minted.
     */
    function passSupply() external view returns (uint256);

    /**
     * @notice Emitted when the funds are transferred from the user to the payment escrow.
     *  During the initial purchase, additional purchases, changing/renewing subscription pricing,
     *  the amount paid is recorded.
     * @param orgId The ID of the organization that the products are purchased for.
     * @param passOwner The address of the owner of the product pass that paid for the products.
     * @param purchaser The address of the purchaser of the products.
     * @param token The token used for the purchase.
     * @param amountPaid The total amount paid for the products including any discounts from coupons.
     */
    event PerformPurchase(
        uint256 indexed orgId,
        address indexed passOwner,
        address indexed purchaser,
        address token,
        uint256 amountPaid
    );

    /**
     * @notice Revert when the provided coupon code is invalid.
     */
    error InvalidCouponCode();

    /**
     * @notice Revert when no products are provided for a purchase or renewal during batch operations.
     */
    error NoProductsProvided();

    /**
     * @notice Revert when the number of product IDs and statuses do not match during batch operations.
     */
    error ProductIdsAndStatusesLengthMismatch();

    /**
     * @notice Revert when the caller is not authorized to perform the action.
     */
    error NotAuthorized();

    /**
     * Purchase Products
     */

    /**
     * @notice Emitted when products are purchased for a product pass.
     * @param orgId The ID of the organization that the products are purchased for.
     * @param productPassId The ID of the product pass that the products are purchased for.
     * @param passOwner The address of the owner of the product pass.
     * @param productIds The IDs of the products that are purchased.
     * @param pricingIds The IDs of the pricing options for the products.
     * @param quantities The quantities of the products that are purchased.
     * @param token The token that is used to purchase the products.
     * @param amountPaid The total amount paid for the products before any discounts.
     */
    event ProductsPurchased(
        uint256 indexed orgId,
        uint256 indexed productPassId,
        address indexed passOwner,
        uint256[] productIds,
        uint256[] pricingIds,
        uint256[] quantities,
        address token,
        uint256 amountPaid
    );

    /**
     * @notice The parameters for an initial purchase.
     *
     * @param to The address of the user to purchase the products for and mint the product pass to.
     * @param organizationId The ID of the organization to purchase the products for.
     * @param productIds The IDs of the products to purchase.
     * @param pricingIds The IDs of the pricing options for the products.
     * @param quantities The quantities of the products to purchase.
     *  Only relevant for products for tiered pricing. 0 must be provided for all other pricing models.
     * @param discountIds The IDs of the discounts to be minted onto the product pass.
     * @param couponCode The coupon code to apply to the purchase.
     * @param airdrop Whether to airdrop the products to the user.
     *  Can only be called by the org admin.
     * @param pause Whether to pause any subscriptions that are purchased during the purchase.
     *  The org must have this feature enabled to pause subscriptions.
     */
    struct InitialPurchaseParams {
        address to;
        uint256 organizationId;
        uint256[] productIds;
        uint256[] pricingIds;
        uint256[] quantities;
        uint256[] discountIds;
        string couponCode;
        bool airdrop;
        bool pause;
    }

    /**
     * @notice Purchase products by minting a new product pass.
     * @param params The parameters for the purchase.
     */
    function purchaseProducts(
        InitialPurchaseParams calldata params
    ) external payable;

    /**
     * @notice The parameters needed for an additional purchase to add products to an existing product pass.
     *
     * @param productPassId The ID of the product pass to add the products to.
     * @param productIds The IDs of the products to purchase.
     * @param pricingIds The IDs of the pricing options for the products.
     * @param quantities The quantities of the products to purchase.
     *  Only relevant for products for tiered pricing. 0 must be provided for all other pricing models.
     * @param couponCode The coupon code to apply to the purchase.
     * @param airdrop Whether to airdrop the products to the user.
     *  Can only be called by the pass owner.
     * @param pause Whether to pause any subscriptions that are purchased during the purchase.
     *  The org must have this feature enabled to pause subscriptions.
     */
    struct AdditionalPurchaseParams {
        uint256 productPassId;
        uint256[] productIds;
        uint256[] pricingIds;
        uint256[] quantities;
        string couponCode;
        bool airdrop;
        bool pause;
    }

    /**
     * @notice Purchase additional products by adding them to an existing product pass.
     * @param params The parameters for the purchase.
     */
    function purchaseAdditionalProducts(
        AdditionalPurchaseParams calldata params
    ) external payable;

    /**
     * @dev Used internally by the PurchaseManager during the product purchase.
     */
    struct PurchaseProductsParams {
        address passOwner;
        address purchaser;
        uint256 orgId;
        uint256 productPassId;
        uint256[] productIds;
        uint256[] pricingIds;
        uint256[] quantities;
        string couponCode;
        bool airdrop;
        bool pause;
        bool isInitialPurchase;
    }

    /**
     * @dev Used internally by the PurchaseManager during the product purchase.
     */
    struct PerformPurchaseParams {
        uint256 orgId;
        uint256 productPassId;
        address passOwner;
        address purchaser;
        uint256 totalAmount;
        address token;
        bool airdrop;
        bool isInitialPurchase;
        bool forceCoupon;
    }

    /**
     * Change subscription pricing
     */

    /**
     * @notice The parameters for changing the pricing for a subscription.
     *
     * @custom:field orgId The ID of the organization that the subscription is purchased for.
     * @custom:field productPassId The ID of the product pass that the subscription is purchased for.
     * @custom:field productId The ID of the product to change the pricing for.
     * @custom:field newPricingId The ID of the new pricing option for the product.
     * @custom:field airdrop Whether to airdrop the change to the user.
     */
    struct ChangeSubscriptionPricingParams {
        uint256 orgId;
        uint256 productPassId;
        uint256 productId;
        uint256 newPricingId;
        bool airdrop;
    }

    /**
     * @notice Change the pricing for an existing subscription.
     *  Can only change between pricing models that are the same charge style i.e. FLAT_RATE, TIERED, or USAGE
     * @dev Only the pass owner or an org admin can change the pricing model for a subscription.
     * @param params The parameters for the change.
     */
    function changeSubscriptionPricing(
        ChangeSubscriptionPricingParams calldata params
    ) external;

    /**
     * Renew subscription
     */

    /**
     * @notice Renew a subscription for a product.
     * @param productPassId The ID of the product pass to renew.
     * @param productId The ID of the product to renew.
     * @param airdrop Whether to airdrop the renewed subscription to the user. Can only be used by an org admin.
     */
    function renewSubscription(
        uint256 productPassId,
        uint256 productId,
        bool airdrop
    ) external;

    /**
     * @notice Batch renew multiple subscriptions on a product pass in a single transaction.
     * @param productPassId The ID of the product pass to renew.
     * @param productIds The IDs of the products to renew.
     * @param airdrop Whether to airdrop the products to the user. Can only be used by an org admin.
     */
    function renewSubscriptionBatch(
        uint256 productPassId,
        uint256[] calldata productIds,
        bool airdrop
    ) external;

    /**
     * Tiered Subscription Unit Quantity
     */

    /**
     * @notice Change the unit quantity for a tiered subscription.
     * @dev Subscription must be active to change the unit quantity and a TIERED pricing model must be set.
     * @param productPassId The ID of the product pass that the subscription is purchased for.
     * @param productId The ID of the product to change the unit quantity for.
     * @param quantity The new unit quantity for the subscription.
     * @param airdrop Whether to airdrop the change to the user. Can only be used by an org admin.
     */
    function changeTieredSubscriptionUnitQuantity(
        uint256 productPassId,
        uint256 productId,
        uint256 quantity,
        bool airdrop
    ) external;

    /**
     * Pause subscription
     */

    /**
     * @notice Pause a subscription for a product.
     * @dev The org must have this feature enabled to pause subscriptions.
     * @param productPassId The ID of the product pass that the subscription is purchased for.
     * @param productId The ID of the product to pause the subscription for.
     * @param _pause Whether to pause the subscription.
     */
    function pauseSubscription(
        uint256 productPassId,
        uint256 productId,
        bool _pause
    ) external;

    /**
     * @notice Batch pause multiple subscriptions on a product pass in a single transaction.
     * @param productPassId The ID of the product pass that the subscriptions are purchased for.
     * @param productIds The IDs of the products to pause the subscriptions for.
     * @param pause Whether to pause the subscriptions.
     */
    function pauseSubscriptionBatch(
        uint256 productPassId,
        uint256[] calldata productIds,
        bool[] calldata pause
    ) external;

    /**
     * Cancel subscription
     */

    /**
     * @notice Cancel a subscription for a product.
     *  When a subscription is cancelled it cannot be renewed.
     *  The subscription will automatically be renewed when un-cancelled.
     * @param productPassId The ID of the product pass that the subscription is purchased for.
     * @param productId The ID of the product to cancel the subscription for.
     * @param cancel Whether to cancel the subscription.
     */
    function cancelSubscription(
        uint256 productPassId,
        uint256 productId,
        bool cancel
    ) external;

    /**
     * @notice Batch cancel multiple subscriptions on a product pass in a single transaction.
     * @param productPassId The ID of the product pass that the subscriptions are purchased for.
     * @param productIds The IDs of the products to cancel the subscriptions for.
     * @param cancel Whether to cancel the subscriptions.
     */
    function cancelSubscriptionBatch(
        uint256 productPassId,
        uint256[] calldata productIds,
        bool[] calldata cancel
    ) external;

    /**
     * Purchase Pause
     */

    /**
     * @notice Pause all activity within the purchase manager.
     * @dev This will prevent any purchases, renewals, cancellations, or pausing of subscriptions.
     * Can be used by the owner when doing upgrades or maintenance.
     */
    function pausePurchases() external;

    /**
     * @notice Unpause activity within the purchase manager.
     * @dev This will resume any paused activity within the purchase manager.
     */
    function unpausePurchases() external;
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

import {RegistryEnabled} from "../abstract/RegistryEnabled.sol";
import {
    ReentrancyGuard
} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {Pausable} from "@openzeppelin/contracts/utils/Pausable.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";

import {IPurchaseManager} from "./IPurchaseManager.sol";
import {IProductPassNFT} from "../tokens/IProductPassNFT.sol";
import {IProductRegistry} from "../registry/IProductRegistry.sol";
import {IPricingRegistry} from "../registry/IPricingRegistry.sol";
import {IPaymentEscrow} from "../escrow/IPaymentEscrow.sol";
import {ISubscriptionEscrow} from "../escrow/ISubscriptionEscrow.sol";
import {IPurchaseRegistry} from "../registry/IPurchaseRegistry.sol";
import {IPricingCalculator} from "../calculator/IPricingCalculator.sol";
import {ICouponRegistry} from "../registry/ICouponRegistry.sol";
import {IDiscountRegistry} from "../registry/IDiscountRegistry.sol";
import {PermissionChecker} from "../abstract/PermissionChecker.sol";
import {PermissionUtils} from "../libs/PermissionUtils.sol";
import {DynamicPriceEnabled} from "../abstract/DynamicPriceEnabled.sol";

/*
 ____                 _            _   __  __ _       _   
|  _ \ _ __ ___   __| |_   _  ___| |_|  \/  (_)_ __ | |_ 
| |_) | '__/ _ \ / _` | | | |/ __| __| |\/| | | '_ \| __|
|  __/| | | (_) | (_| | |_| | (__| |_| |  | | | | | | |_ 
|_|   |_|  \___/ \__,_|\__,_|\___|\__|_|  |_|_|_| |_|\__|
 
 NFT based payment system to mint products onchain with one-time payments and 
 recurring permissionless subscriptions.

 https://productmint.io
*/

/**
 * @title PurchaseManager
 * @notice The PurchaseManager is responsible for purchasing products and managing subscriptions for a user.
 * When a product is purchased, a Product Pass NFT is minted to the user.
 *
 * The PurchaseManager is also responsible for renewing subscriptions, pausing and cancelling subscriptions, and
 * changing the pricing model for an existing subscription.
 *
 * Upon minting a product pass, the user is granted initial owner permissions to the organization enabling the
 * organization to charge renewals, change pricing, and pause and cancel subscriptions.
 */
contract PurchaseManager is
    RegistryEnabled,
    DynamicPriceEnabled,
    ReentrancyGuard,
    Pausable,
    IERC165,
    IPurchaseManager,
    PermissionChecker,
    Ownable2Step
{
    // Total number of product pass tokens minted
    uint256 public passSupply;

    constructor(
        address _contractRegistry,
        address _permissionRegistry,
        address _oldPurchaseManager,
        address _dynamicPriceRegistry
    )
        Ownable(_msgSender())
        RegistryEnabled(_contractRegistry)
        ReentrancyGuard()
        Pausable()
        PermissionChecker(_permissionRegistry)
        DynamicPriceEnabled(_dynamicPriceRegistry)
    {
        if (_oldPurchaseManager != address(0)) {
            passSupply = IPurchaseManager(_oldPurchaseManager).passSupply();
        }
    }

    /**
     * Purchase Products
     */

    function purchaseProducts(
        InitialPurchaseParams calldata params
    ) external payable nonReentrant whenNotPaused {
        passSupply++;

        address purchaser = _msgSender();

        permissionRegistry.grantInitialOwnerPermissions(
            params.organizationId,
            purchaser
        );

        if (params.discountIds.length > 0) {
            IDiscountRegistry(registry.discountRegistry()).mintDiscountsToPass(
                params.organizationId,
                passSupply,
                purchaser,
                params.discountIds
            );
        }

        _purchaseProducts(
            PurchaseProductsParams({
                passOwner: params.to,
                purchaser: purchaser,
                orgId: params.organizationId,
                productPassId: passSupply,
                productIds: params.productIds,
                pricingIds: params.pricingIds,
                quantities: params.quantities,
                couponCode: params.couponCode,
                airdrop: params.airdrop,
                pause: params.pause ||
                    (purchaser != params.to &&
                        !_isOrgAdmin(params.organizationId)),
                isInitialPurchase: true
            })
        );

        IProductPassNFT(registry.productPassNFT()).mint(params.to, passSupply);
    }

    function purchaseAdditionalProducts(
        AdditionalPurchaseParams calldata params
    ) external payable onlyPassOwnerOrAdmin(params.productPassId) nonReentrant {
        address passOwner = _passOwner(params.productPassId);

        uint256 orgId = IPurchaseRegistry(registry.purchaseRegistry())
            .passOrganization(params.productPassId);

        _checkPermissionName(
            PermissionUtils.PASS_PURCHASE_ADDITIONAL,
            orgId,
            passOwner
        );

        _purchaseProducts(
            PurchaseProductsParams({
                passOwner: passOwner,
                purchaser: passOwner,
                orgId: orgId,
                productPassId: params.productPassId,
                productIds: params.productIds,
                pricingIds: params.pricingIds,
                quantities: params.quantities,
                couponCode: params.couponCode,
                airdrop: params.airdrop,
                pause: params.pause,
                isInitialPurchase: false
            })
        );
    }

    function _purchaseProducts(PurchaseProductsParams memory params) internal {
        IProductRegistry(registry.productRegistry()).canPurchaseProducts(
            params.orgId,
            params.productIds,
            params.pricingIds
        );

        (address token, uint256[] memory cycleDurations) = IPricingRegistry(
            registry.pricingRegistry()
        ).validateCheckoutBatch(
                params.orgId,
                params.passOwner,
                params.pricingIds,
                params.quantities
            );

        if (bytes(params.couponCode).length > 0) {
            _setCoupon(params.orgId, params.purchaser, params.couponCode);
        }

        IPurchaseRegistry(registry.purchaseRegistry()).recordProductPurchase(
            params.orgId,
            params.productPassId,
            params.passOwner,
            params.purchaser,
            params.productIds,
            params.pricingIds
        );

        ISubscriptionEscrow(registry.subscriptionEscrow()).createSubscriptions(
            params.orgId,
            params.productPassId,
            params.productIds,
            params.pricingIds,
            cycleDurations,
            params.quantities,
            params.pause
        );

        uint256 totalAmount = IPricingCalculator(registry.pricingCalculator())
            .getInitialPurchaseCost(params.pricingIds, params.quantities);

        (token, totalAmount) = _translateBaseTokenPurchasePrice(
            token,
            totalAmount
        );

        if (totalAmount > 0) {
            _performPurchase(
                PerformPurchaseParams({
                    orgId: params.orgId,
                    productPassId: params.productPassId,
                    purchaser: params.purchaser,
                    passOwner: params.passOwner,
                    totalAmount: totalAmount,
                    token: token,
                    airdrop: params.airdrop,
                    isInitialPurchase: params.isInitialPurchase,
                    forceCoupon: true
                })
            );
        }

        emit ProductsPurchased(
            params.orgId,
            params.productPassId,
            params.passOwner,
            params.productIds,
            params.pricingIds,
            params.quantities,
            token,
            totalAmount
        );
    }

    /**
     * Change subscription pricing
     */

    function changeSubscriptionPricing(
        ChangeSubscriptionPricingParams calldata params
    ) external onlyPassOwnerOrAdmin(params.productPassId) nonReentrant {
        IProductRegistry(registry.productRegistry()).canPurchaseProduct(
            params.orgId,
            params.productId,
            params.newPricingId
        );

        address passOwner = _passOwner(params.productPassId);

        _checkPermissionName(
            PermissionUtils.PASS_SUBSCRIPTION_PRICING,
            params.orgId,
            passOwner
        );

        IPricingRegistry(registry.pricingRegistry()).validateCheckout(
            params.orgId,
            passOwner,
            params.newPricingId,
            ISubscriptionEscrow(registry.subscriptionEscrow()).getUnitQuantity(
                params.productPassId,
                params.productId
            )
        );

        (address token, uint256 amount) = ISubscriptionEscrow(
            registry.subscriptionEscrow()
        ).changeSubscriptionPricing(
                params.productPassId,
                params.productId,
                params.newPricingId,
                _isPassOwner(params.productPassId)
            );

        if (amount > 0) {
            (token, amount) = _translateBaseTokenPurchasePrice(token, amount);

            _performPurchase(
                PerformPurchaseParams({
                    orgId: params.orgId,
                    productPassId: params.productPassId,
                    passOwner: passOwner,
                    purchaser: passOwner,
                    totalAmount: amount,
                    token: token,
                    airdrop: params.airdrop,
                    isInitialPurchase: false,
                    forceCoupon: false
                })
            );
        }
    }

    /**
     * Renew subscription
     */

    function renewSubscription(
        uint256 productPassId,
        uint256 productId,
        bool airdrop
    ) external nonReentrant whenNotPaused {
        _renewSubscription(productPassId, productId, airdrop);
    }

    function renewSubscriptionBatch(
        uint256 productPassId,
        uint256[] calldata productIds,
        bool airdrop
    ) external nonReentrant whenNotPaused {
        if (productIds.length == 0) {
            revert NoProductsProvided();
        }

        for (uint256 i = 0; i < productIds.length; i++) {
            _renewSubscription(productPassId, productIds[i], airdrop);
        }
    }

    function _renewSubscription(
        uint256 productPassId,
        uint256 productId,
        bool airdrop
    ) internal {
        (uint256 orgId, address token, uint256 price) = ISubscriptionEscrow(
            registry.subscriptionEscrow()
        ).renewSubscription(productPassId, productId);

        address passOwner = _passOwner(productPassId);

        _checkPermissionName(
            PermissionUtils.PASS_SUBSCRIPTION_RENEWAL,
            orgId,
            passOwner
        );

        (token, price) = _translateBaseTokenPurchasePrice(token, price);

        if (price > 0) {
            _performPurchase(
                PerformPurchaseParams({
                    orgId: orgId,
                    productPassId: productPassId,
                    passOwner: passOwner,
                    purchaser: passOwner,
                    totalAmount: price,
                    token: token,
                    airdrop: airdrop,
                    isInitialPurchase: false,
                    forceCoupon: false
                })
            );
        }

        emit SubscriptionRenewed(
            orgId,
            productPassId,
            productId,
            passOwner,
            token,
            price
        );
    }

    /**
     * @notice Emitted when a subscription is renewed via the purchase manager
     * @param orgId The organization ID
     * @param productPassId The product pass ID
     * @param productId The product ID
     * @param purchaser The purchaser address
     * @param token The token address used for the renewal purchase
     * @param subtotalAmount The subtotal amount
     */
    event SubscriptionRenewed(
        uint256 indexed orgId,
        uint256 indexed productPassId,
        uint256 indexed productId,
        address purchaser,
        address token,
        uint256 subtotalAmount
    );

    /**
     * Tiered Subscription Unit Quantity
     */

    function changeTieredSubscriptionUnitQuantity(
        uint256 productPassId,
        uint256 productId,
        uint256 quantity,
        bool airdrop
    ) external onlyPassOwnerOrAdmin(productPassId) nonReentrant {
        (uint256 orgId, address token, uint256 amount) = ISubscriptionEscrow(
            registry.subscriptionEscrow()
        ).changeSubscriptionUnitQuantity(productPassId, productId, quantity);

        address passOwner = _passOwner(productPassId);

        _checkPermissionName(
            PermissionUtils.PASS_SUBSCRIPTION_QUANTITY,
            orgId,
            passOwner
        );

        if (amount > 0) {
            (token, amount) = _translateBaseTokenPurchasePrice(token, amount);

            _performPurchase(
                PerformPurchaseParams({
                    orgId: orgId,
                    productPassId: productPassId,
                    passOwner: passOwner,
                    purchaser: passOwner,
                    totalAmount: amount,
                    token: token,
                    airdrop: airdrop,
                    isInitialPurchase: false,
                    forceCoupon: false
                })
            );
        }
    }

    /**
     * Pause subscription
     */

    function pauseSubscription(
        uint256 productPassId,
        uint256 productId,
        bool _pause
    ) external onlyPassOwnerOrAdmin(productPassId) nonReentrant {
        _pauseSubscription(productPassId, productId, _pause);
    }

    function pauseSubscriptionBatch(
        uint256 productPassId,
        uint256[] calldata productIds,
        bool[] calldata pause
    ) external onlyPassOwnerOrAdmin(productPassId) nonReentrant {
        _checkProductStatuses(productIds, pause);

        for (uint256 i = 0; i < productIds.length; i++) {
            _pauseSubscription(productPassId, productIds[i], pause[i]);
        }
    }

    function _pauseSubscription(
        uint256 productPassId,
        uint256 productId,
        bool _pause
    ) internal {
        bool needsRenewal = ISubscriptionEscrow(registry.subscriptionEscrow())
            .pauseSubscription(productPassId, productId, _pause);

        if (!_pause && needsRenewal) {
            _renewSubscription(productPassId, productId, false);
        }
    }

    /**
     * Cancel subscription
     */

    function cancelSubscription(
        uint256 productPassId,
        uint256 productId,
        bool cancel
    ) external onlyPassOwnerOrAdmin(productPassId) nonReentrant {
        _cancelSubscription(productPassId, productId, cancel);
    }

    function cancelSubscriptionBatch(
        uint256 productPassId,
        uint256[] calldata productIds,
        bool[] calldata cancel
    ) external onlyPassOwnerOrAdmin(productPassId) nonReentrant {
        _checkProductStatuses(productIds, cancel);

        for (uint256 i = 0; i < productIds.length; i++) {
            _cancelSubscription(productPassId, productIds[i], cancel[i]);
        }
    }

    function _cancelSubscription(
        uint256 productPassId,
        uint256 productId,
        bool cancel
    ) internal {
        bool needsRenewal = ISubscriptionEscrow(registry.subscriptionEscrow())
            .cancelSubscription(productPassId, productId, cancel);

        if (!cancel && needsRenewal) {
            _renewSubscription(productPassId, productId, false);
        }
    }

    /**
     * Payment Processing
     */

    function _performPurchase(PerformPurchaseParams memory params) internal {
        if (params.airdrop) {
            _checkOrgAdmin(params.orgId);
            return;
        }

        // Apply coupon discount first
        if (
            ICouponRegistry(registry.couponRegistry()).hasPassCouponCode(
                params.orgId,
                params.purchaser
            )
        ) {
            params.totalAmount = _redeemCoupon(
                params.orgId,
                params.purchaser,
                params.totalAmount,
                params.isInitialPurchase,
                params.forceCoupon
            );
        }

        // Apply permanent pass discounts
        params.totalAmount = IDiscountRegistry(registry.discountRegistry())
            .calculateTotalPassDiscountedAmount(
                params.productPassId,
                params.totalAmount
            );

        // Transfer funds
        if (params.totalAmount > 0) {
            if (!params.isInitialPurchase) {
                _checkPermissionName(
                    PermissionUtils.PASS_WALLET_SPEND,
                    params.orgId,
                    params.purchaser
                );
            }

            IPaymentEscrow(registry.paymentEscrow()).transferDirect{
                value: msg.value
            }(
                params.orgId,
                payable(params.purchaser),
                params.token,
                params.totalAmount
            );
        }

        emit PerformPurchase(
            params.orgId,
            params.passOwner,
            params.purchaser,
            params.token,
            params.totalAmount
        );
    }

    receive() external payable {}

    /**
     * Coupons
     */

    function _redeemCoupon(
        uint256 orgId,
        address passOwner,
        uint256 totalAmount,
        bool isInitialPurchase,
        bool forceCoupon
    ) internal returns (uint256) {
        uint256 amount = totalAmount;

        try
            ICouponRegistry(registry.couponRegistry()).redeemCoupon(
                orgId,
                passOwner,
                isInitialPurchase,
                totalAmount
            )
        returns (uint256 discountedAmount) {
            amount = discountedAmount;
        } catch {
            if (forceCoupon) {
                revert InvalidCouponCode();
            } else {
                ICouponRegistry(registry.couponRegistry()).removePassCouponCode(
                        orgId,
                        passOwner
                    );
            }
        }

        return amount;
    }

    function _setCoupon(
        uint256 orgId,
        address passOwner,
        string memory code
    ) internal {
        ICouponRegistry(registry.couponRegistry()).setPassCouponCode(
            orgId,
            passOwner,
            code
        );
    }

    /**
     * Purchase Pause
     */

    function pausePurchases() external onlyOwner {
        _pause();
    }

    function unpausePurchases() external onlyOwner {
        _unpause();
    }

    /**
     * Permission Registry
     */

    function setPermissionRegistry(
        address _permissionRegistry
    ) external onlyOwner {
        _setPermissionRegistry(_permissionRegistry);
    }

    /**
     * Dynamic Price Registry
     */

    function setDynamicPriceRegistry(
        address _dynamicPriceRegistry
    ) external onlyOwner {
        _setDynamicPriceRegistry(_dynamicPriceRegistry);
    }

    /**
     * Checks
     */

    function _checkProductStatuses(
        uint256[] calldata productIds,
        bool[] calldata statuses
    ) internal pure {
        if (productIds.length == 0) {
            revert NoProductsProvided();
        }

        if (productIds.length != statuses.length) {
            revert ProductIdsAndStatusesLengthMismatch();
        }
    }

    function _checkPassOwnerOrAdmin(uint256 productPassId) internal view {
        if (
            !_isPassOwner(productPassId) &&
            !_isOrgAdmin(
                IPurchaseRegistry(registry.purchaseRegistry()).passOrganization(
                    productPassId
                )
            )
        ) {
            revert NotAuthorized();
        }
    }

    /**
     * Modifiers
     */

    modifier onlyPassOwnerOrAdmin(uint256 productPassId) {
        _requireNotPaused();
        _checkPassOwnerOrAdmin(productPassId);
        _;
    }

    /**
     * ERC165
     */

    function supportsInterface(
        bytes4 interfaceId
    ) external pure returns (bool) {
        return
            interfaceId == type(IERC165).interfaceId ||
            interfaceId == type(IPurchaseManager).interfaceId;
    }
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

import {
    EnumerableSet
} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {IERC165} from "@openzeppelin/contracts/interfaces/IERC165.sol";

import {IDynamicERC20} from "../tokens/IDynamicERC20.sol";
import {IDynamicPriceRegistry} from "./IDynamicPriceRegistry.sol";
import {IPaymentEscrow} from "../escrow/IPaymentEscrow.sol";
import {RegistryEnabled} from "../abstract/RegistryEnabled.sol";

/*
 ____                 _            _   __  __ _       _   
|  _ \ _ __ ___   __| |_   _  ___| |_|  \/  (_)_ __ | |_ 
| |_) | '__/ _ \ / _` | | | |/ __| __| |\/| | | '_ \| __|
|  __/| | | (_) | (_| | |_| | (__| |_| |  | | | | | | |_ 
|_|   |_|  \___/ \__,_|\__,_|\___|\__|_|  |_|_|_| |_|\__|
 
 NFT based payment system to mint products onchain with one-time payments and 
 recurring permissionless subscriptions.

 https://productmint.io
*/

/**
 * @title Dynamic Price Registry
 * @notice Manages the dynamic price tokens for the ProductMint system.
 * The registry is used to store the dynamic token contract addresses.
 */
contract DynamicPriceRegistry is
    AccessControl,
    RegistryEnabled,
    IDynamicPriceRegistry
{
    using EnumerableSet for EnumerableSet.AddressSet;

    // The set of all dynamic tokens
    EnumerableSet.AddressSet private tokens;

    // The role required to register a new dynamic token
    bytes32 public constant REGISTER_TOKEN_ROLE =
        keccak256("REGISTER_TOKEN_ROLE");

    // The role required to unregister and remove a dynamic token
    bytes32 public constant UNREGISTER_TOKEN_ROLE =
        keccak256("UNREGISTER_TOKEN_ROLE");

    constructor(
        address _contractRegistry
    ) AccessControl() RegistryEnabled(_contractRegistry) {
        _grantRole(DEFAULT_ADMIN_ROLE, _msgSender());
        _grantRole(REGISTER_TOKEN_ROLE, _msgSender());
        _grantRole(UNREGISTER_TOKEN_ROLE, _msgSender());
    }

    /**
     * View functions
     */

    function getTokenCount() external view returns (uint256) {
        return tokens.length();
    }

    function getTokens() external view returns (address[] memory) {
        return tokens.values();
    }

    function isTokenRegistered(address _token) external view returns (bool) {
        return tokens.contains(_token);
    }

    function isTokenRegisteredBatch(
        address[] calldata _tokens
    ) external view returns (bool) {
        require(_tokens.length > 0, "No tokens provided");

        for (uint256 i = 0; i < _tokens.length; i++) {
            if (!tokens.contains(_tokens[i])) {
                return false;
            }
        }

        return true;
    }

    /**
     * Update the registry
     */

    function registerToken(
        address _token
    ) external onlyRole(REGISTER_TOKEN_ROLE) {
        require(!tokens.contains(_token), "Token already registered");
        require(
            IERC165(_token).supportsInterface(type(IDynamicERC20).interfaceId),
            "Token does not support IDynamicERC20"
        );
        require(
            IPaymentEscrow(registry.paymentEscrow()).whitelistedTokens(_token),
            "Dynamic token is not whitelisted"
        );
        require(
            IPaymentEscrow(registry.paymentEscrow()).whitelistedTokens(
                IDynamicERC20(_token).baseToken()
            ),
            "Base token is not whitelisted"
        );

        tokens.add(_token);

        emit DynamicTokenRegistrationUpdated(_token, true);
    }

    function unregisterToken(
        address _token
    ) external onlyRole(UNREGISTER_TOKEN_ROLE) {
        require(tokens.contains(_token), "Token not registered");

        tokens.remove(_token);

        emit DynamicTokenRegistrationUpdated(_token, false);
    }

    /**
     * IERC165
     */

    function supportsInterface(
        bytes4 interfaceId
    ) public view override returns (bool) {
        return
            interfaceId == type(IDynamicPriceRegistry).interfaceId ||
            super.supportsInterface(interfaceId);
    }
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

interface IContractRegistry {
    /**
     * @notice Emitted when a contract is updated.
     * @param contractName The name of the contract.
     * @param newAddress The new address of the contract.
     */
    event ContractUpdated(
        string indexed contractName,
        address indexed newAddress
    );

    /**
     * Manager
     */

    /**
     * @notice Purchase manager responsible for facilitating product purchases and managing subscriptions.
     * @return The address of the purchase manager.
     */
    function purchaseManager() external view returns (address);

    /**
     * Admin
     */

    /**
     * @notice Organization admin that can delegate other addresses to help manage the organization and make calls on behalf of the organization.
     * @return The address of the organization admin.
     */
    function orgAdmin() external view returns (address);

    /**
     * NFTs
     */

    /**
     * @notice Product Pass NFT that represents a user's membership to an organization and allows them to purchase products.
     * @return The address of the product pass NFT.
     */
    function productPassNFT() external view returns (address);

    /**
     * @notice Organization NFT that represents a product owner and allows for the creation of products that can be purchased by users via Product Passes.
     * @return The address of the organization NFT.
     */
    function organizationNFT() external view returns (address);

    /**
     * Registry
     */

    /**
     * @notice Product registry that allows for the creation of products that can be purchased by users via Product Passes.
     * @return The address of the product registry.
     */
    function productRegistry() external view returns (address);

    /**
     * @notice Pricing registry that allows for the creation of pricing models that can be linked to products for purchase.
     * @return The address of the pricing registry.
     */
    function pricingRegistry() external view returns (address);

    /**
     * @notice Purchase registry records all purchases made by users via Product Passes.
     * @return The address of the purchase registry.
     */
    function purchaseRegistry() external view returns (address);

    /**
     * @notice Coupon registry that allows for the creation of coupons that can be applied to purchases and subscriptions.
     * @return The address of the coupon registry.
     */
    function couponRegistry() external view returns (address);

    /**
     * @notice Permanent discount registry that allows for the creation of permanent discounts that are minted onto product passes.
     * Discounts minted onto product passes are permanent and used in all future purchases including any subscription renewals.
     * @return The address of the discount registry.
     */
    function discountRegistry() external view returns (address);

    /**
     * Calculator
     */

    /**
     * @notice Pricing calculator that allows performs all the pricing calculations for different pricing models created in the pricing registry.
     * @return The address of the pricing calculator.
     */
    function pricingCalculator() external view returns (address);

    /**
     * Oracles
     */

    /**
     * @notice Responsible for knowing whether the product pass NFT can be transferred from one user to another.
     *  Products created in the registry must be set to transferable to be able to be transferred.
     * @return The address of the product transfer oracle.
     */
    function productTransferOracle() external view returns (address);

    /**
     * @notice Responsible for knowing whether the subscription can be transferred from one user to another.
     *  Subs must be in a paused state to be transferred.
     *  Organizations must enable this feature for subscriptions to be paused.
     * @return The address of the subscription transfer oracle.
     */
    function subscriptionTransferOracle() external view returns (address);

    /**
     * Escrow
     */

    /**
     * @notice Escrow that that holds all the subscriptions that have been created for product passes
     * @return The address of the subscription escrow.
     */
    function subscriptionEscrow() external view returns (address);

    /**
     * @notice Escrow that holds all the payments that have been made for purchases that are withdrawable by the organization.
     * @return The address of the payment escrow.
     */
    function paymentEscrow() external view returns (address);

    /**
     * Usage Recorder
     */

    /**
     * @notice Usage recorder where usage meters can be created for pricing models set to usage based billing.
     *  Once a meter has been created and is active, organizations can begin recording usage for their products.
     * @return The address of the usage recorder.
     */
    function usageRecorder() external view returns (address);

    /**
     * Locks
     */

    /**
     * @notice Lock for the product pass NFT.
     *  This lock prevents the product pass NFT from being changed once it has been set.
     * @return The address of the product pass NFT.
     */
    function PASS_LOCK() external view returns (bytes32);

    /**
     * @notice Lock for the organization NFT.
     *  This lock prevents the organization NFT from being changed once it has been set.
     * @return The address of the organization NFT.
     */
    function ORG_LOCK() external view returns (bytes32);

    /**
     * Batch Setup
     */

    struct BatchSetupContracts {
        // Manager
        address purchaseManager;
        // Admin
        address orgAdmin;
        // NFTs
        address productPassNFT;
        address organizationNFT;
        // Registry
        address productRegistry;
        address pricingRegistry;
        address purchaseRegistry;
        address couponRegistry;
        address discountRegistry;
        // Calculator
        address pricingCalculator;
        // Oracles
        address productTransferOracle;
        address subscriptionTransferOracle;
        // Escrow
        address subscriptionEscrow;
        address paymentEscrow;
        // Usage recorder
        address usageRecorder;
    }

    /**
     * @notice Batch set all the contracts in the registry.
     * @param _contracts The contracts to be set.
     */
    function batchSetContracts(BatchSetupContracts memory _contracts) external;
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

interface ICouponRegistry {
    /**
     * @notice Coupon struct
     * @param orgId Organization ID that the coupon belongs to
     * @param code Coupon code
     * @param discount Discount percentage. 10 = 0.1%, 100 = 1%, 250 = 2.5%, 1000 = 10%, 10000 = 100%, etc.
     * @param expiration Expiration timestamp when the coupon will no longer be valid. 0 = never expires.
     * @param totalRedemptions Total number of redemptions for the coupon
     * @param maxTotalRedemptions Maximum total redemptions for the coupon. 0 = unlimited.
     * @param isInitialPurchaseOnly True then only initial product pass mint purchases should be able to use the coupon
     * @param isActive True then the coupon is currently active and can be redeemed, else it is inactive and cannot be redeemed
     * @param isRestricted True then the coupon is restricted to a specific group of users that have been granted access
     * @param isOneTimeUse True then the coupon can only be used once per customer
     */
    struct Coupon {
        uint256 orgId;
        string code;
        uint256 discount;
        uint256 expiration;
        uint256 totalRedemptions;
        uint256 maxTotalRedemptions;
        bool isInitialPurchaseOnly;
        bool isActive;
        bool isRestricted;
        bool isOneTimeUse;
    }

    /**
     * @notice Create coupon parameters
     * @param orgId Organization ID that the coupon belongs to
     * @param code Coupon code
     * @param discount Discount percentage. 10 = 0.1%, 100 = 1%, 250 = 2.5%, 1000 = 10%, 10000 = 100%, etc.
     * @param expiration Expiration timestamp when the coupon will no longer be valid. 0 = never expires.
     * @param maxTotalRedemptions Maximum total redemptions for the coupon. 0 = unlimited.
     * @param isInitialPurchaseOnly True then only initial product pass mint purchases should be able to use the coupon
     * @param isActive True then the coupon is currently active and can be redeemed
     * @param isRestricted True then the coupon is restricted to a specific group of users that have been granted access
     * @param isOneTimeUse True then the coupon can only be used once per customer
     */
    struct CreateCouponParams {
        uint256 orgId;
        string code;
        uint256 discount;
        uint256 expiration;
        uint256 maxTotalRedemptions;
        bool isInitialPurchaseOnly;
        bool isActive;
        bool isRestricted;
        bool isOneTimeUse;
    }

    /**
     * @notice Get the total number of coupons that have been created
     * @return The total number of coupons
     */
    function totalCoupons() external view returns (uint256);

    /**
     * Redemption
     */

    /**
     * @notice Invalid coupon code error when redeeming a coupon
     */
    error InvalidCouponCode();

    /**
     * @notice Coupon code not found error when redeeming a coupon
     * @param code Coupon code
     */
    error CouponCodeNotFound(string code);

    /**
     * @notice Coupon not active error when redeeming a coupon
     * @param couponId Coupon ID
     */
    error CouponNotActive(uint256 couponId);

    /**
     * @notice Coupon expired error when redeeming a coupon
     * @param couponId Coupon ID
     */
    error CouponExpired(uint256 couponId);

    /**
     * @notice Coupon max redemptions reached error when redeeming a coupon
     * @param couponId Coupon ID
     * @param maxRedemptions Maximum redemptions
     */
    error CouponMaxRedemptionsReached(uint256 couponId, uint256 maxRedemptions);

    /**
     * @notice Coupon initial purchase only error when redeeming a coupon
     * @param couponId Coupon ID
     */
    error CouponInitialPurchaseOnly(uint256 couponId);

    /**
     * @notice Coupon already redeemed error when redeeming a coupon
     * @param couponId Coupon ID
     * @param passOwner Pass owner address
     */
    error CouponAlreadyRedeemed(uint256 couponId, address passOwner);

    /**
     * @notice Coupon restricted error when redeeming a coupon
     * @param couponId Coupon ID
     * @param passOwner Pass owner address
     */
    error CouponRestricted(uint256 couponId, address passOwner);

    /**
     * @notice Emitted when a coupon is redeemed
     */
    event CouponRedeemed(
        uint256 indexed orgId,
        uint256 indexed couponId,
        address indexed passOwner
    );

    /**
     * @notice Attempt to redeem the current pass coupon code that is set by recording the redemption and return the discounted amount
     * @dev Will revert if the coupon is not redeemable
     * @param orgId Organization ID that the coupon belongs to
     * @param passOwner Pass owner address
     * @param isInitialPurchase True then the coupon is for an initial product pass mint purchase
     * @param amount Amount to redeem the coupon for
     * @return The discounted total amount
     */
    function redeemCoupon(
        uint256 orgId,
        address passOwner,
        bool isInitialPurchase,
        uint256 amount
    ) external returns (uint256);

    /**
     * Queries
     */

    /**
     * @notice Get a coupon by ID
     * @param couponId Coupon ID
     * @return The coupon
     */
    function getCoupon(uint256 couponId) external view returns (Coupon memory);

    /**
     * @notice Get all the coupon IDs for an organization
     * @param orgId Organization ID
     * @return The coupon IDs
     */
    function getOrgCouponIds(
        uint256 orgId
    ) external view returns (uint256[] memory);

    /**
     * @notice Get all the coupons for an organization
     * @param orgId Organization ID
     * @return The coupons
     */
    function getOrgCoupons(
        uint256 orgId
    ) external view returns (Coupon[] memory);

    /**
     * @notice Check if a coupon exists for an organization
     * @param orgId Organization ID
     * @param code Coupon code
     * @return True if the coupon exists, else false
     */
    function orgCouponExists(
        uint256 orgId,
        string calldata code
    ) external view returns (bool);

    /**
     * @notice Get the coupon ID for an organization and code
     * @param orgId Organization ID
     * @param code Coupon code
     * @return The coupon ID
     */
    function orgCouponCodes(
        uint256 orgId,
        string calldata code
    ) external view returns (uint256);

    /**
     * @notice Get the redeemed coupons for a pass owner
     * @param orgId Organization ID
     * @param passOwner Pass owner address
     * @return The coupons that the pass owner has redeemed
     */
    function getRedeemedCoupons(
        uint256 orgId,
        address passOwner
    ) external view returns (uint256[] memory);

    /**
     * @notice Check if a pass owner has redeemed a coupon
     * @param orgId Organization ID
     * @param passOwner Pass owner address
     * @param couponId Coupon ID
     * @return True if the pass owner has redeemed the coupon, else false
     */
    function hasRedeemedCoupon(
        uint256 orgId,
        address passOwner,
        uint256 couponId
    ) external view returns (bool);

    /**
     * @notice Get the discounted amount for a coupon
     * @param couponId Coupon ID
     * @param amount Amount to get the discounted amount for
     * @return The discounted amount
     */
    function discountedAmount(
        uint256 couponId,
        uint256 amount
    ) external view returns (uint256);

    /**
     * @notice Check if a coupon code is redeemable
     * @dev Will revert if the coupon is not redeemable
     * @param orgId Organization ID
     * @param passOwner Pass owner address
     * @param code Coupon code
     * @param isInitialPurchase True then the coupon is for an initial product pass mint purchase
     * @return The coupon ID if the coupon is redeemable, else 0
     */
    function isCodeRedeemable(
        uint256 orgId,
        address passOwner,
        string memory code,
        bool isInitialPurchase
    ) external view returns (uint256);

    /**
     * Creation
     */

    /**
     * @notice Emitted when a coupon is created
     */
    event CouponCreated(uint256 indexed orgId, uint256 indexed couponId);

    /**
     * @notice Creates a new coupon for an organization
     * @param params Coupon parameters
     */
    function createCoupon(CreateCouponParams calldata params) external;

    /**
     * Management
     */

    /**
     * @notice Emitted when a coupon is updated
     */
    event CouponUpdated(uint256 indexed orgId, uint256 indexed couponId);

    /**
     * @notice Emitted when a coupon status is updated
     */
    event CouponStatusUpdated(
        uint256 indexed orgId,
        uint256 indexed couponId,
        bool isActive
    );

    /**
     * @notice Set the discount for a coupon
     * @param couponId Coupon ID
     * @param discount Discount percentage. 10 = 0.1%, 100 = 1%, 250 = 2.5%, 1000 = 10%, 10000 = 100%, etc.
     */
    function setCouponDiscount(uint256 couponId, uint256 discount) external;

    /**
     * @notice Set the expiration for a coupon
     * @dev Will revert if the expiration is in the past
     * @param couponId Coupon ID
     * @param expiration Expiration timestamp when the coupon will no longer be valid. 0 = never expires.
     */
    function setCouponExpiration(uint256 couponId, uint256 expiration) external;

    /**
     * @notice Set the maximum total redemptions for a coupon
     * @param couponId Coupon ID
     * @param maxTotalRedemptions Maximum total redemptions for the coupon. 0 = unlimited.
     */
    function setCouponMaxRedemptions(
        uint256 couponId,
        uint256 maxTotalRedemptions
    ) external;

    /**
     * @notice Set if a coupon is only for initial product pass mint purchases
     * @param couponId Coupon ID
     * @param isInitialPurchaseOnly True then only initial product pass mint purchases should be able to use the coupon
     */
    function setCouponNewCustomers(
        uint256 couponId,
        bool isInitialPurchaseOnly
    ) external;

    /**
     * @notice Set if a coupon is active
     * @param couponId Coupon ID
     * @param active True then the coupon is currently active and can be redeemed, else it is inactive and cannot be redeemed
     */
    function setCouponActive(uint256 couponId, bool active) external;

    /**
     * @notice Set if a coupon is restricted to a specific group of users that have been granted access
     * @param couponId Coupon ID
     * @param restricted True then the coupon is restricted to a specific group of users that have been granted access
     */
    function setCouponRestricted(uint256 couponId, bool restricted) external;

    /**
     * Restricted Access
     */

    /**
     * @notice Batch set the restricted access for a pass owners
     * @param couponId Coupon ID
     * @param passOwners Pass owners addresses
     * @param restricted True then the pass owner has restricted access to the coupon, else false
     */
    function setRestrictedAccess(
        uint256 couponId,
        address[] calldata passOwners,
        bool[] calldata restricted
    ) external;

    /**
     * Pass Owner Codes
     */

    /**
     * @notice Emitted when a pass owner coupon code is set or removed. If removed, the code will be an empty string.
     */
    event PassCouponCodeSet(
        uint256 indexed orgId,
        address indexed passOwner,
        string code
    );

    /**
     * @notice Get the coupon code for a pass owner
     * @param orgId Organization ID
     * @param passOwner Pass owner address
     * @return The coupon code
     */
    function passOwnerCodes(
        uint256 orgId,
        address passOwner
    ) external view returns (string memory);

    /**
     * @notice Check if a pass owner has a coupon code
     * @param orgId Organization ID
     * @param passOwner Pass owner address
     * @return True if the pass owner has a coupon code set, else false
     */
    function hasPassCouponCode(
        uint256 orgId,
        address passOwner
    ) external view returns (bool);

    /**
     * @notice Set the coupon code for a pass owner
     * @param orgId Organization ID
     * @param passOwner Pass owner address
     * @param code Coupon code
     */
    function setPassCouponCode(
        uint256 orgId,
        address passOwner,
        string calldata code
    ) external;

    /**
     * @notice Batch set the coupon code for multiple pass owners
     * @dev Only org admins can call this function
     * @param orgId Organization ID
     * @param passOwners Pass owners addresses
     * @param codes Coupon codes
     */
    function setPassCouponCodeBatch(
        uint256 orgId,
        address[] calldata passOwners,
        string[] calldata codes
    ) external;

    /**
     * @notice Remove the coupon code for a pass owner
     * @param orgId Organization ID
     * @param passOwner Pass owner address
     */
    function removePassCouponCode(uint256 orgId, address passOwner) external;
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

interface IDiscountRegistry {
    /**
     * Discount
     */

    /**
     * @notice Discount struct
     * @param id The id of the discount
     * @param orgId The organization ID
     * @param name The name of the discount. Must be unique within the organization.
     * @param discount The discount amount
     * @param totalMints The total number of mints for the discount
     * @param maxMints The maximum number of times the discount can be minted
     * @param isActive Whether the discount is active
     * @param isRestricted Whether the discount is restricted
     */
    struct Discount {
        uint256 id;
        uint256 orgId;
        string name;
        uint256 discount;
        uint256 totalMints;
        uint256 maxMints;
        bool isActive;
        bool isRestricted;
    }

    /**
     * @notice Total number of discounts created
     * @return The total number of discounts created
     */
    function totalDiscounts() external view returns (uint256);

    /**
     * Get Discounts
     */

    /**
     * @notice Revert when discount does not exist
     * @param discountId The discount ID
     */
    error DiscountDoesNotExist(uint256 discountId);

    /**
     * @notice Get discount details
     * @param discountId The discount ID
     * @return The discount
     */
    function getDiscount(
        uint256 discountId
    ) external view returns (Discount memory);

    /**
     * @notice Get discount details for multiple discounts
     * @param discountIds The discount IDs
     * @return The discounts
     */
    function getDiscountBatch(
        uint256[] calldata discountIds
    ) external view returns (Discount[] memory);

    /**
     * @notice Get discount names
     * @param discountIds The discount IDs
     * @return The discount names
     */
    function getDiscountNames(
        uint256[] calldata discountIds
    ) external view returns (string[] memory);

    /**
     * @notice Get organization discount IDs for discounts created by the organization
     * @param orgId The organization ID
     * @return The discount IDs
     */
    function getOrgDiscountIds(
        uint256 orgId
    ) external view returns (uint256[] memory);

    /**
     * @notice Get all discount IDs minted onto a pass
     * @param passId The pass ID
     * @return The discount IDs
     */
    function getPassDiscountIds(
        uint256 passId
    ) external view returns (uint256[] memory);

    /**
     * @notice Check if a pass has a discount
     * @param passId The pass ID
     * @param discountId The discount ID
     * @return True if the pass has the discount, false otherwise
     */
    function hasPassDiscount(
        uint256 passId,
        uint256 discountId
    ) external view returns (bool);

    /**
     * @notice Get the total discount for a list of discounts
     * @param discountIds The discount IDs
     * @return The total discount
     */
    function getTotalDiscount(
        uint256[] memory discountIds
    ) external view returns (uint256);

    /**
     * @notice Get the total discount for a pass by summing all discounts minted onto the pass
     * @param passId The pass ID
     * @return The total discount
     */
    function getTotalPassDiscount(
        uint256 passId
    ) external view returns (uint256);

    /**
     * @notice Get the discount ID for a discount name
     * @param orgId The organization ID
     * @param name The discount name
     * @return The discount ID. 0 if the discount name does not exist.
     */
    function discountNames(
        uint256 orgId,
        string memory name
    ) external view returns (uint256);

    /**
     * Mint Discounts
     */

    /**
     * @notice Emitted when a discount is minted onto a pass
     * @param orgId The organization ID
     * @param passId The pass ID
     * @param discountId The discount ID
     * @param minter The minter for the discount
     */
    event DiscountMinted(
        uint256 indexed orgId,
        uint256 indexed passId,
        uint256 indexed discountId,
        address minter
    );

    /**
     * @notice Revert when discount is not found
     * @param orgId The organization ID
     * @param name The discount name
     */
    error DiscountNotFound(uint256 orgId, string name);

    /**
     * @notice Revert when discount is not for organization during minting
     * @param orgId The organization ID
     * @param discountId The discount ID
     */
    error DiscountNotForOrg(uint256 orgId, uint256 discountId);

    /**
     * @notice Revert when discount is not active during minting and cannot be minted
     * @param discountId The discount ID
     */
    error DiscountNotActive(uint256 discountId);

    /**
     * @notice Revert when discount is restricted and access is not granted during minting
     * @param discountId The discount ID
     * @param minter The minter
     */
    error DiscountAccessRestricted(uint256 discountId, address minter);

    /**
     * @notice Revert when discount max mints is reached during minting
     * @param discountId The discount ID
     * @param maxMints The maximum number of mints
     */
    error DiscountMaxMintsReached(uint256 discountId, uint256 maxMints);

    /**
     * @notice Revert when discount is already minted onto a pass
     * @param discountId The discount ID
     * @param passId The pass ID
     */
    error DiscountAlreadyMinted(uint256 discountId, uint256 passId);

    /**
     * @notice Revert when pass is not a member of the organization
     * @param orgId The organization ID
     * @param passId The pass ID
     */
    error PassNotOrgMember(uint256 orgId, uint256 passId);

    /**
     * @notice Check if a discount can be minted by a pass owner
     * @dev will revert if any of the checks fail
     * @param orgId The organization ID
     * @param passId The pass ID
     * @param minter The minter
     * @param discountId The discount ID
     */
    function canMintDiscount(
        uint256 orgId,
        uint256 passId,
        address minter,
        uint256 discountId
    ) external view;

    /**
     * Check if a discount can be minted by a pass owner with the name
     * @param orgId The organization ID
     * @param passId The pass ID
     * @param minter The minter
     * @param name The discount name
     * @return The discount ID
     */
    function canMintDiscountByName(
        uint256 orgId,
        uint256 passId,
        address minter,
        string memory name
    ) external view returns (uint256);

    /**
     * Check if a list of discounts can be minted by a pass owner with the name
     * @param orgId The organization ID
     * @param passId The pass ID
     * @param minter The minter
     * @param names The discount names
     * @return The discount IDs
     */
    function canMintDiscountByNameBatch(
        uint256 orgId,
        uint256 passId,
        address minter,
        string[] memory names
    ) external view returns (uint256[] memory);

    /**
     * @notice Mint discounts onto a pass
     * @dev used by purchase manager
     * @param orgId The organization ID
     * @param passId The pass ID
     * @param minter The minter for the discount
     * @param discountIds The discount IDs
     */
    function mintDiscountsToPass(
        uint256 orgId,
        uint256 passId,
        address minter,
        uint256[] calldata discountIds
    ) external;

    /**
     * @notice Mint discounts onto a pass by a pass owner
     * @dev used by pass owner
     * @param passId The pass ID
     * @param discountIds The discount IDs
     */
    function mintDiscountsToPassByOwner(
        uint256 passId,
        uint256[] calldata discountIds
    ) external;

    /**
     * @notice Mint discounts onto a pass by an organization
     * @dev used by organization
     * @param orgId The organization ID
     * @param passIds The pass IDs
     * @param discountIds The discount IDs
     */
    function mintDiscountsToPassByOrg(
        uint256 orgId,
        uint256[] calldata passIds,
        uint256[] calldata discountIds
    ) external;

    /**
     * Calculations
     */

    /**
     * @notice Calculate the total discounted amount for a list of discounts
     * @param discountIds The discount IDs
     * @param amount The amount to be discounted
     * @return The total discounted amount
     */
    function calculateTotalDiscountedAmount(
        uint256[] memory discountIds,
        uint256 amount
    ) external view returns (uint256);

    /**
     * @notice Calculate the total discounted amount for a pass by summing all discounts minted onto the pass
     * @param passId The pass ID
     * @param amount The amount to be discounted
     * @return The total discounted amount
     */
    function calculateTotalPassDiscountedAmount(
        uint256 passId,
        uint256 amount
    ) external view returns (uint256);

    /**
     * Create Discount
     */

    /**
     * @notice Emitted when a discount is created
     * @param orgId The organization ID
     * @param discountId The discount ID
     * @param name The name of the discount
     * @param discount The discount in basis points up to 10000
     */
    event DiscountCreated(
        uint256 indexed orgId,
        uint256 indexed discountId,
        string name,
        uint256 discount
    );

    /**
     * @notice Create discount params
     * @param orgId The organization ID
     * @param name The name of the discount
     * @param discount The discount in basis points up to 10000
     * @param maxMints The maximum number of times the discount can be minted. 0 means unlimited.
     * @param isActive Whether the discount is active. Must be true to be minted.
     * @param isRestricted Whether the discount is restricted. If true, only specific pass owners can mint the discount.
     */
    struct CreateDiscountParams {
        uint256 orgId;
        string name;
        uint256 discount;
        uint256 maxMints;
        bool isActive;
        bool isRestricted;
    }

    /**
     * @notice Create a new discount
     * @param params The create discount params
     */
    function createDiscount(CreateDiscountParams calldata params) external;

    /**
     * Update Discount
     */

    /**
     * @notice Emitted when a discount is updated
     * @param orgId The organization ID
     * @param discountId The discount ID
     */
    event DiscountUpdated(uint256 indexed orgId, uint256 indexed discountId);

    /**
     * @notice Set discount name
     * @param discountId The discount ID
     * @param name The new name of the discount
     */
    function setDiscountName(uint256 discountId, string calldata name) external;

    /**
     * @notice Set discount
     * @param discountId The discount ID
     * @param discount The new discount in basis points up to 10000
     */
    function setDiscount(uint256 discountId, uint256 discount) external;

    /**
     * @notice Set discount max mints
     * @param discountId The discount ID
     * @param maxMints The new maximum number of times the discount can be minted. 0 means unlimited.
     */
    function setDiscountMaxMints(uint256 discountId, uint256 maxMints) external;

    /**
     * @notice Set discount active
     * @param discountId The discount ID
     * @param isActive Whether the discount is active and can be minted.
     */
    function setDiscountActive(uint256 discountId, bool isActive) external;

    /**
     * @notice Set discount restricted
     * @param discountId The discount ID
     * @param isRestricted Whether the discount is restricted. If true, only specific pass owners can mint the discount.
     */
    function setDiscountRestricted(
        uint256 discountId,
        bool isRestricted
    ) external;

    /**
     * Restricted Access
     */

    /**
     * @notice Set restricted access
     * @param discountId The discount ID
     * @param passOwners The pass owners
     * @param restricted Whether the pass owners can mint the discount.
     */
    function setRestrictedAccess(
        uint256 discountId,
        address[] calldata passOwners,
        bool[] calldata restricted
    ) external;
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

interface IDynamicPriceRegistry {
    /**
     * Roles
     */

    /**
     * @notice The role required to register a new dynamic token
     */
    function REGISTER_TOKEN_ROLE() external view returns (bytes32);

    /**
     * @notice The role required to unregister and remove a dynamic token
     */
    function UNREGISTER_TOKEN_ROLE() external view returns (bytes32);

    /**
     * View functions
     */

    /**
     * @notice Get the number of tokens registered in the registry
     * @return The number of tokens registered in the registry
     */
    function getTokenCount() external view returns (uint256);

    /**
     * @notice Get the list of tokens registered in the registry
     * @return The list of tokens registered in the registry
     */
    function getTokens() external view returns (address[] memory);

    /**
     * @notice Check if a token is registered in the registry
     * @param token The address of the token to check
     * @return True if the token is registered, false otherwise
     */
    function isTokenRegistered(address token) external view returns (bool);

    /**
     * @notice Check if a list of tokens are registered in the registry
     * @param tokens The list of tokens to check
     * @return True if all tokens are registered, false otherwise
     */
    function isTokenRegisteredBatch(
        address[] calldata tokens
    ) external view returns (bool);

    /**
     * Update the registry
     */

    /**
     * @notice Emitted when a token is registered or unregistered
     * @param token The address of the token that was registered or unregistered
     * @param registered True if the token was registered, false if it was unregistered
     */
    event DynamicTokenRegistrationUpdated(
        address indexed token,
        bool registered
    );

    /**
     * @notice Register a token in the registry
     * @param token The address of the token to register
     */
    function registerToken(address token) external;

    /**
     * @notice Unregister a token in the registry
     * @param token The address of the token to unregister
     */
    function unregisterToken(address token) external;
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

interface IPermissionRegistry {
    /**
     * @dev Errors
     */

    /**
     * @notice Revert if a permission is inactive
     *
     * @param _permission The ID of the inactive permission
     */
    error InactivePermission(bytes32 _permission);

    /**
     * @notice Revert if any of the permissions are inactive
     *
     * @param _permissions The IDs of the potentially inactive permissions
     */
    error InactivePermissionBatch(bytes32[] _permissions);

    /**
     * @dev View owner permissions
     */

    /**
     * @notice Has the owner had their initial permissions set for the org?
     *
     * @param _orgId The ID of the organization
     * @param _owner The address of the owner
     * @return True if the owner permissions have been set for the org, false otherwise
     */
    function ownerPermissionsSet(
        uint256 _orgId,
        address _owner
    ) external view returns (bool);

    /**
     * @notice Does the owner have a permission for the org?
     *
     * @param _orgId The ID of the organization
     * @param _owner The address of the owner
     * @param _permission The ID of the permission
     * @return True if the owner has the permission, false otherwise
     */
    function hasOwnerPermission(
        uint256 _orgId,
        address _owner,
        bytes32 _permission
    ) external view returns (bool);

    /**
     * @notice Does the owner have a batch of permissions for the org?
     *
     * @param _orgId The ID of the organization
     * @param _owner The address of the owner
     * @param _permissions The IDs of the permissions
     * @return _hasPermissions Resulting array of booleans indicating if the owner has each permission
     */
    function hasOwnerPermissionBatch(
        uint256 _orgId,
        address _owner,
        bytes32[] memory _permissions
    ) external view returns (bool[] memory _hasPermissions);

    /**
     * @notice Get all the permissions for the owner
     *
     * @param _orgId The ID of the organization
     * @param _owner The address of the owner
     * @return _permissions The IDs of the permissions
     */
    function getOwnerPermissions(
        uint256 _orgId,
        address _owner
    ) external view returns (bytes32[] memory);

    /**
     * @notice Get all the permissions for a batch of owners
     *
     * @param _orgIds The IDs of the organizations
     * @param _owners The addresses of the owners
     * @return _permissions The IDs of the permissions
     */
    function getOwnerPermissionsBatch(
        uint256[] memory _orgIds,
        address[] memory _owners
    ) external view returns (bytes32[][] memory _permissions);

    /**
     * @dev Grant and revoke owner permissions
     */

    /**
     * @notice Emitted when owner permissions are updated
     *
     * @param _orgId The ID of the organization
     * @param _owner The address of the owner
     * @param _grantAccess True if the permissions are being granted, false if they are being revoked
     * @param _permissions The IDs of the permissions
     */
    event OwnerPermissionsUpdated(
        uint256 indexed _orgId,
        address indexed _owner,
        bool indexed _grantAccess,
        bytes32[] _permissions
    );

    /**
     * @notice Grant permissions to the owner
     *
     * @dev caller can only update their own permissions
     * @param _orgId The ID of the organization
     * @param _permissions The IDs of the permissions
     */
    function addOwnerPermissions(
        uint256 _orgId,
        bytes32[] memory _permissions
    ) external;

    /**
     * @notice Revoke permissions from the owner
     *
     * @dev caller can only update their own permissions
     * @param _orgId The ID of the organization
     * @param _permissions The IDs of the permissions
     */
    function removeOwnerPermissions(
        uint256 _orgId,
        bytes32[] memory _permissions
    ) external;

    /**
     * @dev Organization permissions
     */

    /**
     * @notice Is the default permissions excluded for the org?
     *
     * If true then new pass owners will not be granted the default permissions.
     *
     * @param _orgId The ID of the organization
     * @return True if the default permissions are excluded, false otherwise
     */
    function excludeDefaultPermissions(
        uint256 _orgId
    ) external view returns (bool);

    /**
     * @notice Emitted when the default permissions exclusion is updated for the org
     *
     * @param _orgId The ID of the organization
     * @param _exclude True if the default permissions are excluded, false otherwise
     */
    event ExcludeDefaultPermissionsUpdated(
        uint256 indexed _orgId,
        bool _exclude
    );

    /**
     * @notice Set if the default permissions are excluded for the org during initial purchase
     *
     * @param _orgId The ID of the organization
     * @param _exclude True if the default permissions are excluded, false otherwise
     */
    function setExcludeDefaultPermissions(
        uint256 _orgId,
        bool _exclude
    ) external;

    /**
     * @notice Get all the permissions for the organization.
     *
     * These additional permissions are granted to the pass owner during the initial purchase.
     *
     * @param _orgId The ID of the organization
     * @return _permissions The IDs of the permissions
     */
    function getOrgPermissions(
        uint256 _orgId
    ) external view returns (bytes32[] memory);

    /**
     * @notice Emitted when an organization permission is updated
     *
     * @param _orgId The ID of the organization
     * @param _permission The ID of the permission
     * @param _add True if the permission is being added, false if it is being removed
     */
    event OrgPermissionUpdated(
        uint256 indexed _orgId,
        bytes32 _permission,
        bool _add
    );

    /**
     * @notice Update the permissions for the organization
     *
     * @param _orgId The ID of the organization
     * @param _permissions The IDs of the permissions
     * @param _add True if the permissions are being added, false if they are being removed
     */
    function updateOrgPermissions(
        uint256 _orgId,
        bytes32[] memory _permissions,
        bool[] memory _add
    ) external;

    /**
     * @notice Grant the initial permissions to the owner
     *
     * @dev Only the purchase manager can call this function
     * @param _orgId The ID of the organization
     * @param _owner The address of the owner
     */
    function grantInitialOwnerPermissions(
        uint256 _orgId,
        address _owner
    ) external;

    /**
     * @dev Admin functions
     */

    /**
     * @notice Parameters for updating owner permissions
     *
     * @param owner The address of the owner
     * @param orgId The ID of the organization
     * @param permissions The IDs of the permissions
     * @param grantAccess True if the permissions are being granted, false if they are being revoked
     */
    struct AdminPermissionParams {
        address owner;
        uint256 orgId;
        bytes32[] permissions;
        bool grantAccess;
    }

    /**
     * @notice Update the owner permissions
     *
     * @dev Only the owner can call this function
     * @param _params The parameters for updating the owner permissions
     */
    function adminUpdateOwnerPermissions(
        AdminPermissionParams[] calldata _params
    ) external;

    /**
     * @notice Grant the initial permissions to the owners of a batch of passes
     *
     * This can be used to backfill the initial permissions for all pass owners
     * who have already purchased passes.
     *
     * @dev Only the owner can call this function
     * @param _passIds The IDs of the passes
     */
    function adminGrantInitialOwnerPermissions(
        uint256[] calldata _passIds
    ) external;

    /**
     * @notice Set the permission factory
     *
     * @dev Only the owner can call this function
     * @param _permissionFactory The address of the permission factory
     */
    function setPermissionFactory(address _permissionFactory) external;
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

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

interface IPricingRegistry {
    /**
     * Errors
     */

    /**
     * @notice Thrown when a pricing configuration is not owned by the organization.
     */
    error PricingNotAuthorized();

    /**
     * @notice Thrown when a pricing configuration is inactive.
     */
    error PricingInactive();

    /**
     * @notice Thrown during checkout validation when multiple pricing configurations have different tokens.
     */
    error PricingTokensMismatch();

    /**
     * @notice Thrown when a pricing configuration is restricted and the product pass owner does not have access.
     */
    error PricingRestrictedAccess();

    /**
     * @notice Thrown when a pricing configuration with usage based charge style required.
     */
    error RequiresUsageChargeStyle();

    /**
     * @notice Thrown when a pricing configuration is not a one time or flat rate charge style.
     */
    error RequiresOneTimeOrFlatRateChargeStyle();

    /**
     * @notice Thrown when a pricing configuration is not an ERC20 token.
     */
    error RequiresERC20Token();

    /**
     * @notice Thrown when a pricing configuration is not whitelisted.
     */
    error TokenNotWhitelisted();

    /**
     * @notice Thrown during checkout validation when a pricing configuration has an invalid quantity for a non tiered pricing configuration.
     * @dev All pricing charge styles except TIERED_VOLUME and TIERED_GRADUATED must have a quantity of 0.
     */
    error InvalidQuantity();

    /**
     * Checkout
     */

    /**
     * Confirm that the pricing configuration can be used in a checkout.
     * @param _organizationId The organization ID to validate the pricing configuration for.
     * @param _productPassOwner The product pass owner to validate the pricing configuration for.
     * @param _pricingId The pricing ID to validate.
     * @param _quantity The quantity to validate. Must be 0 unless the pricing configuration is a TIERED_VOLUME or TIERED_GRADUATED charge style.
     * @return cycleDuration The cycle duration for the pricing configuration.
     */
    function validateCheckout(
        uint256 _organizationId,
        address _productPassOwner,
        uint256 _pricingId,
        uint256 _quantity
    ) external view returns (uint256 cycleDuration);

    /**
     * @notice Confirm that all the pricing configurations exist, are active, and have the same token for an organization.
     * @dev Reverts if the pricing configuration is not valid to be used in a checkout.
     * @param _organizationId The organization ID to validate the pricing configuration for.
     * @param _productPassOwner The product pass owner to validate the pricing configuration for.
     * @param _pricingIds The pricing IDs to validate.
     * @param _quantities The quantities to validate. Must be 0 unless the pricing configuration is a TIERED_VOLUME or TIERED_GRADUATED charge style.
     * @return token The token to use for the checkout.
     * @return cycleDurations The cycle durations for the pricing configurations.
     */
    function validateCheckoutBatch(
        uint256 _organizationId,
        address _productPassOwner,
        uint256[] calldata _pricingIds,
        uint256[] calldata _quantities
    ) external view returns (address token, uint256[] memory cycleDurations);

    /**
     * @notice Validate the pricing configurations exist for an organization.
     * @dev Reverts if any of the pricing configurations do not exist for the organization.
     * @param _organizationId The organization ID to validate the pricing configurations for.
     * @param _pricingIds The pricing IDs to validate.
     */
    function validateOrgPricing(
        uint256 _organizationId,
        uint256[] calldata _pricingIds
    ) external view;

    /**
     * Get Pricing Details
     */

    /**
     * @notice Get the total number of pricing configurations that have been created.
     * @return The total number of pricing configurations.
     */
    function pricingSupply() external view returns (uint256);

    /**
     * @notice Get the pricing details for a pricing configuration.
     * @dev Reverts if the pricing configuration does not exist.
     * @param pricingId The pricing ID to get the pricing details for.
     * @return pricing The pricing details for the pricing configuration.
     */
    function getPricing(
        uint256 pricingId
    ) external view returns (PricingUtils.Pricing memory);

    /**
     * @notice Get the pricing details for a batch of pricing configurations.
     * @param _pricingIds The pricing IDs to get the pricing details for.
     * @return _pricing The pricing details for the pricing configurations.
     */
    function getPricingBatch(
        uint256[] memory _pricingIds
    ) external view returns (PricingUtils.Pricing[] memory _pricing);

    /**
     * @notice Get all pricing IDs for an organization.
     * @param organizationId The organization ID to get the pricing IDs for.
     * @return pricingIds The pricing IDs for the organization.
     */
    function getOrgPricingIds(
        uint256 organizationId
    ) external view returns (uint256[] memory);

    /**
     * @notice Get all pricing details for an organization.
     * @param organizationId The organization ID to get the pricing details for.
     * @return pricingIds The pricing IDs for the organization.
     * @return pricing The pricing details for the pricing configurations.
     */
    function getOrgPricing(
        uint256 organizationId
    ) external view returns (uint256[] memory, PricingUtils.Pricing[] memory);

    /**
     * Restricted Access
     */

    /**
     * @notice Get the restricted access for a pricing configuration.
     * @param pricingId The pricing ID to get the restricted access for.
     * @param productPassOwner The product pass owner to get the restricted access for.
     * @return isRestricted True if the address has restricted access, false otherwise.
     */
    function restrictedAccess(
        uint256 pricingId,
        address productPassOwner
    ) external view returns (bool);

    /**
     * @notice Grant access to a pricing configuration.
     * @param pricingId The pricing ID to grant access to.
     * @param productPassOwners The product pass owners to grant access to.
     * @param isRestricted True if the address has access, false otherwise.
     */
    function setRestrictedAccess(
        uint256 pricingId,
        address[] calldata productPassOwners,
        bool[] calldata isRestricted
    ) external;

    /**
     * Pricing Creation
     */

    /**
     * @notice Emitted when a pricing configuration is created.
     * @param organizationId The organization ID that the pricing configuration belongs to.
     * @param pricingId The pricing ID that was created.
     */
    event PricingCreated(
        uint256 indexed organizationId,
        uint256 indexed pricingId
    );

    /**
     * @notice Parameters for creating a one time pricing configuration.
     * @param organizationId The organization ID that the pricing configuration belongs to.
     * @param flatPrice The price for the one time purchase.
     * @param token The token to use for the one time purchase. address(0) to use the native token.
     * @param isRestricted Whether the pricing configuration is restricted to only addresses with restricted access.
     */
    struct CreateOneTimeParams {
        uint256 organizationId;
        uint256 flatPrice;
        address token;
        bool isRestricted;
    }

    /**
     * @notice Create a one time pricing configuration.
     * @param params The parameters for the one time pricing configuration.
     */
    function createOneTimePricing(CreateOneTimeParams calldata params) external;

    /**
     * @notice Parameters for creating a flat rate recurring subscription pricing configuration.
     * @param organizationId The organization ID that the pricing configuration belongs to.
     * @param flatPrice The price for the subscription.
     * @param token The token to use for the subscription. address(0) to use the native token.
     * @param chargeFrequency The frequency of the subscription that it is charged.
     * @param isRestricted Whether the pricing configuration is restricted to only addresses with restricted access.
     */
    struct CreateFlatRateSubscriptionParams {
        uint256 organizationId;
        uint256 flatPrice;
        address token;
        PricingUtils.ChargeFrequency chargeFrequency;
        bool isRestricted;
    }

    /**
     * @notice Create a flat rate recurring subscription pricing configuration.
     * @param params The parameters for the flat rate recurring subscription pricing configuration.
     */
    function createFlatRateSubscriptionPricing(
        CreateFlatRateSubscriptionParams calldata params
    ) external;

    /**
     * @notice Parameters for creating a tiered subscription pricing configuration.
     * @param organizationId The organization ID that the pricing configuration belongs to.
     * @param token The token to use for the subscription. address(0) to use the native token.
     * @param chargeFrequency The frequency of the subscription that it is charged.
     * @param tiers The tiers of the subscription.
     * @param isVolume Whether the tiers are volume tiers or graduated tiers.
     * @param isRestricted Whether the pricing configuration is restricted to only addresses with restricted access.
     */
    struct CreateTieredSubscriptionParams {
        uint256 organizationId;
        address token;
        PricingUtils.ChargeFrequency chargeFrequency;
        PricingUtils.PricingTier[] tiers;
        bool isVolume;
        bool isRestricted;
    }

    /**
     * @notice Create a tiered subscription pricing configuration.
     * @param params The parameters for the tiered subscription pricing configuration.
     */
    function createTieredSubscriptionPricing(
        CreateTieredSubscriptionParams calldata params
    ) external;

    /**
     * @notice Parameters for creating a usage based subscription pricing configuration.
     * @dev The usage meter ID is the ID of the usage meter that the subscription is based on.
     * @param organizationId The organization ID that the pricing configuration belongs to.
     * @param token The token to use for the subscription. address(0) to use the native token.
     * @param chargeFrequency The frequency of the subscription that it is charged.
     * @param tiers The tiers of the subscription.
     * @param usageMeterId The usage meter ID that the subscription is based on.
     * @param isVolume Whether the tiers are volume tiers or graduated tiers.
     * @param isRestricted Whether the pricing configuration is restricted to only addresses with restricted access.
     */
    struct CreateUsageBasedSubscriptionParams {
        uint256 organizationId;
        address token;
        PricingUtils.ChargeFrequency chargeFrequency;
        PricingUtils.PricingTier[] tiers;
        uint256 usageMeterId;
        bool isVolume;
        bool isRestricted;
    }

    /**
     * @notice Create a usage based subscription pricing configuration.
     * @param params The parameters for the usage based subscription pricing configuration.
     */
    function createUsageBasedSubscriptionPricing(
        CreateUsageBasedSubscriptionParams calldata params
    ) external;

    /**
     * Pricing Updates
     */

    /**
     * @notice Emitted when a pricing configuration is updated.
     * @param organizationId The organization ID that the pricing configuration belongs to.
     * @param pricingId The pricing ID that was updated.
     */
    event PricingUpdated(
        uint256 indexed organizationId,
        uint256 indexed pricingId
    );

    /**
     * @notice Emitted when a pricing status is changed.
     * @param organizationId The organization ID that the pricing configuration belongs to.
     * @param pricingId The pricing ID that was updated.
     * @param isActive The new status of the pricing configuration.
     */
    event PricingStatusChanged(
        uint256 indexed organizationId,
        uint256 indexed pricingId,
        bool isActive
    );

    /**
     * @notice Emitted when restricted access is granted or revoked to a pricing configuration.
     * @param organizationId The organization ID that the pricing configuration belongs to.
     * @param pricingId The pricing ID that the restricted access was granted to.
     * @param productPassOwner The product pass owner that the restricted access was granted to.
     * @param isRestricted True if the address has restricted access, false otherwise.
     */
    event RestrictedAccessGranted(
        uint256 indexed organizationId,
        uint256 indexed pricingId,
        address indexed productPassOwner,
        bool isRestricted
    );

    /**
     * @notice Set the tiers for TIERED or USAGE_BASED pricing configuration.
     * @dev Reverts if the pricing configuration is not a TIERED or USAGE_BASED charge style.
     * @param pricingId The pricing ID that the tiers are being set for.
     * @param tiers The tiers to set for the pricing configuration.
     */
    function setPricingTiers(
        uint256 pricingId,
        PricingUtils.PricingTier[] calldata tiers
    ) external;

    /**
     * @notice Reverts when no tiers are found during validation.
     */
    error NoTiersFound();

    /**
     * @notice Reverts when the lower bound is not 1 for a volume tier.
     */
    error VolumeLowerBoundMustBeOne();

    /**
     * @notice Reverts when the lower bound is not 0 for a graduated tier.
     */
    error GraduatedLowerBoundMustBeZero();

    /**
     * @notice Reverts when the lower bound is not one greater than the previous upper bound.
     */
    error LowerBoundMustBeOneGreaterThanPreviousUpperBound();

    /**
     * @notice Reverts when the last tier upper bound is not 0 to represent infinity.
     */
    error LastTierUpperBoundMustBeZeroToRepresentInfinity();

    /**
     * @notice Reverts when the lower bound is greater than the upper bound.
     */
    error LowerBoundGreaterThanUpperBound();

    /**
     * @notice Reverts when the charge style is not valid.
     */
    error InvalidChargeStyle();

    /**
     * @notice Validate the tiers for a pricing configuration.
     * @dev Reverts if the tiers are not valid.
     * @param tiers The tiers to validate.
     * @param chargeStyle The charge style of the pricing configuration.
     */
    function validateTiers(
        PricingUtils.PricingTier[] calldata tiers,
        PricingUtils.ChargeStyle chargeStyle
    ) external pure;

    /**
     * @notice Set the token for a pricing configuration.
     * @param pricingId The pricing ID that the token is being set for.
     * @param token The token to set for the pricing configuration. address(0) to switch back to native token.
     */
    function setPricingToken(uint256 pricingId, address token) external;

    /**
     * @notice Set the flat price for a pricing configuration.
     * @dev Reverts if the pricing configuration is not a ONE_TIME or FLAT_RATE charge style.
     * @param pricingId The pricing ID that the flat price is being set for.
     * @param flatPrice The flat price to set for the pricing configuration.
     */
    function setPricingFlatPrice(uint256 pricingId, uint256 flatPrice) external;

    /**
     * @notice Set the usage meter ID for a pricing configuration.
     * @dev Reverts if the pricing configuration is not a USAGE_BASED_VOLUME or USAGE_BASED_GRADUATED charge style.
     * @param pricingId The pricing ID that the usage meter ID is being set for.
     * @param usageMeterId The usage meter ID to set for the pricing configuration.
     */
    function setPricingUsageMeterId(
        uint256 pricingId,
        uint256 usageMeterId
    ) external;

    /**
     * @notice Set the active status for a pricing configuration.
     * @dev Pricing must be active to be used in a checkout.
     * @param pricingId The pricing ID that the active status is being set for.
     * @param isActive The new active status of the pricing configuration.
     */
    function setPricingActive(uint256 pricingId, bool isActive) external;

    /**
     * @notice Set the restricted status for a pricing configuration.
     * @dev If pricing is restricted, then only addresses with restricted access can purchase using pricing configuration.
     * @param pricingId The pricing ID that the restricted status is being set for.
     * @param isRestricted True if the pricing configuration is restricted, false otherwise.
     */
    function setPricingRestricted(
        uint256 pricingId,
        bool isRestricted
    ) external;

    /**
     * Pricing Cycle Duration
     */

    /**
     * @notice Get the cycle duration for a pricing configuration.
     * @param pricingId The pricing ID to get the cycle duration for.
     * @return cycleDuration The cycle duration for the pricing configuration.
     */
    function getCycleDuration(
        uint256 pricingId
    ) external view returns (uint256);

    /**
     * @notice Get the cycle duration for a batch of pricing configurations.
     * @param _pricingIds The pricing IDs to get the cycle duration for.
     * @return cycleDurations The cycle durations for the pricing configurations.
     */
    function getCycleDurationBatch(
        uint256[] calldata _pricingIds
    ) external view returns (uint256[] memory cycleDurations);

    /**
     * @notice Get the cycle duration for a charge frequency.
     * @param chargeFrequency The charge frequency to get the cycle duration for.
     * @return cycleDuration The cycle duration for the charge frequency.
     */
    function getChargeFrequencyCycleDuration(
        PricingUtils.ChargeFrequency chargeFrequency
    ) external pure returns (uint256);
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

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

interface IProductRegistry {
    /**
     * @notice ProductInfo is the information about the product.
     * @param orgId The organization ID that the product belongs to.
     * @param name The name of the product.
     * @param description The description of the product.
     * @param imageUrl An image URL for the product.
     * @param externalUrl An external URL of the product. (e.g. a link to a product page on a website)
     * @param isTransferable True if the product is transferable, else False and the product is soulbound to the NFT owner.
     * @param isActive If true, then the product is purchasable, else it is not available for purchase.
     */
    struct Product {
        uint256 orgId;
        string name;
        string description;
        string imageUrl;
        string externalUrl;
        bool isTransferable;
        bool isActive;
    }

    /**
     * @notice CreateProductParams are the parameters for creating a new product.
     * @param orgId The organization ID that the product belongs to.
     * @param name The name of the product.
     * @param description The description of the product.
     * @param imageUrl An image URL for the product. (optional)
     * @param externalUrl An external URL of the product. (e.g. a link to a product page on a website) (optional)
     * @param isTransferable True if the product is transferable, else False and the product is soulbound to the NFT owner.
     */
    struct CreateProductParams {
        uint256 orgId;
        string name;
        string description;
        string imageUrl;
        string externalUrl;
        bool isTransferable;
    }

    /**
     * @notice Get the total number of products that have been created.
     * @return The total number of products.
     */
    function totalProductSupply() external view returns (uint256);

    /**
     * @notice ProductNotFoundForOrganization is an error that is reverted when a product is not found for an organization.
     */
    error ProductNotFoundForOrganization(
        uint256 organizationId,
        uint256 productId
    );

    /**
     * @notice PricingNotLinkedToProduct is an error that is reverted when a pricing is not linked to a product.
     */
    error PricingNotLinkedToProduct(uint256 productId, uint256 pricingId);

    /**
     * @notice ProductIsNotActive is an error that is reverted when a product is not active.
     */
    error ProductIsNotActive(uint256 productId);

    /**
     * @notice Check if a product can be purchased.
     * @dev Will revert if the product cannot be purchased.
     * @param organizationId The organization ID that the product belongs to.
     * @param productId The product ID to check if it can be purchased.
     * @param pricingId The pricing ID to check if it can be used with the product purchase.
     */
    function canPurchaseProduct(
        uint256 organizationId,
        uint256 productId,
        uint256 pricingId
    ) external view;

    /**
     * @notice Batch check if multiple products can be purchased.
     * @dev Will revert if any of the products cannot be purchased.
     * @param _organizationId The organization ID that the products belong to.
     * @param _productIds The product IDs to check if they can be purchased.
     * @param _pricingIds The pricing IDs to check if they can be used with the product purchases.
     */
    function canPurchaseProducts(
        uint256 _organizationId,
        uint256[] calldata _productIds,
        uint256[] calldata _pricingIds
    ) external view;

    /**
     * Get Product Details
     */

    /**
     * @notice Get the product info for a product.
     * @param productId The product ID to get the info for.
     * @return productInfo The info for the product.
     */
    function getProduct(
        uint256 productId
    ) external view returns (Product memory);

    /**
     * @notice Get the product info for a batch of products.
     * @param _productIds The product IDs to get the info for.
     * @return _products The info for the products.
     */
    function getProductsBatch(
        uint256[] memory _productIds
    ) external view returns (Product[] memory _products);

    /**
     * @notice Get the names for a batch of products.
     * @param _productIds The product IDs to get the names for.
     * @return _productNames The names for the products.
     */
    function getProductNames(
        uint256[] memory _productIds
    ) external view returns (string[] memory _productNames);

    /**
     * @notice Get the product IDs for an organization.
     * @param _organizationId The organization ID to get the product IDs for.
     * @return productIds The product IDs that belong to the organization.
     */
    function getOrgProductIds(
        uint256 _organizationId
    ) external view returns (uint256[] memory);

    /**
     * @notice Get the product info for all products for an organization.
     * @param _organizationId The organization ID to get the product info for.
     * @return productIds The product IDs that belong to the organization.
     * @return products The info for the products.
     */
    function getOrgProducts(
        uint256 _organizationId
    ) external view returns (uint256[] memory, Product[] memory);

    /**
     * @notice Check if a product belongs to an organization.
     * @param organizationId The organization ID that the product belongs to.
     * @param productId The product ID to check if it belongs to the organization.
     * @return isOrgProduct True if the product belongs to the organization, else False.
     */
    function isOrgProduct(
        uint256 organizationId,
        uint256 productId
    ) external view returns (bool);

    /**
     * Product Creation
     */

    /**
     * @notice ValueCannotBeEmpty is an error that is thrown when a value is found to be empty during validation.
     */
    error ValueCannotBeEmpty();

    /**
     * @notice ValueTooLong is an error that is thrown when a value is found to be too long during validation.
     */
    error ValueTooLong();

    /**
     * @notice ProductCreated is an event that is emitted when a product is created.
     * @param organizationId The organization ID that the product belongs to.
     * @param productId The product ID that was created.
     */
    event ProductCreated(
        uint256 indexed organizationId,
        uint256 indexed productId
    );

    /**
     * @notice Create a new product for an organization.
     * @dev Will revert if not an org admin.
     * @param params The parameters for creating a new product.
     */
    function createProduct(CreateProductParams calldata params) external;

    /**
     * Product Management
     */

    /**
     * @notice ProductUpdated is an event that is emitted when a product is updated.
     * @param organizationId The organization ID that the product belongs to.
     * @param productId The product ID that was updated.
     */
    event ProductUpdated(
        uint256 indexed organizationId,
        uint256 indexed productId
    );

    /**
     * @notice ProductStatusChanged is an event that is emitted when a product's status is changed.
     * @param organizationId The organization ID that the product belongs to.
     * @param productId The product ID that was updated.
     * @param isActive The new status of the product.
     */
    event ProductStatusChanged(
        uint256 indexed organizationId,
        uint256 indexed productId,
        bool isActive
    );

    /**
     * @notice Set a new name for the product.
     * @dev Will revert if not an org admin or product does not exist.
     * @param productId The product ID to set the name for.
     * @param name The new name for the product.
     */
    function setProductName(uint256 productId, string calldata name) external;

    /**
     * @notice Set a new description of a product.
     * @dev Will revert if not an org admin or product does not exist.
     * @param productId The product ID to set the description for.
     * @param description The new description for the product.
     */
    function setProductDescription(
        uint256 productId,
        string calldata description
    ) external;

    /**
     * @notice Set a new image URL of a product.
     * @dev Will revert if not an org admin or product does not exist.
     * @param productId The product ID to set the image URL for.
     * @param imageUrl The new image URL for the product. Can be empty.
     */
    function setProductImageUrl(
        uint256 productId,
        string calldata imageUrl
    ) external;

    /**
     * @notice Set a new external URL of a product.
     * @dev Will revert if not an org admin or product does not exist.
     * @param productId The product ID to set the external URL for.
     * @param externalUrl The new external URL for the product. Can be empty.
     */
    function setProductExternalUrl(
        uint256 productId,
        string calldata externalUrl
    ) external;

    /**
     * @notice Set a new transferable status for a product.
     * @dev Will revert if not an org admin or product does not exist.
     * @param productId The product ID to set the transferable status for.
     * @param _isTransferable The new transferable status for the product.
     */
    function setProductTransferable(
        uint256 productId,
        bool _isTransferable
    ) external;

    /**
     * @notice Set a new active status for a product. True if the product can be purchased during new checkouts, else it is not available for purchase.
     * @dev Will revert if not an org admin or product does not exist.
     * @param productId The product ID to set the active status for.
     * @param _isActive The new active status for the product.
     */
    function setProductActive(uint256 productId, bool _isActive) external;

    /**
     * Link Products with Pricing
     */

    /**
     * @notice ProductPricingLinkUpdate is an event that is emitted when a product's pricing is linked or unlinked.
     * @param organizationId The organization ID that the product belongs to.
     * @param productId The product ID that was updated.
     * @param pricingId The pricing ID that was linked or unlinked.
     * @param isLinked True if the pricing is linked, else False.
     */
    event ProductPricingLinkUpdate(
        uint256 indexed organizationId,
        uint256 indexed productId,
        uint256 pricingId,
        bool isLinked
    );

    /**
     * @notice Link a pricing to a product. When a pricing is linked to a product, it can be used in checkouts for the product.
     * @dev Will revert if not an org admin, product does not exist, or pricing is not authorized.
     * @param productId The product ID to link the pricing to.
     * @param pricingIds The pricing IDs to link to the product.
     */
    function linkPricing(
        uint256 productId,
        uint256[] calldata pricingIds
    ) external;

    /**
     * @notice Unlink a pricing from a product.
     * @dev Will revert if not an org admin, product does not exist, or pricing is not linked to the product.
     * NOTE: Even if a pricing is unlinked from a product, subscription renewals using the product and pricing model will still continue to renew.
     * @param productId The product ID to unlink the pricing from.
     * @param pricingIds The pricing IDs to unlink from the product.
     */
    function unlinkPricing(
        uint256 productId,
        uint256[] calldata pricingIds
    ) external;

    /**
     * @notice Get the pricing IDs linked to a product.
     * @param productId The product ID to get the pricing IDs for.
     * @return pricingIds The pricing IDs that are linked to the product.
     */
    function getProductPricingIds(
        uint256 productId
    ) external view returns (uint256[] memory);

    /**
     * @notice Get all the pricing options for a product.
     * @param productId The product ID to get the pricing options for.
     * @return pricingIds The pricing IDs that are linked to the product.
     * @return pricingOptions The pricing options for the product.
     */
    function getProductPricing(
        uint256 productId
    ) external view returns (uint256[] memory, PricingUtils.Pricing[] memory);

    /**
     * @notice Get all the pricing options for a batch of products.
     * @dev This will be expensive to call and should be used with view only.
     * @param _productIds The product IDs to get the pricing options for.
     * @return pricingIds The pricing IDs that are linked to the products.
     * @return pricingOptions The pricing options for the products.
     */
    function getProductPricingBatch(
        uint256[] memory _productIds
    )
        external
        view
        returns (
            uint256[][] memory pricingIds,
            PricingUtils.Pricing[][] memory pricingOptions
        );
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

interface IPurchaseRegistry {
    /**
     * @notice Get the organization ID that the pass token belongs to.
     * @param tokenId The Product Pass Token ID to get the organization ID for.
     * @return The organization ID for the pass.
     */
    function passOrganization(uint256 tokenId) external view returns (uint256);

    /**
     * @notice Get the the count for the number of products purchased.
     * @param productId The product ID to get the supply for.
     * @return The supply of the product across all passes.
     */
    function productSupply(uint256 productId) external view returns (uint256);

    /**
     * @notice Get the max supply for a product. Used to create scarcity.
     * @param productId The product ID to get the max supply for.
     * @return The max supply for the product. 0 if no limit.
     */
    function productMaxSupply(
        uint256 productId
    ) external view returns (uint256);

    /**
     * @notice Get the total number of products sold for an organization.
     * @param organizationId The organization ID to get the total products sold for.
     * @return The total products sold across all passes for the organization.
     */
    function totalProductsSold(
        uint256 organizationId
    ) external view returns (uint256);

    /**
     * @notice Get the total number of product passes minted for an organization.
     * @param organizationId The organization ID to get the total pass mints for.
     * @return The total pass mints for the organization.
     */
    function totalPassMints(
        uint256 organizationId
    ) external view returns (uint256);

    /**
     * @notice Get the product IDs that have been purchased for a pass.
     * @param tokenId The Product Pass Token ID to get the product IDs for.
     * @return The product IDs for the pass.
     */
    function getPassProductIds(
        uint256 tokenId
    ) external view returns (uint256[] memory);

    /**
     * @notice Check if a pass has purchased a list of products.
     * @param tokenId The Product Pass Token ID to check.
     * @param productIds The product IDs to check.
     * @return True if the pass has purchased all the products, false otherwise.
     */
    function hasPassPurchasedProducts(
        uint256 tokenId,
        uint256[] calldata productIds
    ) external view returns (bool);

    /**
     * @notice Get the number of mints for an organization by a single wallet.
     * @param organizationId The organization ID that the pass belongs to.
     * @param purchaser The wallet that is purchasing the products.
     * @return The number of mints by the wallet for the organization.
     */
    function passMintCount(
        uint256 organizationId,
        address purchaser
    ) external view returns (uint256);

    /**
     * @notice Get the max mints for an organization.
     * @param organizationId The organization ID to get the max mints for.
     * @return The max amount of product passes that can be minted by a single wallet for the organization.
     */
    function maxMints(uint256 organizationId) external view returns (uint256);

    /**
     * @notice Get the whitelist status for an organization.
     * @param organizationId The organization ID to get the whitelist status for.
     * @return True if the organization is whitelist only, false otherwise.
     */
    function isWhitelist(uint256 organizationId) external view returns (bool);

    /**
     * @notice Get the mint closed status for an organization.
     * @dev When the mint is closed, no passes can be purchased for the organization regardless of whitelist status.
     * @param organizationId The organization ID to get the mint closed status for.
     * @return True if the mint is closed, false otherwise.
     */
    function isMintClosed(uint256 organizationId) external view returns (bool);

    /**
     * Record Purchase
     */

    /**
     * @notice Revert for when a product is already added to the pass. Prevent duplicate purchases.
     */
    error ProductAlreadyAdded();

    /**
     * @notice Revert for when the max supply is reached.
     */
    error MaxSupplyReached();

    /**
     * @notice Revert for when the organization is invalid for the pass.
     */
    error InvalidOrganization();

    /**
     * @notice Revert for when the max mints are reached.
     */
    error MaxMintsReached();

    /**
     * @notice Revert for when the address is not whitelisted when attempting to purchase a pass in a whitelist only organization.
     */
    error AddressNotWhitelisted();

    /**
     * @notice Revert for when the mint is closed and no passes can be minted or additional products can be added to the pass.
     */
    error MintClosed();

    /**
     * @notice Revert for when the gifting is disabled for an organization.
     */
    error GiftingIsDisabled(uint256 organizationId);

    /**
     * @notice Record a product purchase and link the products to the pass.
     * @dev Only the purchase manager can record a product purchase.
     * @param _organizationId The organization ID that the product belongs to.
     * @param _passId The Product Pass ID to be used in the purchase.
     * @param _passOwner The owner of the pass.
     * @param _purchaser The wallet that is purchasing the products.
     * @param _productIds The product IDs to be used in the purchase.
     * @param _pricingIds The pricing IDs to be used in the purchase.
     */
    function recordProductPurchase(
        uint256 _organizationId,
        uint256 _passId,
        address _passOwner,
        address _purchaser,
        uint256[] calldata _productIds,
        uint256[] calldata _pricingIds
    ) external;

    /**
     * Max Supply
     */

    /**
     * @notice Emitted for when the max supply is updated.
     * @param organizationId The organization ID that the product belongs to.
     * @param productId The product ID to set the max supply for.
     * @param maxSupply The new max supply for the product. 0 if no limit.
     */
    event ProductMaxSupplyUpdated(
        uint256 indexed organizationId,
        uint256 indexed productId,
        uint256 maxSupply
    );

    /**
     * @notice Set a new max supply for a product.
     * @dev Will revert if not an org admin or product does not exist or max supply is less than current supply.
     * @param organizationId The organization ID that the product belongs to.
     * @param productId The product ID to set the max supply for.
     * @param _maxSupply The new max supply for the product. 0 if no limit.
     */
    function setProductMaxSupply(
        uint256 organizationId,
        uint256 productId,
        uint256 _maxSupply
    ) external;

    /**
     * Max Mints
     */

    /**
     * @notice Emitted when max mints are updated for an organization.
     * @param organizationId The organization ID to update the max mints for.
     * @param maxMints The new max mints for the organization. 0 if no limit.
     */
    event MaxMintsUpdated(uint256 indexed organizationId, uint256 maxMints);

    /**
     * @notice Set a new max mint amount for an organization.
     * @dev NOTE: When lowering the limit, it will not affect existing wallets that have already minted.
     * @param organizationId The organization ID to set the max mints for.
     * @param _maxMints The new max mints for the organization. 0 if no limit.
     */
    function setMaxMints(uint256 organizationId, uint256 _maxMints) external;

    /**
     * Whitelist
     */

    /**
     * @notice Emitted when the whitelist status for an organization is updated.
     * @param organizationId The organization ID to update the whitelist for.
     * @param isWhitelist True if the organization is whitelist only, false otherwise.
     */
    event WhitelistStatusChanged(
        uint256 indexed organizationId,
        bool isWhitelist
    );

    /**
     * @notice Set a new whitelist status for an organization.
     * @param organizationId The organization ID to set the whitelist for.
     * @param _isWhitelist True if the organization is whitelist only, false otherwise.
     */
    function setWhitelist(uint256 organizationId, bool _isWhitelist) external;

    /**
     * @notice Emitted when the whitelist is updated for an address.
     * @param organizationId The organization ID to update the whitelist for.
     * @param passOwner The address to update the whitelist for.
     * @param isWhitelisted The new whitelist status for the address.
     */
    event WhitelistPassOwnerUpdated(
        uint256 indexed organizationId,
        address indexed passOwner,
        bool isWhitelisted
    );

    /**
     * @notice Whitelist addresses for an organization.
     * @dev When an org level whitelist is enabled, only whitelisted addresses can purchase passes.
     * @param organizationId The organization ID to update the whitelist for.
     * @param _addresses The addresses to update the whitelist for.
     * @param _isWhitelisted The new whitelist status for the addresses.
     */
    function whitelistPassOwners(
        uint256 organizationId,
        address[] calldata _addresses,
        bool[] calldata _isWhitelisted
    ) external;

    /**
     * @notice Get the whitelist status for an organization and a purchaser.
     * @param orgId The organization ID to get the whitelist status for.
     * @param purchaser The purchaser to get the whitelist status for.
     * @return True if the purchaser is whitelisted, false otherwise.
     */
    function whitelisted(
        uint256 orgId,
        address purchaser
    ) external view returns (bool);

    /**
     * Mint Closed
     */

    /**
     * @notice Emitted when the mint closed status for an organization is updated.
     * @param organizationId The organization ID to update the mint closed status for.
     * @param isMintClosed True if the mint is closed, false otherwise.
     */
    event MintClosedStatusChanged(
        uint256 indexed organizationId,
        bool isMintClosed
    );

    /**
     * @notice Set a new mint closed status for an organization.
     * @param organizationId The organization ID to set the mint closed status for.
     * @param _isMintClosed True if the mint is closed, false otherwise.
     */
    function setMintClosed(uint256 organizationId, bool _isMintClosed) external;

    /**
     * Gifting
     */

    /**
     * @notice Emitted when the gifting status for an organization is updated.
     * @param organizationId The organization ID to update the gifting status for.
     * @param isGifting True if the organization allows product passes to be gifted to other addresses, false otherwise.
     */
    event GiftingStatusChanged(uint256 indexed organizationId, bool isGifting);

    /**
     * @notice Get the gifting status for an organization.
     * @param orgId The organization ID to get the gifting status for.
     * @return True if the organization allows product passes to be gifted to other addresses, false otherwise.
     */
    function isGiftingEnabled(uint256 orgId) external view returns (bool);

    /**
     * @notice Set a new gifting status for an organization.
     * @param organizationId The organization ID to set the gifting status for.
     * @param _isGifting True if the organization allows product passes to be gifted to other addresses, false otherwise.
     */
    function setGiftingEnabled(
        uint256 organizationId,
        bool _isGifting
    ) external;
}

File 37 of 42 : IDynamicPriceRouter.sol
// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

interface IDynamicPriceRouter {
    /**
     * @notice Get the name of the underlying price router.
     * @return The name of the underlying price router. i.e. "uniswap-v2" or "uniswap-v3"
     */
    function ROUTER_NAME() external view returns (string memory);
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

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

/**
 * @title IUniswapV2DynamicPriceRouter
 * @notice Interface for a dynamic price router that uses Uniswap V2.
 */
interface IUniswapV2DynamicPriceRouter is IDynamicPriceRouter {
    /**
     * @notice Get the direct swap price for the final token in the given path with Uniswap fees included.
     * @param amountIn The amount of token to convert.
     * @param path The path to use for the conversion.
     * @return The amount of token at the end of the path received.
     */
    function getPrice(
        uint256 amountIn,
        address[] calldata path
    ) external view returns (uint256);

    /**
     * @notice Get the direct swap price for the final token in the given path with Uniswap fees excluded.
     * @dev We do a best approximation of the price without fees.
     * @param amountIn The amount of token to convert.
     * @param path The path to use for the conversion.
     * @return The amount of token at the end of the path received.
     */
    function getPriceFeesRemoved(
        uint256 amountIn,
        address[] calldata path
    ) external view returns (uint256);
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

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

/**
 * @title IUniswapV3DynamicPriceRouter
 * @notice Interface for a dynamic price router that uses Uniswap V3.
 */
interface IUniswapV3DynamicPriceRouter is IDynamicPriceRouter {
    /**
     * Pricing
     */

    /**
     * @notice Get the swap price for the final token in the given path with Uniswap fees included.
     * @param amountIn The amount of token to convert.
     * @param path The path to use for the conversion.
     * @return The amount of token at the end of the path received.
     */
    function getPrice(
        uint256 amountIn,
        bytes calldata path
    ) external returns (uint256);

    /**
     * @notice Get the swap price for the final token in the given path with Uniswap fees excluded.
     * @dev We do a best approximation of the price without fees.
     * @param amountIn The amount of token to convert.
     * @param path The path to use for the conversion.
     * @param fees The fees for each pool in the path. Used to calculate the fee-free price.
     * @return The amount of token at the end of the path received.
     */
    function getPriceFeesRemoved(
        uint256 amountIn,
        bytes calldata path,
        Fee[] calldata fees
    ) external returns (uint256);

    /**
     * Fees
     */

    /**
     * @notice The Uniswap V3 fee options.
     */
    enum Fee {
        LOWEST_001, // 0.01%
        LOW_005, // 0.05%
        MEDIUM_03, // 0.3%
        HIGH_1 // 1%
    }

    /**
     * @notice Get the Uniswap V3 fee for a given fee option.
     * @param fee The fee option.
     * @return The fee as a percentage of the amount out.
     * @dev 0.01% = 100, 0.05% = 500, 0.3% = 3000, 1% = 10000.
     */
    function getFee(Fee fee) external pure returns (uint256);
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

/**
 * @title IDynamicERC20
 * @notice An interface for a DynamicERC20 contract allowing for targeted pricing against a quote token.
 */
interface IDynamicERC20 {
    /**
     * @notice Get the name of the dynamic price router
     * @return The name of the dynamic price router
     */
    function routerName() external view returns (string memory);

    /**
     * @notice Get the address of the dynamic price router
     * @return The address of the dynamic price router
     */
    function dynamicPriceRouter() external view returns (address);

    /**
     * @notice The ERC20 token used to charge for payment
     * @return The contract address of the base token
     */
    function baseToken() external view returns (address);

    /**
     * @dev The ERC20 token used for price targeting
     * @return The contract address of the quote token
     */
    function quoteToken() external view returns (address);

    /**
     * @notice Get the path used to convert the base token to the quote token
     * @return The path used to pass through the dex
     */
    function getBaseToQuotePath() external view returns (address[] memory);

    /**
     * @notice Get the path used to convert the quote token to the base token
     * @return The path used to pass through the dex
     */
    function getQuoteToBasePath() external view returns (address[] memory);

    /**
     * @notice Get the current swap price of the base token in terms of the quote token
     * @return The amount of quote token per 1 base token
     */
    function getBaseTokenPrice() external returns (uint256);

    /**
     * @notice Get the balance of the base token in terms of the quote token pricing
     * @param account The address to get the balance of
     * @return The balance of the base token in quote token terms
     */
    function balanceOfQuote(address account) external returns (uint256);

    /**
     * @notice Get the allowance of the base token in terms of the quote token pricing
     * @param owner The address to get the allowance of
     * @return The allowance of the base token in quote token terms
     */
    function allowanceQuote(
        address owner,
        address spender
    ) external returns (uint256);

    /**
     * @notice Get the amount of base tokens that would be received for a given amount of quote tokens
     * @param quoteTokenAmount The amount of quote tokens to convert to base tokens
     * @return baseToken The address of the base token
     * @return baseTokenAmount The amount of base tokens that would be received
     */
    function getBaseTokenAmount(
        uint256 quoteTokenAmount
    ) external returns (address baseToken, uint256 baseTokenAmount);

    /**
     * @notice Get the amount of quote tokens that would be received for a given amount of base tokens
     * @param baseTokenAmount The amount of base tokens to convert to quote tokens
     * @return quoteToken The address of the quote token
     * @return quoteTokenAmount The amount of quote tokens that would be received
     */
    function getQuoteTokenAmount(
        uint256 baseTokenAmount
    ) external returns (address quoteToken, uint256 quoteTokenAmount);
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

interface IProductPassNFT {
    /**
     * Revert when attempting to transfer a product pass with products that are not transferable.
     */
    error ProductsNotTransferable();

    /**
     * Revert when attempting to transfer a product pass with subscriptions that are not transferable.
     */
    error SubscriptionsNotTransferable();

    /**
     * Mint a product pass to an address.
     * @dev Only the purchase manager can mint a product pass.
     * @param to The address to mint the product pass to.
     * @param tokenId The token ID of the product pass to mint.
     */
    function mint(address to, uint256 tokenId) external;
}

// SPDX-License-Identifier: BUSL-1.1

pragma solidity ^0.8.24;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Ownable2Step} from "@openzeppelin/contracts/access/Ownable2Step.sol";
import {
    IERC20Metadata
} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

import {DynamicERC20} from "../abstract/DynamicERC20.sol";
import {
    IUniswapV3DynamicPriceRouter
} from "../router/IUniswapV3DynamicPriceRouter.sol";

/*
 ____                 _            _   __  __ _       _   
|  _ \ _ __ ___   __| |_   _  ___| |_|  \/  (_)_ __ | |_ 
| |_) | '__/ _ \ / _` | | | |/ __| __| |\/| | | '_ \| __|
|  __/| | | (_) | (_| | |_| | (__| |_| |  | | | | | | |_ 
|_|   |_|  \___/ \__,_|\__,_|\___|\__|_|  |_|_|_| |_|\__|
 
 NFT based payment system to mint products onchain with one-time payments and 
 recurring permissionless subscriptions.

 https://productmint.io
*/

/**
 * @title UniswapV3DynamicERC20
 * @notice A dynamic ERC20 token that uses Uniswap V3 to get the current swap price.
 * @dev A UniswapV3DynamicERC20 cannot be minted, burned, or transferred
 *
 * Used within the ProductMint system to act as a proxy for the base token against the quote token.
 * The base token is used to charge for payment.
 * The quote token is used for price targeting.
 * A dynamic price router is used to get the current swap price at a dex such as Uniswap.
 *
 * For example, assume the base token is WETH and the quote token is USDC.
 * An organization can use the DynamicERC20 to create a pricing model that targets a price of 100 USDC.
 * Then, when a user purchases a product, 100 USDC worth of WETH will be transferred to the organization.
 */
contract UniswapV3DynamicERC20 is DynamicERC20, Ownable2Step {
    // Path used to convert the base token to the quote token
    bytes public baseToQuotePathEncoded;

    // Path used to convert the quote token to the base token
    bytes public quoteToBasePathEncoded;

    // Fees for each pool in the base to quote path
    IUniswapV3DynamicPriceRouter.Fee[] private baseToQuoteFees;

    // Fees for each pool in the quote to base path
    IUniswapV3DynamicPriceRouter.Fee[] private quoteToBaseFees;

    constructor(
        string memory _name,
        string memory _symbol,
        address _baseToken,
        address _quoteToken,
        address _dynamicPriceRouter,
        address[] memory _baseToQuotePath,
        address[] memory _quoteToBasePath,
        IUniswapV3DynamicPriceRouter.Fee[] memory _baseToQuoteFees,
        IUniswapV3DynamicPriceRouter.Fee[] memory _quoteToBaseFees
    )
        DynamicERC20(
            _name,
            _symbol,
            _baseToken,
            _quoteToken,
            _dynamicPriceRouter
        )
        Ownable(_msgSender())
    {
        _setBaseToQuotePath(_baseToQuotePath, _baseToQuoteFees);
        _setQuoteToBasePath(_quoteToBasePath, _quoteToBaseFees);
    }

    /**
     * IDynamicERC20
     */

    function getBaseTokenPrice() external returns (uint256) {
        return _getQuoteTokenAmount(10 ** IERC20Metadata(baseToken).decimals());
    }

    function balanceOfQuote(address account) external returns (uint256) {
        return _getQuoteTokenAmount(IERC20(baseToken).balanceOf(account));
    }

    function allowanceQuote(
        address owner,
        address spender
    ) external returns (uint256) {
        return
            _getQuoteTokenAmount(IERC20(baseToken).allowance(owner, spender));
    }

    function getBaseTokenAmount(
        uint256 quoteTokenAmount
    ) external returns (address, uint256) {
        return (baseToken, _getBaseTokenAmount(quoteTokenAmount));
    }

    function getQuoteTokenAmount(
        uint256 baseTokenAmount
    ) external returns (address, uint256) {
        return (quoteToken, _getQuoteTokenAmount(baseTokenAmount));
    }

    function _getBaseTokenAmount(uint256 amount) internal returns (uint256) {
        if (amount == 0) return 0;
        return
            IUniswapV3DynamicPriceRouter(dynamicPriceRouter)
                .getPriceFeesRemoved(
                    amount,
                    quoteToBasePathEncoded,
                    quoteToBaseFees
                );
    }

    function _getQuoteTokenAmount(uint256 amount) internal returns (uint256) {
        if (amount == 0) return 0;
        return
            IUniswapV3DynamicPriceRouter(dynamicPriceRouter)
                .getPriceFeesRemoved(
                    amount,
                    baseToQuotePathEncoded,
                    baseToQuoteFees
                );
    }

    /**
     * Base to quote path
     */

    function getBaseToQuoteFees()
        external
        view
        returns (IUniswapV3DynamicPriceRouter.Fee[] memory)
    {
        return baseToQuoteFees;
    }

    /**
     * @notice Emitted when the base to quote path is set
     * @param dynamicERC20 The address of the current dynamic ERC20 contract
     * @param baseToken The address of the base token
     * @param quoteToken The address of the quote token
     * @param path The path used to convert the base token to the quote token
     * @param pathEncoded The encoded path
     * @param fees The fees for each pool in the path
     */
    event UniswapV3BaseToQuotePathSet(
        address indexed dynamicERC20,
        address indexed baseToken,
        address indexed quoteToken,
        address[] path,
        bytes pathEncoded,
        IUniswapV3DynamicPriceRouter.Fee[] fees
    );

    function setBaseToQuotePath(
        address[] memory _path,
        IUniswapV3DynamicPriceRouter.Fee[] memory _fees
    ) external onlyOwner {
        _setBaseToQuotePath(_path, _fees);
    }

    function _setBaseToQuotePath(
        address[] memory _path,
        IUniswapV3DynamicPriceRouter.Fee[] memory _fees
    ) internal {
        _checkBaseToQuotePath(_path);
        _checkFees(_path, _fees);

        bytes memory pathEncoded = _encodePath(_path, _fees);
        baseToQuotePathEncoded = pathEncoded;
        baseToQuotePath = _path;
        baseToQuoteFees = _fees;

        emit UniswapV3BaseToQuotePathSet(
            address(this),
            baseToken,
            quoteToken,
            _path,
            pathEncoded,
            _fees
        );
    }

    /**
     * Quote to base path
     */

    function getQuoteToBaseFees()
        external
        view
        returns (IUniswapV3DynamicPriceRouter.Fee[] memory)
    {
        return quoteToBaseFees;
    }

    /**
     * @notice Emitted when the quote to base path is set
     * @param dynamicERC20 The address of the current dynamic ERC20 contract
     * @param baseToken The address of the base token
     * @param quoteToken The address of the quote token
     * @param path The path used to convert the quote token to the base token
     * @param pathEncoded The encoded path
     * @param fees The fees for each pool in the path
     */
    event UniswapV3QuoteToBasePathSet(
        address indexed dynamicERC20,
        address indexed baseToken,
        address indexed quoteToken,
        address[] path,
        bytes pathEncoded,
        IUniswapV3DynamicPriceRouter.Fee[] fees
    );

    function setQuoteToBasePath(
        address[] memory _path,
        IUniswapV3DynamicPriceRouter.Fee[] memory _fees
    ) external onlyOwner {
        _setQuoteToBasePath(_path, _fees);
    }

    function _setQuoteToBasePath(
        address[] memory _path,
        IUniswapV3DynamicPriceRouter.Fee[] memory _fees
    ) internal {
        _checkQuoteToBasePath(_path);
        _checkFees(_path, _fees);

        bytes memory pathEncoded = _encodePath(_path, _fees);
        quoteToBasePathEncoded = pathEncoded;
        quoteToBasePath = _path;
        quoteToBaseFees = _fees;

        emit UniswapV3QuoteToBasePathSet(
            address(this),
            baseToken,
            quoteToken,
            _path,
            pathEncoded,
            _fees
        );
    }

    /**
     * Path updates
     */

    /**
     * @dev Error when attempting to set an invalid path
     */
    error InvalidPath(address[] _path);

    function _encodePath(
        address[] memory _path,
        IUniswapV3DynamicPriceRouter.Fee[] memory _fees
    ) internal returns (bytes memory pathEncoded) {
        IUniswapV3DynamicPriceRouter _router = IUniswapV3DynamicPriceRouter(
            dynamicPriceRouter
        );

        for (uint256 i = 0; i < _path.length - 1; i++) {
            pathEncoded = abi.encodePacked(
                pathEncoded,
                _path[i],
                uint24(_router.getFee(_fees[i]))
            );
        }

        pathEncoded = abi.encodePacked(pathEncoded, _path[_path.length - 1]);

        try
            _router.getPriceFeesRemoved(
                10 ** IERC20Metadata(_path[0]).decimals(),
                pathEncoded,
                _fees
            )
        {} catch {
            revert InvalidPath(_path);
        }
    }

    function _checkFees(
        address[] memory _path,
        IUniswapV3DynamicPriceRouter.Fee[] memory _fees
    ) internal pure {
        require(
            _fees.length == _path.length - 1,
            "Fees length must match hops"
        );
    }

    /**
     * Dynamic price router updates
     */

    function setDynamicPriceRouter(
        address _dynamicPriceRouter
    ) external onlyOwner {
        _setDynamicPriceRouter(_dynamicPriceRouter);
    }

    function _setDynamicPriceRouter(
        address _dynamicPriceRouter
    ) internal override {
        require(
            IERC165(_dynamicPriceRouter).supportsInterface(
                type(IUniswapV3DynamicPriceRouter).interfaceId
            ),
            "Does not implement IUniswapV3DynamicPriceRouter"
        );

        super._setDynamicPriceRouter(_dynamicPriceRouter);
    }
}

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

Contract Security Audit

Contract ABI

API
[{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"address","name":"_baseToken","type":"address"},{"internalType":"address","name":"_quoteToken","type":"address"},{"internalType":"address","name":"_dynamicPriceRouter","type":"address"},{"internalType":"address[]","name":"_baseToQuotePath","type":"address[]"},{"internalType":"address[]","name":"_quoteToBasePath","type":"address[]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ApproveNotAllowed","type":"error"},{"inputs":[{"internalType":"address[]","name":"_path","type":"address[]"}],"name":"InvalidPath","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"TransferNotAllowed","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"dynamicERC20","type":"address"},{"indexed":true,"internalType":"address","name":"dynamicPriceRouter","type":"address"}],"name":"DynamicPriceRouterSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"dynamicERC20","type":"address"},{"indexed":true,"internalType":"address","name":"baseToken","type":"address"},{"indexed":true,"internalType":"address","name":"quoteToken","type":"address"},{"indexed":false,"internalType":"address[]","name":"baseToQuotePath","type":"address[]"}],"name":"UniswapV2BaseToQuotePathSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"dynamicERC20","type":"address"},{"indexed":true,"internalType":"address","name":"baseToken","type":"address"},{"indexed":true,"internalType":"address","name":"quoteToken","type":"address"},{"indexed":false,"internalType":"address[]","name":"quoteToBasePath","type":"address[]"}],"name":"UniswapV2QuoteToBasePathSet","type":"event"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowanceQuote","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"balanceOfQuote","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dynamicPriceRouter","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBaseToQuotePath","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"quoteTokenAmount","type":"uint256"}],"name":"getBaseTokenAmount","outputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getBaseTokenPrice","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getQuoteToBasePath","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"baseTokenAmount","type":"uint256"}],"name":"getQuoteTokenAmount","outputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"quoteToken","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"routerName","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_baseToQuotePath","type":"address[]"}],"name":"setBaseToQuotePath","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_dynamicPriceRouter","type":"address"}],"name":"setDynamicPriceRouter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"_quoteToBasePath","type":"address[]"}],"name":"setQuoteToBasePath","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]

60c06040523480156200001157600080fd5b5060405162002a1638038062002a16833981016040819052620000349162000a99565b3387878787876001600160a01b038316620000a05760405162461bcd60e51b815260206004820152602160248201527f4261736520746f6b656e2063616e6e6f74206265207a65726f206164647265736044820152607360f81b60648201526084015b60405180910390fd5b6001600160a01b038216620001035760405162461bcd60e51b815260206004820152602260248201527f51756f746520746f6b656e2063616e6e6f74206265207a65726f206164647265604482015261737360f01b606482015260840162000097565b816001600160a01b0316836001600160a01b031603620001765760405162461bcd60e51b815260206004820152602760248201527f4261736520616e642071756f746520746f6b656e2063616e6e6f74206265207460448201526668652073616d6560c81b606482015260840162000097565b600062000184868262000c1b565b50600162000193858262000c1b565b506001600160a01b03808416608052821660a052620001b28162000214565b5050506001600160a01b0383169150620001e5905057604051631e4fbdf760e01b81526000600482015260240162000097565b620001f081620002fa565b50620001fc8262000315565b62000207816200039f565b5050505050505062000f26565b6040516301ffc9a760e01b815263b130abe360e01b60048201526001600160a01b038216906301ffc9a790602401602060405180830381865afa15801562000260573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019062000286919062000ce7565b620002ec5760405162461bcd60e51b815260206004820152602f60248201527f446f6573206e6f7420696d706c656d656e742049556e6973776170563244796e60448201526e30b6b4b1a83934b1b2a937baba32b960891b606482015260840162000097565b620002f7816200041e565b50565b600680546001600160a01b0319169055620002f7816200046a565b6200032081620004bc565b6200032b8162000610565b80516200034090600290602084019062000894565b5060a0516001600160a01b03166080516001600160a01b0316306001600160a01b03167f25b4c0059a7a45c3fb2e9a613393e8a0b8339d669153d7add5c83611d89074808460405162000394919062000d59565b60405180910390a450565b620003aa8162000736565b620003b58162000610565b8051620003ca90600390602084019062000894565b5060a0516001600160a01b03166080516001600160a01b0316306001600160a01b03167f3105ebf8cfe0d8803d5b103773a21806a6c43fa57501763b771be3db158bfcd58460405162000394919062000d59565b600480546001600160a01b0319166001600160a01b03831690811790915560405130907fd208b3f2137ba34e4b2ea29e6e0558a1395796409f6b8328f2cbd5ff9159bc1590600090a350565b600580546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b6001815111620004fe5760405162461bcd60e51b81526020600482018190526024820152600080516020620029f6833981519152604482015260640162000097565b6080516001600160a01b03168160008151811062000520576200052062000d6e565b60200260200101516001600160a01b031614620005805760405162461bcd60e51b815260206004820181905260248201527f4261736520746f6b656e206d75737420626520666972737420696e2070617468604482015260640162000097565b60a0516001600160a01b031681600183516200059d919062000d9a565b81518110620005b057620005b062000d6e565b60200260200101516001600160a01b031614620002f75760405162461bcd60e51b815260206004820181905260248201527f51756f746520746f6b656e206d757374206265206c61737420696e2070617468604482015260640162000097565b60045481516001600160a01b039091169063bd3f03f99083906000906200063b576200063b62000d6e565b60200260200101516001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa15801562000681573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190620006a7919062000db6565b620006b490600a62000ed8565b836040518363ffffffff1660e01b8152600401620006d492919062000ee9565b602060405180830381865afa92505050801562000710575060408051601f3d908101601f191682019092526200070d9181019062000f0c565b60015b6200073257806040516354da4f1960e01b815260040162000097919062000d59565b5050565b6001815111620007785760405162461bcd60e51b81526020600482018190526024820152600080516020620029f6833981519152604482015260640162000097565b60a0516001600160a01b0316816000815181106200079a576200079a62000d6e565b60200260200101516001600160a01b031614620008045760405162461bcd60e51b815260206004820152602160248201527f51756f746520746f6b656e206d75737420626520666972737420696e207061746044820152600d60fb1b606482015260840162000097565b6080516001600160a01b0316816001835162000821919062000d9a565b8151811062000834576200083462000d6e565b60200260200101516001600160a01b031614620002f75760405162461bcd60e51b815260206004820152601f60248201527f4261736520746f6b656e206d757374206265206c61737420696e207061746800604482015260640162000097565b828054828255906000526020600020908101928215620008ec579160200282015b82811115620008ec57825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190620008b5565b50620008fa929150620008fe565b5090565b5b80821115620008fa5760008155600101620008ff565b634e487b7160e01b600052604160045260246000fd5b604051601f8201601f191681016001600160401b038111828210171562000956576200095662000915565b604052919050565b600082601f8301126200097057600080fd5b81516001600160401b038111156200098c576200098c62000915565b6020620009a2601f8301601f191682016200092b565b8281528582848701011115620009b757600080fd5b60005b83811015620009d7578581018301518282018401528201620009ba565b506000928101909101919091529392505050565b80516001600160a01b038116811462000a0357600080fd5b919050565b600082601f83011262000a1a57600080fd5b815160206001600160401b0382111562000a385762000a3862000915565b8160051b62000a498282016200092b565b928352848101820192828101908785111562000a6457600080fd5b83870192505b8483101562000a8e5762000a7e83620009eb565b8252918301919083019062000a6a565b979650505050505050565b600080600080600080600060e0888a03121562000ab557600080fd5b87516001600160401b038082111562000acd57600080fd5b62000adb8b838c016200095e565b985060208a015191508082111562000af257600080fd5b62000b008b838c016200095e565b975062000b1060408b01620009eb565b965062000b2060608b01620009eb565b955062000b3060808b01620009eb565b945060a08a015191508082111562000b4757600080fd5b62000b558b838c0162000a08565b935060c08a015191508082111562000b6c57600080fd5b5062000b7b8a828b0162000a08565b91505092959891949750929550565b600181811c9082168062000b9f57607f821691505b60208210810362000bc057634e487b7160e01b600052602260045260246000fd5b50919050565b601f82111562000c16576000816000526020600020601f850160051c8101602086101562000bf15750805b601f850160051c820191505b8181101562000c125782815560010162000bfd565b5050505b505050565b81516001600160401b0381111562000c375762000c3762000915565b62000c4f8162000c48845462000b8a565b8462000bc6565b602080601f83116001811462000c87576000841562000c6e5750858301515b600019600386901b1c1916600185901b17855562000c12565b600085815260208120601f198616915b8281101562000cb85788860151825594840194600190910190840162000c97565b508582101562000cd75787850151600019600388901b60f8161c191681555b5050505050600190811b01905550565b60006020828403121562000cfa57600080fd5b8151801515811462000d0b57600080fd5b9392505050565b60008151808452602080850194506020840160005b8381101562000d4e5781516001600160a01b03168752958201959082019060010162000d27565b509495945050505050565b60208152600062000d0b602083018462000d12565b634e487b7160e01b600052603260045260246000fd5b634e487b7160e01b600052601160045260246000fd5b8181038181111562000db05762000db062000d84565b92915050565b60006020828403121562000dc957600080fd5b815160ff8116811462000d0b57600080fd5b600181815b8085111562000e1c57816000190482111562000e005762000e0062000d84565b8085161562000e0e57918102915b93841c939080029062000de0565b509250929050565b60008262000e355750600162000db0565b8162000e445750600062000db0565b816001811462000e5d576002811462000e685762000e88565b600191505062000db0565b60ff84111562000e7c5762000e7c62000d84565b50506001821b62000db0565b5060208310610133831016604e8410600b841016171562000ead575081810a62000db0565b62000eb9838362000ddb565b806000190482111562000ed05762000ed062000d84565b029392505050565b600062000d0b60ff84168362000e24565b82815260406020820152600062000f04604083018462000d12565b949350505050565b60006020828403121562000f1f57600080fd5b5051919050565b60805160a051611a3362000fc36000396000818161023f015281816105f6015281816107d701528181610cde01528181610db50152818161104e01526112aa0152600081816103c001528181610552015281816106fa0152818161088b0152818161097c01528181610a1601528181610b3a01528181610bac01528181610d0801528181610ddf01528181610fb3015261134f0152611a336000f3fe608060405234801561001057600080fd5b50600436106101cf5760003560e01c806379ba509711610104578063aebab157116100a2578063dd62ed3e11610071578063dd62ed3e146103ea578063e30c3978146103fd578063e71dc5e51461040e578063f2fde38b1461042157600080fd5b8063aebab15714610395578063be41d5bc146103a8578063c55dae63146103bb578063cb27a434146103e257600080fd5b80638da5cb5b116100de5780638da5cb5b1461035b57806395d89b411461036c5780639bd230f314610374578063a9059cbb1461038757600080fd5b806379ba50971461032d5780637a19c83d146103355780638caecde51461034857600080fd5b806339ff2da2116101715780636702fcac1161014b5780636702fcac146102cb5780636bb26dba146102fd57806370a0823114610312578063715018a61461032557600080fd5b806339ff2da2146102a65780634971c79a146102ae5780634b230342146102b657600080fd5b806318160ddd116101ad57806318160ddd14610224578063217a4b701461023a57806323b872dd14610279578063313ce5671461028c57600080fd5b806301ffc9a7146101d457806306fdde03146101fc578063095ea7b314610211575b600080fd5b6101e76101e23660046114ba565b610434565b60405190151581526020015b60405180910390f35b6102046104a1565b6040516101f39190611508565b6101e761021f366004611557565b610533565b61022c61054e565b6040519081526020016101f3565b6102617f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016101f3565b6101e7610287366004611581565b6105d7565b6102946105f2565b60405160ff90911681526020016101f3565b610204610676565b61022c6106f3565b6102c96102c43660046115bd565b61078a565b005b6102de6102d9366004611632565b6107d2565b604080516001600160a01b0390931683526020830191909152016101f3565b610305610808565b6040516101f39190611690565b61022c6103203660046116a3565b610869565b6102c96108f9565b6102c961090d565b61022c6103433660046116a3565b610956565b61022c6103563660046116be565b6109e8565b6005546001600160a01b0316610261565b610204610a4c565b6102c96103823660046115bd565b610a5b565b6101e7610287366004611557565b6102c96103a33660046116a3565b610a9f565b600454610261906001600160a01b031681565b6102617f000000000000000000000000000000000000000000000000000000000000000081565b610305610ab0565b61022c6103f83660046116be565b610b10565b6006546001600160a01b0316610261565b6102de61041c366004611632565b610ba7565b6102c961042f3660046116a3565b610bd4565b60006001600160e01b03198216631f9aa5f160e31b148061046557506001600160e01b031982166336372b0760e01b145b8061048057506001600160e01b0319821663a219a02560e01b145b8061049b57506301ffc9a760e01b6001600160e01b03198316145b92915050565b6060600080546104b0906116f1565b80601f01602080910402602001604051908101604052809291908181526020018280546104dc906116f1565b80156105295780601f106104fe57610100808354040283529160200191610529565b820191906000526020600020905b81548152906001019060200180831161050c57829003601f168201915b5050505050905090565b6000604051632028747160e01b815260040160405180910390fd5b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105ae573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105d2919061172b565b905090565b6000604051638cd22d1960e01b815260040160405180910390fd5b60007f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610652573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105d29190611744565b6060600460009054906101000a90046001600160a01b03166001600160a01b0316630e05f6766040518163ffffffff1660e01b8152600401600060405180830381865afa1580156106cb573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526105d2919081019061177d565b60006105d27f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610756573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061077a9190611744565b61078590600a611924565b610c45565b610792610c89565b6107ce828280806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250610cb692505050565b5050565b6000807f00000000000000000000000000000000000000000000000000000000000000006107ff84610c45565b91509150915091565b6060600380548060200260200160405190810160405280929190818152602001828054801561052957602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610842575050505050905090565b6040516370a0823160e01b81526001600160a01b0382811660048301526000917f0000000000000000000000000000000000000000000000000000000000000000909116906370a08231906024015b602060405180830381865afa1580156108d5573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061049b919061172b565b610901610c89565b61090b6000610d74565b565b60065433906001600160a01b0316811461094a5760405163118cdaa760e01b81526001600160a01b03821660048201526024015b60405180910390fd5b61095381610d74565b50565b6040516370a0823160e01b81526001600160a01b03828116600483015260009161049b917f000000000000000000000000000000000000000000000000000000000000000016906370a08231906024015b602060405180830381865afa1580156109c4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610785919061172b565b604051636eb1769f60e11b81526001600160a01b0383811660048301528281166024830152600091610a45917f0000000000000000000000000000000000000000000000000000000000000000169063dd62ed3e906044016109a7565b9392505050565b6060600180546104b0906116f1565b610a63610c89565b6107ce828280806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250610d8d92505050565b610aa7610c89565b61095381610e40565b60606002805480602002602001604051908101604052809291908181526020018280548015610529576020028201919060005260206000209081546001600160a01b03168152600190910190602001808311610842575050505050905090565b604051636eb1769f60e11b81526001600160a01b03838116600483015282811660248301526000917f00000000000000000000000000000000000000000000000000000000000000009091169063dd62ed3e90604401602060405180830381865afa158015610b83573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a45919061172b565b6000807f00000000000000000000000000000000000000000000000000000000000000006107ff84610f1c565b610bdc610c89565b600680546001600160a01b0383166001600160a01b03199091168117909155610c0d6005546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b600081600003610c5757506000919050565b6004805460405163bd3f03f960e01b81526001600160a01b039091169163bd3f03f9916108b891869160029101611933565b6005546001600160a01b0316331461090b5760405163118cdaa760e01b8152336004820152602401610941565b610cbf81610f60565b610cc8816110f3565b8051610cdb906002906020840190611440565b507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316306001600160a01b03167f25b4c0059a7a45c3fb2e9a613393e8a0b8339d669153d7add5c83611d890748084604051610d699190611690565b60405180910390a450565b600680546001600160a01b031916905561095381611205565b610d9681611257565b610d9f816110f3565b8051610db2906003906020840190611440565b507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b03167f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316306001600160a01b03167f3105ebf8cfe0d8803d5b103773a21806a6c43fa57501763b771be3db158bfcd584604051610d699190611690565b6040516301ffc9a760e01b815263b130abe360e01b60048201526001600160a01b038216906301ffc9a790602401602060405180830381865afa158015610e8b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610eaf9190611991565b610f135760405162461bcd60e51b815260206004820152602f60248201527f446f6573206e6f7420696d706c656d656e742049556e6973776170563244796e60448201526e30b6b4b1a83934b1b2a937baba32b960891b6064820152608401610941565b610953816113f4565b600081600003610f2e57506000919050565b6004805460405163bd3f03f960e01b81526001600160a01b039091169163bd3f03f9916108b891869160039101611933565b6001815111610fb15760405162461bcd60e51b815260206004820181905260248201527f50617468206d7573742068617665206174206c65617374203220746f6b656e736044820152606401610941565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031681600081518110610fee57610fee6119b3565b60200260200101516001600160a01b03161461104c5760405162461bcd60e51b815260206004820181905260248201527f4261736520746f6b656e206d75737420626520666972737420696e20706174686044820152606401610941565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316816001835161108591906119c9565b81518110611095576110956119b3565b60200260200101516001600160a01b0316146109535760405162461bcd60e51b815260206004820181905260248201527f51756f746520746f6b656e206d757374206265206c61737420696e20706174686044820152606401610941565b60045481516001600160a01b039091169063bd3f03f990839060009061111b5761111b6119b3565b60200260200101516001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611160573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111849190611744565b61118f90600a611924565b836040518363ffffffff1660e01b81526004016111ad9291906119dc565b602060405180830381865afa9250505080156111e6575060408051601f3d908101601f191682019092526111e39181019061172b565b60015b6107ce57806040516354da4f1960e01b81526004016109419190611690565b600580546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b60018151116112a85760405162461bcd60e51b815260206004820181905260248201527f50617468206d7573742068617665206174206c65617374203220746f6b656e736044820152606401610941565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316816000815181106112e5576112e56119b3565b60200260200101516001600160a01b03161461134d5760405162461bcd60e51b815260206004820152602160248201527f51756f746520746f6b656e206d75737420626520666972737420696e207061746044820152600d60fb1b6064820152608401610941565b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316816001835161138691906119c9565b81518110611396576113966119b3565b60200260200101516001600160a01b0316146109535760405162461bcd60e51b815260206004820152601f60248201527f4261736520746f6b656e206d757374206265206c61737420696e2070617468006044820152606401610941565b600480546001600160a01b0319166001600160a01b03831690811790915560405130907fd208b3f2137ba34e4b2ea29e6e0558a1395796409f6b8328f2cbd5ff9159bc1590600090a350565b828054828255906000526020600020908101928215611495579160200282015b8281111561149557825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190611460565b506114a19291506114a5565b5090565b5b808211156114a157600081556001016114a6565b6000602082840312156114cc57600080fd5b81356001600160e01b031981168114610a4557600080fd5b60005b838110156114ff5781810151838201526020016114e7565b50506000910152565b60208152600082518060208401526115278160408501602087016114e4565b601f01601f19169190910160400192915050565b80356001600160a01b038116811461155257600080fd5b919050565b6000806040838503121561156a57600080fd5b6115738361153b565b946020939093013593505050565b60008060006060848603121561159657600080fd5b61159f8461153b565b92506115ad6020850161153b565b9150604084013590509250925092565b600080602083850312156115d057600080fd5b823567ffffffffffffffff808211156115e857600080fd5b818501915085601f8301126115fc57600080fd5b81358181111561160b57600080fd5b8660208260051b850101111561162057600080fd5b60209290920196919550909350505050565b60006020828403121561164457600080fd5b5035919050565b60008151808452602080850194506020840160005b838110156116855781516001600160a01b031687529582019590820190600101611660565b509495945050505050565b602081526000610a45602083018461164b565b6000602082840312156116b557600080fd5b610a458261153b565b600080604083850312156116d157600080fd5b6116da8361153b565b91506116e86020840161153b565b90509250929050565b600181811c9082168061170557607f821691505b60208210810361172557634e487b7160e01b600052602260045260246000fd5b50919050565b60006020828403121561173d57600080fd5b5051919050565b60006020828403121561175657600080fd5b815160ff81168114610a4557600080fd5b634e487b7160e01b600052604160045260246000fd5b60006020828403121561178f57600080fd5b815167ffffffffffffffff808211156117a757600080fd5b818401915084601f8301126117bb57600080fd5b8151818111156117cd576117cd611767565b604051601f8201601f19908116603f011681019083821181831017156117f5576117f5611767565b8160405282815287602084870101111561180e57600080fd5b61181f8360208301602088016114e4565b979650505050505050565b634e487b7160e01b600052601160045260246000fd5b600181815b8085111561187b5781600019048211156118615761186161182a565b8085161561186e57918102915b93841c9390800290611845565b509250929050565b6000826118925750600161049b565b8161189f5750600061049b565b81600181146118b557600281146118bf576118db565b600191505061049b565b60ff8411156118d0576118d061182a565b50506001821b61049b565b5060208310610133831016604e8410600b84101617156118fe575081810a61049b565b6119088383611840565b806000190482111561191c5761191c61182a565b029392505050565b6000610a4560ff841683611883565b600060408201848352602060406020850152818554808452606086019150866000526020600020935060005b818110156119845784546001600160a01b03168352600194850194928401920161195f565b5090979650505050505050565b6000602082840312156119a357600080fd5b81518015158114610a4557600080fd5b634e487b7160e01b600052603260045260246000fd5b8181038181111561049b5761049b61182a565b8281526040602082015260006119f5604083018461164b565b94935050505056fea2646970667358221220010d22d06d443982ab6747b918a4f147524563fb657637be8943b6752884d25564736f6c6343000818003350617468206d7573742068617665206174206c65617374203220746f6b656e7300000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001200000000000000000000000004200000000000000000000000000000000000006000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000017b0e768cd785fcaad2729650714fbf57e8f4ddc000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000001c556e69737761702056322044796e616d696320574554482f5553444300000000000000000000000000000000000000000000000000000000000000000000000a64574554482d555344430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000004200000000000000000000000000000000000006000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000000000002000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000004200000000000000000000000000000000000006

Deployed Bytecode

0x608060405234801561001057600080fd5b50600436106101cf5760003560e01c806379ba509711610104578063aebab157116100a2578063dd62ed3e11610071578063dd62ed3e146103ea578063e30c3978146103fd578063e71dc5e51461040e578063f2fde38b1461042157600080fd5b8063aebab15714610395578063be41d5bc146103a8578063c55dae63146103bb578063cb27a434146103e257600080fd5b80638da5cb5b116100de5780638da5cb5b1461035b57806395d89b411461036c5780639bd230f314610374578063a9059cbb1461038757600080fd5b806379ba50971461032d5780637a19c83d146103355780638caecde51461034857600080fd5b806339ff2da2116101715780636702fcac1161014b5780636702fcac146102cb5780636bb26dba146102fd57806370a0823114610312578063715018a61461032557600080fd5b806339ff2da2146102a65780634971c79a146102ae5780634b230342146102b657600080fd5b806318160ddd116101ad57806318160ddd14610224578063217a4b701461023a57806323b872dd14610279578063313ce5671461028c57600080fd5b806301ffc9a7146101d457806306fdde03146101fc578063095ea7b314610211575b600080fd5b6101e76101e23660046114ba565b610434565b60405190151581526020015b60405180910390f35b6102046104a1565b6040516101f39190611508565b6101e761021f366004611557565b610533565b61022c61054e565b6040519081526020016101f3565b6102617f000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291381565b6040516001600160a01b0390911681526020016101f3565b6101e7610287366004611581565b6105d7565b6102946105f2565b60405160ff90911681526020016101f3565b610204610676565b61022c6106f3565b6102c96102c43660046115bd565b61078a565b005b6102de6102d9366004611632565b6107d2565b604080516001600160a01b0390931683526020830191909152016101f3565b610305610808565b6040516101f39190611690565b61022c6103203660046116a3565b610869565b6102c96108f9565b6102c961090d565b61022c6103433660046116a3565b610956565b61022c6103563660046116be565b6109e8565b6005546001600160a01b0316610261565b610204610a4c565b6102c96103823660046115bd565b610a5b565b6101e7610287366004611557565b6102c96103a33660046116a3565b610a9f565b600454610261906001600160a01b031681565b6102617f000000000000000000000000420000000000000000000000000000000000000681565b610305610ab0565b61022c6103f83660046116be565b610b10565b6006546001600160a01b0316610261565b6102de61041c366004611632565b610ba7565b6102c961042f3660046116a3565b610bd4565b60006001600160e01b03198216631f9aa5f160e31b148061046557506001600160e01b031982166336372b0760e01b145b8061048057506001600160e01b0319821663a219a02560e01b145b8061049b57506301ffc9a760e01b6001600160e01b03198316145b92915050565b6060600080546104b0906116f1565b80601f01602080910402602001604051908101604052809291908181526020018280546104dc906116f1565b80156105295780601f106104fe57610100808354040283529160200191610529565b820191906000526020600020905b81548152906001019060200180831161050c57829003601f168201915b5050505050905090565b6000604051632028747160e01b815260040160405180910390fd5b60007f00000000000000000000000042000000000000000000000000000000000000066001600160a01b03166318160ddd6040518163ffffffff1660e01b8152600401602060405180830381865afa1580156105ae573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105d2919061172b565b905090565b6000604051638cd22d1960e01b815260040160405180910390fd5b60007f000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029136001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610652573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105d29190611744565b6060600460009054906101000a90046001600160a01b03166001600160a01b0316630e05f6766040518163ffffffff1660e01b8152600401600060405180830381865afa1580156106cb573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526105d2919081019061177d565b60006105d27f00000000000000000000000042000000000000000000000000000000000000066001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015610756573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061077a9190611744565b61078590600a611924565b610c45565b610792610c89565b6107ce828280806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250610cb692505050565b5050565b6000807f000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029136107ff84610c45565b91509150915091565b6060600380548060200260200160405190810160405280929190818152602001828054801561052957602002820191906000526020600020905b81546001600160a01b03168152600190910190602001808311610842575050505050905090565b6040516370a0823160e01b81526001600160a01b0382811660048301526000917f0000000000000000000000004200000000000000000000000000000000000006909116906370a08231906024015b602060405180830381865afa1580156108d5573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061049b919061172b565b610901610c89565b61090b6000610d74565b565b60065433906001600160a01b0316811461094a5760405163118cdaa760e01b81526001600160a01b03821660048201526024015b60405180910390fd5b61095381610d74565b50565b6040516370a0823160e01b81526001600160a01b03828116600483015260009161049b917f000000000000000000000000420000000000000000000000000000000000000616906370a08231906024015b602060405180830381865afa1580156109c4573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610785919061172b565b604051636eb1769f60e11b81526001600160a01b0383811660048301528281166024830152600091610a45917f0000000000000000000000004200000000000000000000000000000000000006169063dd62ed3e906044016109a7565b9392505050565b6060600180546104b0906116f1565b610a63610c89565b6107ce828280806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250610d8d92505050565b610aa7610c89565b61095381610e40565b60606002805480602002602001604051908101604052809291908181526020018280548015610529576020028201919060005260206000209081546001600160a01b03168152600190910190602001808311610842575050505050905090565b604051636eb1769f60e11b81526001600160a01b03838116600483015282811660248301526000917f00000000000000000000000042000000000000000000000000000000000000069091169063dd62ed3e90604401602060405180830381865afa158015610b83573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610a45919061172b565b6000807f00000000000000000000000042000000000000000000000000000000000000066107ff84610f1c565b610bdc610c89565b600680546001600160a01b0383166001600160a01b03199091168117909155610c0d6005546001600160a01b031690565b6001600160a01b03167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b600081600003610c5757506000919050565b6004805460405163bd3f03f960e01b81526001600160a01b039091169163bd3f03f9916108b891869160029101611933565b6005546001600160a01b0316331461090b5760405163118cdaa760e01b8152336004820152602401610941565b610cbf81610f60565b610cc8816110f3565b8051610cdb906002906020840190611440565b507f000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029136001600160a01b03167f00000000000000000000000042000000000000000000000000000000000000066001600160a01b0316306001600160a01b03167f25b4c0059a7a45c3fb2e9a613393e8a0b8339d669153d7add5c83611d890748084604051610d699190611690565b60405180910390a450565b600680546001600160a01b031916905561095381611205565b610d9681611257565b610d9f816110f3565b8051610db2906003906020840190611440565b507f000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029136001600160a01b03167f00000000000000000000000042000000000000000000000000000000000000066001600160a01b0316306001600160a01b03167f3105ebf8cfe0d8803d5b103773a21806a6c43fa57501763b771be3db158bfcd584604051610d699190611690565b6040516301ffc9a760e01b815263b130abe360e01b60048201526001600160a01b038216906301ffc9a790602401602060405180830381865afa158015610e8b573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610eaf9190611991565b610f135760405162461bcd60e51b815260206004820152602f60248201527f446f6573206e6f7420696d706c656d656e742049556e6973776170563244796e60448201526e30b6b4b1a83934b1b2a937baba32b960891b6064820152608401610941565b610953816113f4565b600081600003610f2e57506000919050565b6004805460405163bd3f03f960e01b81526001600160a01b039091169163bd3f03f9916108b891869160039101611933565b6001815111610fb15760405162461bcd60e51b815260206004820181905260248201527f50617468206d7573742068617665206174206c65617374203220746f6b656e736044820152606401610941565b7f00000000000000000000000042000000000000000000000000000000000000066001600160a01b031681600081518110610fee57610fee6119b3565b60200260200101516001600160a01b03161461104c5760405162461bcd60e51b815260206004820181905260248201527f4261736520746f6b656e206d75737420626520666972737420696e20706174686044820152606401610941565b7f000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029136001600160a01b0316816001835161108591906119c9565b81518110611095576110956119b3565b60200260200101516001600160a01b0316146109535760405162461bcd60e51b815260206004820181905260248201527f51756f746520746f6b656e206d757374206265206c61737420696e20706174686044820152606401610941565b60045481516001600160a01b039091169063bd3f03f990839060009061111b5761111b6119b3565b60200260200101516001600160a01b031663313ce5676040518163ffffffff1660e01b8152600401602060405180830381865afa158015611160573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906111849190611744565b61118f90600a611924565b836040518363ffffffff1660e01b81526004016111ad9291906119dc565b602060405180830381865afa9250505080156111e6575060408051601f3d908101601f191682019092526111e39181019061172b565b60015b6107ce57806040516354da4f1960e01b81526004016109419190611690565b600580546001600160a01b038381166001600160a01b0319831681179093556040519116919082907f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e090600090a35050565b60018151116112a85760405162461bcd60e51b815260206004820181905260248201527f50617468206d7573742068617665206174206c65617374203220746f6b656e736044820152606401610941565b7f000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029136001600160a01b0316816000815181106112e5576112e56119b3565b60200260200101516001600160a01b03161461134d5760405162461bcd60e51b815260206004820152602160248201527f51756f746520746f6b656e206d75737420626520666972737420696e207061746044820152600d60fb1b6064820152608401610941565b7f00000000000000000000000042000000000000000000000000000000000000066001600160a01b0316816001835161138691906119c9565b81518110611396576113966119b3565b60200260200101516001600160a01b0316146109535760405162461bcd60e51b815260206004820152601f60248201527f4261736520746f6b656e206d757374206265206c61737420696e2070617468006044820152606401610941565b600480546001600160a01b0319166001600160a01b03831690811790915560405130907fd208b3f2137ba34e4b2ea29e6e0558a1395796409f6b8328f2cbd5ff9159bc1590600090a350565b828054828255906000526020600020908101928215611495579160200282015b8281111561149557825182546001600160a01b0319166001600160a01b03909116178255602090920191600190910190611460565b506114a19291506114a5565b5090565b5b808211156114a157600081556001016114a6565b6000602082840312156114cc57600080fd5b81356001600160e01b031981168114610a4557600080fd5b60005b838110156114ff5781810151838201526020016114e7565b50506000910152565b60208152600082518060208401526115278160408501602087016114e4565b601f01601f19169190910160400192915050565b80356001600160a01b038116811461155257600080fd5b919050565b6000806040838503121561156a57600080fd5b6115738361153b565b946020939093013593505050565b60008060006060848603121561159657600080fd5b61159f8461153b565b92506115ad6020850161153b565b9150604084013590509250925092565b600080602083850312156115d057600080fd5b823567ffffffffffffffff808211156115e857600080fd5b818501915085601f8301126115fc57600080fd5b81358181111561160b57600080fd5b8660208260051b850101111561162057600080fd5b60209290920196919550909350505050565b60006020828403121561164457600080fd5b5035919050565b60008151808452602080850194506020840160005b838110156116855781516001600160a01b031687529582019590820190600101611660565b509495945050505050565b602081526000610a45602083018461164b565b6000602082840312156116b557600080fd5b610a458261153b565b600080604083850312156116d157600080fd5b6116da8361153b565b91506116e86020840161153b565b90509250929050565b600181811c9082168061170557607f821691505b60208210810361172557634e487b7160e01b600052602260045260246000fd5b50919050565b60006020828403121561173d57600080fd5b5051919050565b60006020828403121561175657600080fd5b815160ff81168114610a4557600080fd5b634e487b7160e01b600052604160045260246000fd5b60006020828403121561178f57600080fd5b815167ffffffffffffffff808211156117a757600080fd5b818401915084601f8301126117bb57600080fd5b8151818111156117cd576117cd611767565b604051601f8201601f19908116603f011681019083821181831017156117f5576117f5611767565b8160405282815287602084870101111561180e57600080fd5b61181f8360208301602088016114e4565b979650505050505050565b634e487b7160e01b600052601160045260246000fd5b600181815b8085111561187b5781600019048211156118615761186161182a565b8085161561186e57918102915b93841c9390800290611845565b509250929050565b6000826118925750600161049b565b8161189f5750600061049b565b81600181146118b557600281146118bf576118db565b600191505061049b565b60ff8411156118d0576118d061182a565b50506001821b61049b565b5060208310610133831016604e8410600b84101617156118fe575081810a61049b565b6119088383611840565b806000190482111561191c5761191c61182a565b029392505050565b6000610a4560ff841683611883565b600060408201848352602060406020850152818554808452606086019150866000526020600020935060005b818110156119845784546001600160a01b03168352600194850194928401920161195f565b5090979650505050505050565b6000602082840312156119a357600080fd5b81518015158114610a4557600080fd5b634e487b7160e01b600052603260045260246000fd5b8181038181111561049b5761049b61182a565b8281526040602082015260006119f5604083018461164b565b94935050505056fea2646970667358221220010d22d06d443982ab6747b918a4f147524563fb657637be8943b6752884d25564736f6c63430008180033

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

00000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001200000000000000000000000004200000000000000000000000000000000000006000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda0291300000000000000000000000017b0e768cd785fcaad2729650714fbf57e8f4ddc000000000000000000000000000000000000000000000000000000000000016000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000001c556e69737761702056322044796e616d696320574554482f5553444300000000000000000000000000000000000000000000000000000000000000000000000a64574554482d555344430000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000004200000000000000000000000000000000000006000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000000000000000000000000000000000000000000002000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda029130000000000000000000000004200000000000000000000000000000000000006

-----Decoded View---------------
Arg [0] : _name (string): Uniswap V2 Dynamic WETH/USDC
Arg [1] : _symbol (string): dWETH-USDC
Arg [2] : _baseToken (address): 0x4200000000000000000000000000000000000006
Arg [3] : _quoteToken (address): 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
Arg [4] : _dynamicPriceRouter (address): 0x17B0e768Cd785fCAad2729650714fbF57E8F4DdC
Arg [5] : _baseToQuotePath (address[]): 0x4200000000000000000000000000000000000006,0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913
Arg [6] : _quoteToBasePath (address[]): 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913,0x4200000000000000000000000000000000000006

-----Encoded View---------------
17 Constructor Arguments found :
Arg [0] : 00000000000000000000000000000000000000000000000000000000000000e0
Arg [1] : 0000000000000000000000000000000000000000000000000000000000000120
Arg [2] : 0000000000000000000000004200000000000000000000000000000000000006
Arg [3] : 000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913
Arg [4] : 00000000000000000000000017b0e768cd785fcaad2729650714fbf57e8f4ddc
Arg [5] : 0000000000000000000000000000000000000000000000000000000000000160
Arg [6] : 00000000000000000000000000000000000000000000000000000000000001c0
Arg [7] : 000000000000000000000000000000000000000000000000000000000000001c
Arg [8] : 556e69737761702056322044796e616d696320574554482f5553444300000000
Arg [9] : 000000000000000000000000000000000000000000000000000000000000000a
Arg [10] : 64574554482d5553444300000000000000000000000000000000000000000000
Arg [11] : 0000000000000000000000000000000000000000000000000000000000000002
Arg [12] : 0000000000000000000000004200000000000000000000000000000000000006
Arg [13] : 000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913
Arg [14] : 0000000000000000000000000000000000000000000000000000000000000002
Arg [15] : 000000000000000000000000833589fcd6edb6e08f4c7c32d4f71b54bda02913
Arg [16] : 0000000000000000000000004200000000000000000000000000000000000006


Loading...
Loading
Loading...
Loading
[ Download: CSV Export  ]

A token is a representation of an on-chain or off-chain asset. The token page shows information such as price, total supply, holders, transfers and social links. Learn more about this page in our Knowledge Base.