// File: contracts/core/interfaces/IMetadataModule.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

/**
 * @title IMetadataModule
 * @notice The interface for custom metadata modules.
 */
interface IMetadataModule {
    /**
     * @dev When implemented, SoundEdition's `tokenURI` redirects execution to this `tokenURI`.
     * @param tokenId The token ID to retrieve the token URI for.
     * @return The token URI string.
     */
    function tokenURI(uint256 tokenId) external view returns (string memory);
}

// File: contracts/core/interfaces/IMinterModule.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

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

/**
 * @title IMinterModule
 * @notice The interface for Sound protocol minter modules.
 */
interface IMinterModule is IERC165 {
    // =============================================================
    //                            STRUCTS
    // =============================================================

    struct BaseData {
        // The start unix timestamp of the mint.
        uint32 startTime;
        // The end unix timestamp of the mint.
        uint32 endTime;
        // The affiliate fee in basis points.
        uint16 affiliateFeeBPS;
        // Whether the mint is paused.
        bool mintPaused;
    }

    // =============================================================
    //                            EVENTS
    // =============================================================

    /**
     * @dev Emitted when the mint instance for an `edition` is created.
     * @param edition The edition address.
     * @param mintId The mint ID, a global incrementing identifier used within the minter
     * @param startTime The start time of the mint.
     * @param endTime The end time of the mint.
     * @param affiliateFeeBPS The affiliate fee in basis points.
     */
    event MintConfigCreated(
        address indexed edition,
        address indexed creator,
        uint128 mintId,
        uint32 startTime,
        uint32 endTime,
        uint16 affiliateFeeBPS
    );

    /**
     * @dev Emitted when the `paused` status of `edition` is updated.
     * @param edition The edition address.
     * @param mintId  The mint ID, to distinguish between multiple mints for the same edition.
     * @param paused  The new paused status.
     */
    event MintPausedSet(address indexed edition, uint128 mintId, bool paused);

    /**
     * @dev Emitted when the `paused` status of `edition` is updated.
     * @param edition   The edition address.
     * @param mintId    The mint ID, to distinguish between multiple mints for the same edition.
     * @param startTime The start time of the mint.
     * @param endTime   The end time of the mint.
     */
    event TimeRangeSet(address indexed edition, uint128 indexed mintId, uint32 startTime, uint32 endTime);

    /**
     * @notice Emitted when the `affiliateFeeBPS` is updated.
     * @param edition The edition address.
     * @param mintId  The mint ID, to distinguish between multiple mints for the same edition.
     * @param bps     The affiliate fee basis points.
     */
    event AffiliateFeeSet(address indexed edition, uint128 indexed mintId, uint16 bps);

    /**
     * @notice Emitted when a mint happens.
     * @param edition            The edition address.
     * @param mintId             The mint ID, to distinguish between multiple mints for
     *                           the same edition.
     * @param buyer              The buyer address.
     * @param fromTokenId        The first token ID of the batch.
     * @param quantity           The size of the batch.
     * @param requiredEtherValue Total amount of Ether required for payment.
     * @param platformFee        The cut paid to the platform.
     * @param affiliateFee       The cut paid to the affiliate.
     * @param affiliate          The affiliate's address.
     * @param affiliated         Whether the affiliate is affiliated.
     */
    event Minted(
        address indexed edition,
        uint128 indexed mintId,
        address indexed buyer,
        uint32 fromTokenId,
        uint32 quantity,
        uint128 requiredEtherValue,
        uint128 platformFee,
        uint128 affiliateFee,
        address affiliate,
        bool affiliated
    );

    // =============================================================
    //                            ERRORS
    // =============================================================

    /**
     * @dev The Ether value paid is below the value required.
     * @param paid The amount sent to the contract.
     * @param required The amount required to mint.
     */
    error Underpaid(uint256 paid, uint256 required);

    /**
     * @dev The number minted has exceeded the max mintable amount.
     * @param available The number of tokens remaining available for mint.
     */
    error ExceedsAvailableSupply(uint32 available);

    /**
     * @dev The mint is not opened.
     * @param blockTimestamp The current block timestamp.
     * @param startTime The start time of the mint.
     * @param endTime The end time of the mint.
     */
    error MintNotOpen(uint256 blockTimestamp, uint32 startTime, uint32 endTime);

    /**
     * @dev The mint is paused.
     */
    error MintPaused();

    /**
     * @dev The `startTime` is not less than the `endTime`.
     */
    error InvalidTimeRange();

    /**
     * @dev Unauthorized caller
     */
    error Unauthorized();

    /**
     * @dev The affiliate fee numerator must not exceed `MAX_BPS`.
     */
    error InvalidAffiliateFeeBPS();

    /**
     * @dev Fee registry cannot be the zero address.
     */
    error FeeRegistryIsZeroAddress();

    // =============================================================
    //               PUBLIC / EXTERNAL WRITE FUNCTIONS
    // =============================================================

    /**
     * @dev Sets the paused status for (`edition`, `mintId`).
     *
     * Calling conditions:
     * - The caller must be the edition's owner or admin.
     */
    function setEditionMintPaused(
        address edition,
        uint128 mintId,
        bool paused
    ) external;

    /**
     * @dev Sets the time range for an edition mint.
     *
     * Calling conditions:
     * - The caller must be the edition's owner or admin.
     *
     * @param edition The edition address.
     * @param mintId The mint ID, a global incrementing identifier used within the minter
     * @param startTime The start time of the mint.
     * @param endTime The end time of the mint.
     */
    function setTimeRange(
        address edition,
        uint128 mintId,
        uint32 startTime,
        uint32 endTime
    ) external;

    /**
     * @dev Sets the affiliate fee for (`edition`, `mintId`).
     *
     * Calling conditions:
     * - The caller must be the edition's owner or admin.
     */
    function setAffiliateFee(
        address edition,
        uint128 mintId,
        uint16 affiliateFeeBPS
    ) external;

    /**
     * @dev Withdraws all the accrued fees for `affiliate`.
     */
    function withdrawForAffiliate(address affiliate) external;

    /**
     * @dev Withdraws all the accrued fees for the platform.
     */
    function withdrawForPlatform() external;

    // =============================================================
    //               PUBLIC / EXTERNAL VIEW FUNCTIONS
    // =============================================================

    /**
     * @dev The total fees accrued for `affiliate`.
     * @param affiliate The affiliate's address.
     * @return The latest value.
     */
    function affiliateFeesAccrued(address affiliate) external view returns (uint128);

    /**
     * @dev The total fees accrued for the platform.
     * @return The latest value.
     */
    function platformFeesAccrued() external view returns (uint128);

    /**
     * @dev Whether `affiliate` is affiliated for (`edition`, `mintId`).
     * @param edition   The edition's address.
     * @param mintId    The mint ID.
     * @param affiliate The affiliate's address.
     * @return The computed value.
     */
    function isAffiliated(
        address edition,
        uint128 mintId,
        address affiliate
    ) external view returns (bool);

    /**
     * @dev The total price for `quantity` tokens for (`edition`, `mintId`).
     * @param edition   The edition's address.
     * @param mintId    The mint ID.
     * @param mintId    The minter's address.
     * @param quantity  The number of tokens to mint.
     * @return The computed value.
     */
    function totalPrice(
        address edition,
        uint128 mintId,
        address minter,
        uint32 quantity
    ) external view returns (uint128);

    /**
     * @dev The next mint ID.
     *      A mint ID is assigned sequentially starting from (0, 1, 2, ...),
     *      and is shared amongst all editions connected to the minter contract.
     * @return The latest value.
     */
    function nextMintId() external view returns (uint128);

    /**
     * @dev The interface ID of the minter.
     * @return The constant value.
     */
    function moduleInterfaceId() external view returns (bytes4);

    /**
     * @dev The fee registry. Used for handling platform fees.
     * @return The immutable value.
     */
    function feeRegistry() external view returns (ISoundFeeRegistry);
}

// File: contracts/core/interfaces/ISoundEditionV1.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

import { IERC721AUpgradeable } from "chiru-labs/ERC721A-Upgradeable/IERC721AUpgradeable.sol";
import { IERC2981Upgradeable } from "openzeppelin-upgradeable/interfaces/IERC2981Upgradeable.sol";
import { IERC165Upgradeable } from "openzeppelin-upgradeable/utils/introspection/IERC165Upgradeable.sol";

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

/**
 * @dev The information pertaining to this edition.
 */
struct EditionInfo {
    // Base URI for the tokenId.
    string baseURI;
    // Contract URI for OpenSea storefront.
    string contractURI;
    // Name of the collection.
    string name;
    // Symbol of the collection.
    string symbol;
    // Address that receives primary and secondary royalties.
    address fundingRecipient;
    // The current max mintable amount;
    uint32 editionMaxMintable;
    // The lower limit of the maximum number of tokens that can be minted.
    uint32 editionMaxMintableUpper;
    // The upper limit of the maximum number of tokens that can be minted.
    uint32 editionMaxMintableLower;
    // The timestamp (in seconds since unix epoch) after which the
    // max amount of tokens mintable will drop from
    // `maxMintableUpper` to `maxMintableLower`.
    uint32 editionCutoffTime;
    // Address of metadata module, address(0x00) if not used.
    address metadataModule;
    // The current mint randomness value.
    uint256 mintRandomness;
    // The royalty BPS (basis points).
    uint16 royaltyBPS;
    // Whether the mint randomness is enabled.
    bool mintRandomnessEnabled;
    // Whether the mint has concluded.
    bool mintConcluded;
    // Whether the metadata has been frozen.
    bool isMetadataFrozen;
    // Next token ID to be minted.
    uint256 nextTokenId;
    // Total number of tokens burned.
    uint256 totalBurned;
    // Total number of tokens minted.
    uint256 totalMinted;
    // Total number of tokens currently in existence.
    uint256 totalSupply;
}

/**
 * @title ISoundEditionV1
 * @notice The interface for Sound edition contracts.
 */
interface ISoundEditionV1 is IERC721AUpgradeable, IERC2981Upgradeable {
    // =============================================================
    //                            EVENTS
    // =============================================================

    /**
     * @dev Emitted when the metadata module is set.
     * @param metadataModule the address of the metadata module.
     */
    event MetadataModuleSet(address metadataModule);

    /**
     * @dev Emitted when the `baseURI` is set.
     * @param baseURI the base URI of the edition.
     */
    event BaseURISet(string baseURI);

    /**
     * @dev Emitted when the `contractURI` is set.
     * @param contractURI The contract URI of the edition.
     */
    event ContractURISet(string contractURI);

    /**
     * @dev Emitted when the metadata is frozen (e.g.: `baseURI` can no longer be changed).
     * @param metadataModule The address of the metadata module.
     * @param baseURI        The base URI of the edition.
     * @param contractURI    The contract URI of the edition.
     */
    event MetadataFrozen(address metadataModule, string baseURI, string contractURI);

    /**
     * @dev Emitted when the `fundingRecipient` is set.
     * @param fundingRecipient The address of the funding recipient.
     */
    event FundingRecipientSet(address fundingRecipient);

    /**
     * @dev Emitted when the `royaltyBPS` is set.
     * @param bps The new royalty, measured in basis points.
     */
    event RoyaltySet(uint16 bps);

    /**
     * @dev Emitted when the edition's maximum mintable token quantity range is set.
     * @param editionMaxMintableLower_ The lower limit of the maximum number of tokens that can be minted.
     * @param editionMaxMintableUpper_ The upper limit of the maximum number of tokens that can be minted.
     */
    event EditionMaxMintableRangeSet(uint32 editionMaxMintableLower_, uint32 editionMaxMintableUpper_);

    /**
     * @dev Emitted when the edition's cutoff time set.
     * @param editionCutoffTime_ The timestamp.
     */
    event EditionCutoffTimeSet(uint32 editionCutoffTime_);

    /**
     * @dev Emitted when the `mintRandomnessEnabled` is set.
     * @param mintRandomnessEnabled_ The boolean value.
     */
    event MintRandomnessEnabledSet(bool mintRandomnessEnabled_);

    /**
     * @dev Emitted upon initialization.
     * @param edition_                 The address of the edition.
     * @param name_                    Name of the collection.
     * @param symbol_                  Symbol of the collection.
     * @param metadataModule_          Address of metadata module, address(0x00) if not used.
     * @param baseURI_                 Base URI.
     * @param contractURI_             Contract URI for OpenSea storefront.
     * @param fundingRecipient_        Address that receives primary and secondary royalties.
     * @param royaltyBPS_              Royalty amount in bps (basis points).
     * @param editionMaxMintableLower_ The lower bound of the max mintable quantity for the edition.
     * @param editionMaxMintableUpper_ The upper bound of the max mintable quantity for the edition.
     * @param editionCutoffTime_       The timestamp after which `editionMaxMintable` drops from
     *                                 `editionMaxMintableUpper` to
     *                                 `max(_totalMinted(), editionMaxMintableLower)`.
     * @param flags_                   The bitwise OR result of the initialization flags.
     *                                 See: {METADATA_IS_FROZEN_FLAG}
     *                                 See: {MINT_RANDOMNESS_ENABLED_FLAG}
     */
    event SoundEditionInitialized(
        address indexed edition_,
        string name_,
        string symbol_,
        address metadataModule_,
        string baseURI_,
        string contractURI_,
        address fundingRecipient_,
        uint16 royaltyBPS_,
        uint32 editionMaxMintableLower_,
        uint32 editionMaxMintableUpper_,
        uint32 editionCutoffTime_,
        uint8 flags_
    );

    /**
     * @dev Emitted upon ETH withdrawal.
     * @param recipient The recipient of the withdrawal.
     * @param amount    The amount withdrawn.
     * @param caller    The account that initiated the withdrawal.
     */
    event ETHWithdrawn(address recipient, uint256 amount, address caller);

    /**
     * @dev Emitted upon ERC20 withdrawal.
     * @param recipient The recipient of the withdrawal.
     * @param tokens    The addresses of the ERC20 tokens.
     * @param amounts   The amount of each token withdrawn.
     * @param caller    The account that initiated the withdrawal.
     */
    event ERC20Withdrawn(address recipient, address[] tokens, uint256[] amounts, address caller);

    /**
     * @dev Emitted upon a mint.
     * @param to          The address to mint to.
     * @param quantity    The number of minted.
     * @param fromTokenId The first token ID minted.
     */
    event Minted(address to, uint256 quantity, uint256 fromTokenId);

    /**
     * @dev Emitted upon an airdrop.
     * @param to          The recipients of the airdrop.
     * @param quantity    The number of tokens airdropped to each address in `to`.
     * @param fromTokenId The first token ID minted to the first address in `to`.
     */
    event Airdropped(address[] to, uint256 quantity, uint256 fromTokenId);

    // =============================================================
    //                            ERRORS
    // =============================================================

    /**
     * @dev The edition's metadata is frozen (e.g.: `baseURI` can no longer be changed).
     */
    error MetadataIsFrozen();

    /**
     * @dev The given `royaltyBPS` is invalid.
     */
    error InvalidRoyaltyBPS();

    /**
     * @dev The given `randomnessLockedAfterMinted` value is invalid.
     */
    error InvalidRandomnessLock();

    /**
     * @dev The requested quantity exceeds the edition's remaining mintable token quantity.
     * @param available The number of tokens remaining available for mint.
     */
    error ExceedsEditionAvailableSupply(uint32 available);

    /**
     * @dev The given amount is invalid.
     */
    error InvalidAmount();

    /**
     * @dev The given `fundingRecipient` address is invalid.
     */
    error InvalidFundingRecipient();

    /**
     * @dev The `editionMaxMintableLower` must not be greater than `editionMaxMintableUpper`.
     */
    error InvalidEditionMaxMintableRange();

    /**
     * @dev The `editionMaxMintable` has already been reached.
     */
    error MaximumHasAlreadyBeenReached();

    /**
     * @dev The mint `quantity` cannot exceed `ADDRESS_BATCH_MINT_LIMIT` tokens.
     */
    error ExceedsAddressBatchMintLimit();

    /**
     * @dev The mint randomness has already been revealed.
     */
    error MintRandomnessAlreadyRevealed();

    /**
     * @dev No addresses to airdrop.
     */
    error NoAddressesToAirdrop();

    /**
     * @dev The mint has already concluded.
     */
    error MintHasConcluded();

    /**
     * @dev Cannot perform the operation after a token has been minted.
     */
    error MintsAlreadyExist();

    // =============================================================
    //               PUBLIC / EXTERNAL WRITE FUNCTIONS
    // =============================================================

    /**
     * @dev Initializes the contract.
     * @param name_                    Name of the collection.
     * @param symbol_                  Symbol of the collection.
     * @param metadataModule_          Address of metadata module, address(0x00) if not used.
     * @param baseURI_                 Base URI.
     * @param contractURI_             Contract URI for OpenSea storefront.
     * @param fundingRecipient_        Address that receives primary and secondary royalties.
     * @param royaltyBPS_              Royalty amount in bps (basis points).
     * @param editionMaxMintableLower_ The lower bound of the max mintable quantity for the edition.
     * @param editionMaxMintableUpper_ The upper bound of the max mintable quantity for the edition.
     * @param editionCutoffTime_       The timestamp after which `editionMaxMintable` drops from
     *                                 `editionMaxMintableUpper` to
     *                                 `max(_totalMinted(), editionMaxMintableLower)`.
     * @param flags_                   The bitwise OR result of the initialization flags.
     *                                 See: {METADATA_IS_FROZEN_FLAG}
     *                                 See: {MINT_RANDOMNESS_ENABLED_FLAG}
     */
    function initialize(
        string memory name_,
        string memory symbol_,
        address metadataModule_,
        string memory baseURI_,
        string memory contractURI_,
        address fundingRecipient_,
        uint16 royaltyBPS_,
        uint32 editionMaxMintableLower_,
        uint32 editionMaxMintableUpper_,
        uint32 editionCutoffTime_,
        uint8 flags_
    ) external;

    /**
     * @dev Mints `quantity` tokens to addrress `to`
     *      Each token will be assigned a token ID that is consecutively increasing.
     *
     * Calling conditions:
     * - The caller must be the owner of the contract, or have either the
     *   `ADMIN_ROLE`, `MINTER_ROLE`, which can be granted via {grantRole}.
     *   Multiple minters, such as different minter contracts,
     *   can be authorized simultaneously.
     *
     * @param to       Address to mint to.
     * @param quantity Number of tokens to mint.
     * @return fromTokenId The first token ID minted.
     */
    function mint(address to, uint256 quantity) external payable returns (uint256 fromTokenId);

    /**
     * @dev Mints `quantity` tokens to each of the addresses in `to`.
     *
     * Calling conditions:
     * - The caller must be the owner of the contract, or have the
     *   `ADMIN_ROLE`, which can be granted via {grantRole}.
     *
     * @param to           Address to mint to.
     * @param quantity     Number of tokens to mint.
     * @return fromTokenId The first token ID minted.
     */
    function airdrop(address[] calldata to, uint256 quantity) external returns (uint256 fromTokenId);

    /**
     * @dev Withdraws collected ETH royalties to the fundingRecipient.
     */
    function withdrawETH() external;

    /**
     * @dev Withdraws collected ERC20 royalties to the fundingRecipient.
     * @param tokens array of ERC20 tokens to withdraw
     */
    function withdrawERC20(address[] calldata tokens) external;

    /**
     * @dev Sets metadata module.
     *
     * Calling conditions:
     * - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
     *
     * @param metadataModule Address of metadata module.
     */
    function setMetadataModule(address metadataModule) external;

    /**
     * @dev Sets global base URI.
     *
     * Calling conditions:
     * - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
     *
     * @param baseURI The base URI to be set.
     */
    function setBaseURI(string memory baseURI) external;

    /**
     * @dev Sets contract URI.
     *
     * Calling conditions:
     * - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
     *
     * @param contractURI The contract URI to be set.
     */
    function setContractURI(string memory contractURI) external;

    /**
     * @dev Freezes metadata by preventing any more changes to base URI.
     *
     * Calling conditions:
     * - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
     */
    function freezeMetadata() external;

    /**
     * @dev Sets funding recipient address.
     *
     * Calling conditions:
     * - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
     *
     * @param fundingRecipient Address to be set as the new funding recipient.
     */
    function setFundingRecipient(address fundingRecipient) external;

    /**
     * @dev Sets royalty amount in bps (basis points).
     *
     * Calling conditions:
     * - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
     *
     * @param bps The new royalty basis points to be set.
     */
    function setRoyalty(uint16 bps) external;

    /**
     * @dev Sets the edition max mintable range.
     *
     * Calling conditions:
     * - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
     *
     * @param editionMaxMintableLower_ The lower limit of the maximum number of tokens that can be minted.
     * @param editionMaxMintableUpper_ The upper limit of the maximum number of tokens that can be minted.
     */
    function setEditionMaxMintableRange(uint32 editionMaxMintableLower_, uint32 editionMaxMintableUpper_) external;

    /**
     * @dev Sets the timestamp after which, the `editionMaxMintable` drops
     *      from `editionMaxMintableUpper` to `editionMaxMintableLower.
     *
     * Calling conditions:
     * - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
     *
     * @param editionCutoffTime_ The timestamp.
     */
    function setEditionCutoffTime(uint32 editionCutoffTime_) external;

    /**
     * @dev Sets whether the `mintRandomness` is enabled.
     *
     * Calling conditions:
     * - The caller must be the owner of the contract, or have the `ADMIN_ROLE`.
     *
     * @param mintRandomnessEnabled_ The boolean value.
     */
    function setMintRandomnessEnabled(bool mintRandomnessEnabled_) external;

    // =============================================================
    //               PUBLIC / EXTERNAL VIEW FUNCTIONS
    // =============================================================

    /**
     * @dev Returns the edition info.
     * @return editionInfo The latest value.
     */
    function editionInfo() external view returns (EditionInfo memory editionInfo);

    /**
     * @dev Returns the minter role flag.
     * @return The constant value.
     */
    function MINTER_ROLE() external view returns (uint256);

    /**
     * @dev Returns the admin role flag.
     * @return The constant value.
     */
    function ADMIN_ROLE() external view returns (uint256);

    /**
     * @dev Returns the maximum limit for the mint or airdrop `quantity`.
     *      Prevents the first-time transfer costs for tokens near the end of large mint batches
     *      via ERC721A from becoming too expensive due to the need to scan many storage slots.
     *      See: https://chiru-labs.github.io/ERC721A/#/tips?id=batch-size
     * @return The constant value.
     */
    function ADDRESS_BATCH_MINT_LIMIT() external pure returns (uint256);

    /**
     * @dev Returns the bit flag to freeze the metadata on initialization.
     * @return The constant value.
     */
    function METADATA_IS_FROZEN_FLAG() external pure returns (uint8);

    /**
     * @dev Returns the bit flag to enable the mint randomness feature on initialization.
     * @return The constant value.
     */
    function MINT_RANDOMNESS_ENABLED_FLAG() external pure returns (uint8);

    /**
     * @dev Returns the base token URI for the collection.
     * @return The configured value.
     */
    function baseURI() external view returns (string memory);

    /**
     * @dev Returns the contract URI to be used by Opensea.
     *      See: https://docs.opensea.io/docs/contract-level-metadata
     * @return The configured value.
     */
    function contractURI() external view returns (string memory);

    /**
     * @dev Returns the address of the funding recipient.
     * @return The configured value.
     */
    function fundingRecipient() external view returns (address);

    /**
     * @dev Returns the maximum amount of tokens mintable for this edition.
     * @return The configured value.
     */
    function editionMaxMintable() external view returns (uint32);

    /**
     * @dev Returns the upper bound for the maximum tokens that can be minted for this edition.
     * @return The configured value.
     */
    function editionMaxMintableUpper() external view returns (uint32);

    /**
     * @dev Returns the lower bound for the maximum tokens that can be minted for this edition.
     * @return The configured value.
     */
    function editionMaxMintableLower() external view returns (uint32);

    /**
     * @dev Returns the timestamp after which `editionMaxMintable` drops from
     *      `editionMaxMintableUpper` to `editionMaxMintableLower`.
     * @return The configured value.
     */
    function editionCutoffTime() external view returns (uint32);

    /**
     * @dev Returns the address of the metadata module.
     * @return The configured value.
     */
    function metadataModule() external view returns (address);

    /**
     * @dev Returns the randomness based on latest block hash, which is stored upon each mint.
     *      unless {mintConcluded} is true.
     *      Used for game mechanics like the Sound Golden Egg.
     *      Returns 0 before revealed.
     *      WARNING: This value should NOT be used for any reward of significant monetary
     *      value, due to it being computed via a purely on-chain psuedorandom mechanism.
     * @return The latest value.
     */
    function mintRandomness() external view returns (uint256);

    /**
     * @dev Returns whether the `mintRandomness` has been enabled.
     * @return The configured value.
     */
    function mintRandomnessEnabled() external view returns (bool);

    /**
     * @dev Returns whether the mint has been concluded.
     * @return The latest value.
     */
    function mintConcluded() external view returns (bool);

    /**
     * @dev Returns the royalty basis points.
     * @return The configured value.
     */
    function royaltyBPS() external view returns (uint16);

    /**
     * @dev Returns whether the metadata module is frozen.
     * @return The configured value.
     */
    function isMetadataFrozen() external view returns (bool);

    /**
     * @dev Returns the next token ID to be minted.
     * @return The latest value.
     */
    function nextTokenId() external view returns (uint256);

    /**
     * @dev Returns the number of tokens minted by `owner`.
     * @param owner Address to query for number minted.
     * @return The latest value.
     */
    function numberMinted(address owner) external view returns (uint256);

    /**
     * @dev Returns the number of tokens burned by `owner`.
     * @param owner Address to query for number burned.
     * @return The latest value.
     */
    function numberBurned(address owner) external view returns (uint256);

    /**
     * @dev Returns the total amount of tokens minted.
     * @return The latest value.
     */
    function totalMinted() external view returns (uint256);

    /**
     * @dev Returns the total amount of tokens burned.
     * @return The latest value.
     */
    function totalBurned() external view returns (uint256);

    /**
     * @dev Informs other contracts which interfaces this contract supports.
     *      Required by https://eips.ethereum.org/EIPS/eip-165
     * @param interfaceId The interface id to check.
     * @return Whether the `interfaceId` is supported.
     */
    function supportsInterface(bytes4 interfaceId)
        external
        view
        override(IERC721AUpgradeable, IERC165Upgradeable)
        returns (bool);
}

// File: contracts/core/interfaces/ISoundFeeRegistry.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.16;

/**
 * @title ISoundFeeRegistry
 * @author Sound.xyz
 */
interface ISoundFeeRegistry {
    // =============================================================
    //                            EVENTS
    // =============================================================

    /**
     * @dev Emitted when the `soundFeeAddress` is changed.
     */
    event SoundFeeAddressSet(address soundFeeAddress);

    /**
     * @dev Emitted when the `platformFeeBPS` is changed.
     */
    event PlatformFeeSet(uint16 platformFeeBPS);

    // =============================================================
    //                             ERRORS
    // =============================================================

    /**
     * @dev The new `soundFeeAddress` must not be address(0).
     */
    error InvalidSoundFeeAddress();

    /**
     * @dev The platform fee numerator must not exceed `_MAX_BPS`.
     */
    error InvalidPlatformFeeBPS();

    // =============================================================
    //               PUBLIC / EXTERNAL WRITE FUNCTIONS
    // =============================================================

    /**
     * @dev Sets the `soundFeeAddress`.
     *
     * Calling conditions:
     * - The caller must be the owner of the contract.
     *
     * @param soundFeeAddress_ The sound fee address.
     */
    function setSoundFeeAddress(address soundFeeAddress_) external;

    /**
     * @dev Sets the `platformFeePBS`.
     *
     * Calling conditions:
     * - The caller must be the owner of the contract.
     *
     * @param platformFeeBPS_ Platform fee amount in bps (basis points).
     */
    function setPlatformFeeBPS(uint16 platformFeeBPS_) external;

    // =============================================================
    //               PUBLIC / EXTERNAL VIEW FUNCTIONS
    // =============================================================

    /**
     * @dev The sound protocol's address that receives platform fees.
     * @return The configured value.
     */
    function soundFeeAddress() external view returns (address);

    /**
     * @dev The numerator of the platform fee.
     * @return The configured value.
     */
    function platformFeeBPS() external view returns (uint16);

    /**
     * @dev The platform fee for `requiredEtherValue`.
     * @param requiredEtherValue The required Ether value for payment.
     * @return fee The computed value.
     */
    function platformFee(uint128 requiredEtherValue) external view returns (uint128 fee);
}

// File: contracts/modules/MinterAdapter.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

import { ISoundEditionV1 } from "@core/interfaces/ISoundEditionV1.sol";
import { IEditionMaxMinter } from "./interfaces/IEditionMaxMinter.sol";
import { ISAM } from "./interfaces/ISAM.sol";
import { IMinterAdapter, IERC165 } from "./interfaces/IMinterAdapter.sol";
import { SafeTransferLib } from "solady/utils/SafeTransferLib.sol";

/**
 * @title Minter Adapter
 * @dev A minter adapter for minting to user specified addresses on
 *      old EditionMaxMinterV2s and RangeEditionMinterV2s,
 *      which do not have a `mintTo` function.
 */
contract MinterAdapter is IMinterAdapter {
    // =============================================================
    //               PUBLIC / EXTERNAL WRITE FUNCTIONS
    // =============================================================

    /**
     * @inheritdoc IMinterAdapter
     */
    function mintTo(
        address minter,
        address edition,
        uint128 mintId,
        address to,
        uint32 quantity,
        address affiliate,
        uint256 attributionId
    ) public payable {
        uint256 tokenId = ISoundEditionV1(edition).nextTokenId();

        // This supports the old `mint` function on both
        // `IEditionMaxMinter` and `IRangeEditionMinter`.
        IEditionMaxMinter(minter).mint{ value: msg.value }(edition, mintId, quantity, affiliate);

        emit AdapterMinted(minter, edition, tokenId, quantity, to, attributionId);

        uint256 end = tokenId + uint256(quantity);
        while (tokenId != end) {
            ISoundEditionV1(edition).transferFrom(address(this), to, tokenId);
            unchecked {
                ++tokenId;
            }
        }

        if (address(this).balance != 0) {
            SafeTransferLib.forceSafeTransferETH(msg.sender, address(this).balance);
        }
    }

    /**
     * @inheritdoc IMinterAdapter
     */
    function samBuy(
        address sam,
        address edition,
        address to,
        uint32 quantity,
        address affiliate,
        bytes32[] calldata affiliateProof,
        uint256 attributionId,
        address excessRefundTo
    ) public payable {
        uint256 tokenId = ISoundEditionV1(edition).nextTokenId();

        ISAM(sam).buy{ value: msg.value }(edition, to, quantity, affiliate, affiliateProof, attributionId);

        emit AdapterMinted(sam, edition, tokenId, quantity, to, attributionId);

        if (address(this).balance != 0) {
            if (excessRefundTo == address(0)) {
                excessRefundTo = msg.sender;
            }
            SafeTransferLib.forceSafeTransferETH(excessRefundTo, address(this).balance);
        }
    }

    receive() external payable {}

    // =============================================================
    //               PUBLIC / EXTERNAL VIEW FUNCTIONS
    // =============================================================

    /**
     * @inheritdoc IERC165
     */
    function supportsInterface(bytes4 interfaceId) public pure override(IERC165) returns (bool) {
        return interfaceId == this.supportsInterface.selector || interfaceId == type(IMinterAdapter).interfaceId;
    }

    /**
     * @inheritdoc IMinterAdapter
     */
    function moduleInterfaceId() public pure returns (bytes4) {
        return type(IMinterAdapter).interfaceId;
    }
}

// File: contracts/modules/interfaces/IEditionMaxMinter.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

import { IMinterModule } from "@core/interfaces/IMinterModule.sol";

/**
 * @dev Data unique to a edition max mint.
 */
struct EditionMintData {
    // The price at which each token will be sold, in ETH.
    uint96 price;
    // The maximum number of tokens that a wallet can mint.
    uint32 maxMintablePerAccount;
}

/**
 * @dev All the information about a edition max mint (combines EditionMintData with BaseData).
 */
struct MintInfo {
    uint32 startTime;
    uint32 endTime;
    uint16 affiliateFeeBPS;
    bool mintPaused;
    uint96 price;
    uint32 maxMintablePerAccount;
    uint32 maxMintableLower;
    uint32 maxMintableUpper;
    uint32 cutoffTime;
}

/**
 * @title IEditionMaxMinter
 * @dev Interface for the `EditionMaxMinter` module.
 * @author Sound.xyz
 */
interface IEditionMaxMinter is IMinterModule {
    // =============================================================
    //                            EVENTS
    // =============================================================

    /**
     * @dev Emitted when a edition max is created.
     * @param edition               Address of the song edition contract we are minting for.
     * @param mintId                The mint ID.
     * @param price                 Sale price in ETH for minting a single token in `edition`.
     * @param startTime             Start timestamp of sale (in seconds since unix epoch).
     * @param endTime               End timestamp of sale (in seconds since unix epoch).
     * @param affiliateFeeBPS       The affiliate fee in basis points.
     * @param maxMintablePerAccount The maximum number of tokens that can be minted per account.
     */
    event EditionMaxMintCreated(
        address indexed edition,
        uint128 indexed mintId,
        uint96 price,
        uint32 startTime,
        uint32 endTime,
        uint16 affiliateFeeBPS,
        uint32 maxMintablePerAccount
    );

    /**
     * @dev Emitted when the `price` is changed for (`edition`, `mintId`).
     * @param edition Address of the song edition contract we are minting for.
     * @param mintId  The mint ID.
     * @param price   Sale price in ETH for minting a single token in `edition`.
     */
    event PriceSet(address indexed edition, uint128 indexed mintId, uint96 price);

    /**
     * @dev Emitted when the `maxMintablePerAccount` is changed for (`edition`, `mintId`).
     * @param edition               Address of the song edition contract we are minting for.
     * @param mintId                The mint ID.
     * @param maxMintablePerAccount The maximum number of tokens that can be minted per account.
     */
    event MaxMintablePerAccountSet(address indexed edition, uint128 indexed mintId, uint32 maxMintablePerAccount);

    // =============================================================
    //                            ERRORS
    // =============================================================

    /**
     * @dev The number of tokens minted has exceeded the number allowed for each account.
     */
    error ExceedsMaxPerAccount();

    /**
     * @dev The max mintable per account cannot be zero.
     */
    error MaxMintablePerAccountIsZero();

    // =============================================================
    //               PUBLIC / EXTERNAL WRITE FUNCTIONS
    // =============================================================

    /*
     * @dev Initializes a range mint instance
     * @param edition               Address of the song edition contract we are minting for.
     * @param price                 Sale price in ETH for minting a single token in `edition`.
     * @param startTime             Start timestamp of sale (in seconds since unix epoch).
     * @param endTime               End timestamp of sale (in seconds since unix epoch).
     * @param affiliateFeeBPS       The affiliate fee in basis points.
     * @param maxMintableLower      The lower limit of the maximum number of tokens that can be minted.
     * @param maxMintableUpper      The upper limit of the maximum number of tokens that can be minted.
     * @param maxMintablePerAccount The maximum number of tokens that can be minted by an account.
     * @return mintId The ID for the new mint instance.
     */
    function createEditionMint(
        address edition,
        uint96 price,
        uint32 startTime,
        uint32 endTime,
        uint16 affiliateFeeBPS,
        uint32 maxMintablePerAccount
    ) external returns (uint128 mintId);

    /*
     * @dev Mints tokens for a given edition.
     * @param edition   Address of the song edition contract we are minting for.
     * @param mintId    The mint ID.
     * @param quantity  Token quantity to mint in song `edition`.
     * @param affiliate The affiliate address.
     */
    function mint(
        address edition,
        uint128 mintId,
        uint32 quantity,
        address affiliate
    ) external payable;

    /*
     * @dev Sets the `price` for (`edition`, `mintId`).
     * @param edition Address of the song edition contract we are minting for.
     * @param mintId  The mint ID.
     * @param price   Sale price in ETH for minting a single token in `edition`.
     */
    function setPrice(
        address edition,
        uint128 mintId,
        uint96 price
    ) external;

    /*
     * @dev Sets the `maxMintablePerAccount` for (`edition`, `mintId`).
     * @param edition               Address of the song edition contract we are minting for.
     * @param mintId                The mint ID.
     * @param maxMintablePerAccount The maximum number of tokens that can be minted by an account.
     */
    function setMaxMintablePerAccount(
        address edition,
        uint128 mintId,
        uint32 maxMintablePerAccount
    ) external;

    // =============================================================
    //               PUBLIC / EXTERNAL VIEW FUNCTIONS
    // =============================================================

    /**
     * @dev Returns {IEditionMaxMinter.MintInfo} instance containing the full minter parameter set.
     * @param edition The edition to get the mint instance for.
     * @param mintId  The ID of the mint instance.
     * @return mintInfo Information about this mint.
     */
    function mintInfo(address edition, uint128 mintId) external view returns (MintInfo memory);
}

// File: contracts/modules/interfaces/IMinterAdapter.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

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

/**
 * @title IMinterAdapter
 * @dev Interface for the `MinterAdapter` module.
 * @author Sound.xyz
 */
interface IMinterAdapter is IERC165 {
    // =============================================================
    //                            EVENTS
    // =============================================================

    /**
     * @dev Emitted when a mint happens via the minter adapter.
     * @param minter        The address of the EditionMaxMinterV2 or RangeEditionMinterV2.
     * @param edition       The address of the edition.
     * @param fromTokenId   The starting token ID in the batch minted.
     * @param quantity      The number of tokens minted.
     * @param to            The address to mint the tokens to.
     * @param attributionId The attribution ID.
     */
    event AdapterMinted(
        address minter,
        address indexed edition,
        uint256 indexed fromTokenId,
        uint32 quantity,
        address to,
        uint256 indexed attributionId
    );

    // =============================================================
    //               PUBLIC / EXTERNAL WRITE FUNCTIONS
    // =============================================================

    /**
     * @dev Mints tokens for a given edition.
     * @param minter         The address of the EditionMaxMinterV2 or RangeEditionMinterV2.
     * @param edition        Address of the song edition contract we are minting for.
     * @param mintId         The mint ID.
     * @param to             The address to mint to.
     * @param quantity       Token quantity to mint in song `edition`.
     * @param affiliate      The affiliate address.
     * @param attributionId  The attribution ID.
     */
    function mintTo(
        address minter,
        address edition,
        uint128 mintId,
        address to,
        uint32 quantity,
        address affiliate,
        uint256 attributionId
    ) external payable;

    /**
     * @dev Buys tokens from the Sound Automated Market (SAM).
     * @param sam            The address of the SAM contract.
     * @param edition        Address of the song edition contract we are minting for.
     * @param to             The address to mint to.
     * @param quantity       Token quantity to mint in song `edition`.
     * @param affiliate      The affiliate address.
     * @param affiliateProof The Merkle proof for the affiliate.
     * @param attributionId  The attribution ID.
     * @param excessRefundTo The address to refund excess ETH to.
     */
    function samBuy(
        address sam,
        address edition,
        address to,
        uint32 quantity,
        address affiliate,
        bytes32[] calldata affiliateProof,
        uint256 attributionId,
        address excessRefundTo
    ) external payable;

    // =============================================================
    //               PUBLIC / EXTERNAL VIEW FUNCTIONS
    // =============================================================

    /**
     * @dev The interface ID of the minter.
     * @return The constant value.
     */
    function moduleInterfaceId() external view returns (bytes4);
}

// File: contracts/modules/interfaces/ISAM.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.16;

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

/**
 * @dev Data unique to a Sound Automated Market (i.e. bonding curve mint).
 */
struct SAMInfo {
    uint96 basePrice;
    uint128 linearPriceSlope;
    uint128 inflectionPrice;
    uint32 inflectionPoint;
    uint128 goldenEggFeesAccrued;
    uint128 balance;
    uint32 supply;
    uint32 maxSupply;
    uint32 buyFreezeTime;
    uint16 artistFeeBPS;
    uint16 affiliateFeeBPS;
    uint16 goldenEggFeeBPS;
    bytes32 affiliateMerkleRoot;
}

/**
 * @title ISAM
 * @dev Interface for the Sound Automated Market module.
 * @author Sound.xyz
 */
interface ISAM is IERC165 {
    // =============================================================
    //                            STRUCTS
    // =============================================================

    struct SAMData {
        // The sigmoid inflection price of the bonding curve.
        uint128 inflectionPrice;
        // The price added to the bonding curve price.
        uint96 basePrice;
        // The sigmoid inflection point of the bonding curve.
        uint32 inflectionPoint;
        // The amount of fees accrued by the golden egg.
        uint112 goldenEggFeesAccrued;
        // The balance of the pool for the edition.
        // 112 bits is enough to represent 5,192,296,858,534,828 ETH.
        // At the point of writing, there are 120,479,006 ETH in Ethereum mainnet,
        // and 9,050,469,069 MATIC in Polygon PoS chain.
        uint112 balance;
        // The amount of tokens in the bonding curve.
        uint32 supply;
        // The slope for the additional linear component to the bonding curve price.
        uint128 linearPriceSlope;
        // The supply cap for buying tokens.
        // Note: The supply can go over the cap if the cap is manually decreased.
        uint32 maxSupply;
        // The cutoff time for buying tokens.
        uint32 buyFreezeTime;
        // The fee BPS (basis points) to pay the artist.
        uint16 artistFeeBPS;
        // The fee BPS (basis points) to pay affiliates.
        uint16 affiliateFeeBPS;
        // The fee BPS (basis points) to pay the golden egg holder.
        uint16 goldenEggFeeBPS;
        // Whether a token has already been minted on the bonding curve.
        bool hasMinted;
        // Whether the SAM has been created.
        bool created;
        // The affiliate Merkle root, if any.
        bytes32 affiliateMerkleRoot;
    }

    // =============================================================
    //                            EVENTS
    // =============================================================

    /**
     * @dev Emitted when a bonding curve is created.
     * @param edition          The edition address.
     * @param linearPriceSlope The linear price slope of the bonding curve.
     * @param inflectionPrice  The sigmoid inflection price of the bonding curve.
     * @param inflectionPoint  The sigmoid inflection point of the bonding curve.
     * @param maxSupply        The supply cap for buying tokens.
     * @param buyFreezeTime    The cutoff time for buying tokens.
     * @param artistFeeBPS     The fee BPS (basis points) to pay the artist.
     * @param goldenEggFeeBPS  The fee BPS (basis points) to pay the golden egg holder.
     * @param affiliateFeeBPS  The fee BPS (basis points) to pay affiliates.
     */
    event Created(
        address indexed edition,
        uint96 basePrice,
        uint128 linearPriceSlope,
        uint128 inflectionPrice,
        uint32 inflectionPoint,
        uint32 maxSupply,
        uint32 buyFreezeTime,
        uint16 artistFeeBPS,
        uint16 goldenEggFeeBPS,
        uint16 affiliateFeeBPS
    );

    /**
     * @dev Emitted when tokens are bought from the bonding curve.
     * @param edition         The edition address.
     * @param buyer           Address of the buyer.
     * @param fromTokenId     The starting token ID minted for the batch.
     * @param fromCurveSupply The start of the curve supply for the batch.
     * @param quantity        The number of tokens bought.
     * @param totalPayment    The total amount of ETH paid.
     * @param platformFee     The cut paid to the platform.
     * @param artistFee       The cut paid to the artist.
     * @param goldenEggFee    The cut paid to the golden egg.
     * @param affiliateFee    The cut paid to the affiliate.
     * @param affiliate       The affiliate's address.
     * @param affiliated      Whether the affiliate is affiliated.
     * @param attributionId   The attribution ID.
     */
    event Bought(
        address indexed edition,
        address indexed buyer,
        uint256 fromTokenId,
        uint32 fromCurveSupply,
        uint32 quantity,
        uint128 totalPayment,
        uint128 platformFee,
        uint128 artistFee,
        uint128 goldenEggFee,
        uint128 affiliateFee,
        address affiliate,
        bool affiliated,
        uint256 indexed attributionId
    );

    /**
     * @dev Emitted when tokens are sold into the bonding curve.
     * @param edition         The edition address.
     * @param seller          Address of the seller.
     * @param fromCurveSupply The start of the curve supply for the batch.
     * @param tokenIds        The token IDs burned.
     * @param totalPayout     The total amount of ETH paid out.
     * @param attributionId   The attribution ID.
     */
    event Sold(
        address indexed edition,
        address indexed seller,
        uint32 fromCurveSupply,
        uint256[] tokenIds,
        uint128 totalPayout,
        uint256 indexed attributionId
    );

    /**
     * @dev Emitted when the `basePrice` is updated.
     * @param edition   The edition address.
     * @param basePrice The price added to the bonding curve price.
     */
    event BasePriceSet(address indexed edition, uint96 basePrice);

    /**
     * @dev Emitted when the `linearPriceSlope` is updated.
     * @param edition          The edition address.
     * @param linearPriceSlope The linear price slope of the bonding curve.
     */
    event LinearPriceSlopeSet(address indexed edition, uint128 linearPriceSlope);

    /**
     * @dev Emitted when the `inflectionPrice` is updated.
     * @param edition         The edition address.
     * @param inflectionPrice The sigmoid inflection price of the bonding curve.
     */
    event InflectionPriceSet(address indexed edition, uint128 inflectionPrice);

    /**
     * @dev Emitted when the `inflectionPoint` is updated.
     * @param edition         The edition address.
     * @param inflectionPoint The sigmoid inflection point of the bonding curve.
     */
    event InflectionPointSet(address indexed edition, uint32 inflectionPoint);

    /**
     * @dev Emitted when the `artistFeeBPS` is updated.
     * @param edition The edition address.
     * @param bps     The affiliate fee basis points.
     */
    event ArtistFeeSet(address indexed edition, uint16 bps);

    /**
     * @dev Emitted when the `affiliateFeeBPS` is updated.
     * @param edition The edition address.
     * @param bps     The affiliate fee basis points.
     */
    event AffiliateFeeSet(address indexed edition, uint16 bps);

    /**
     * @dev Emitted when the Merkle root for an affiliate allow list is updated.
     * @param edition The edition address.
     * @param root    The Merkle root for the affiliate allow list.
     */
    event AffiliateMerkleRootSet(address indexed edition, bytes32 root);

    /**
     * @dev Emitted when the `goldenEggFeeBPS` is updated.
     * @param edition The edition address.
     * @param bps     The golden egg fee basis points.
     */
    event GoldenEggFeeSet(address indexed edition, uint16 bps);

    /**
     * @dev Emitted when the `maxSupply` updated.
     * @param edition The edition address.
     */
    event MaxSupplySet(address indexed edition, uint32 maxSupply);

    /**
     * @dev Emitted when the `buyFreezeTime` updated.
     * @param edition The edition address.
     */
    event BuyFreezeTimeSet(address indexed edition, uint32 buyFreezeTime);

    /**
     * @dev Emitted when the `platformFeeBPS` is updated.
     * @param bps The platform fee basis points.
     */
    event PlatformFeeSet(uint16 bps);

    /**
     * @dev Emitted when the `platformFeeAddress` is updated.
     * @param addr The platform fee address.
     */
    event PlatformFeeAddressSet(address addr);

    /**
     * @dev Emitted when the accrued fees for `affiliate` are withdrawn.
     * @param affiliate The affiliate address.
     * @param accrued   The amount of fees withdrawn.
     */
    event AffiliateFeesWithdrawn(address indexed affiliate, uint256 accrued);

    /**
     * @dev Emitted when the accrued fees for the golden egg of `edition` are withdrawn.
     * @param edition    The edition address.
     * @param receipient The receipient.
     * @param accrued    The amount of fees withdrawn.
     */
    event GoldenEggFeesWithdrawn(address indexed edition, address indexed receipient, uint128 accrued);

    /**
     * @dev Emitted when the accrued fees for the platform are withdrawn.
     * @param accrued The amount of fees withdrawn.
     */
    event PlatformFeesWithdrawn(uint128 accrued);

    /**
     * @dev Emitted when the approved factories are set.
     * @param factories The list of approved factories.
     */
    event ApprovedEditionFactoriesSet(address[] factories);

    // =============================================================
    //                            ERRORS
    // =============================================================

    /**
     * @dev The Ether value paid is below the value required.
     * @param paid     The amount sent to the contract.
     * @param required The amount required.
     */
    error Underpaid(uint256 paid, uint256 required);

    /**
     * @dev The Ether value paid out is below the value required.
     * @param payout   The amount to pau out..
     * @param required The amount required.
     */
    error InsufficientPayout(uint256 payout, uint256 required);

    /**
     * @dev There is not enough tokens in the Sound Automated Market for selling back.
     * @param available The number of tokens in the Sound Automated Market.
     * @param required  The amount of tokens required.
     */
    error InsufficientSupply(uint256 available, uint256 required);

    /**
     * @dev Cannot perform the operation during the SAM phase.
     */
    error InSAMPhase();

    /**
     * @dev The inflection price cannot be zero.
     */
    error InflectionPriceIsZero();

    /**
     * @dev The inflection point cannot be zero.
     */
    error InflectionPointIsZero();

    /**
     * @dev The max supply cannot be increased after the SAM has started.
     *      In the `create` function, the initial max supply cannot be zero.
     */
    error InvalidMaxSupply();

    /**
     * @dev The buy freeze time cannot be increased after the SAM has started.
     *      In the `create` function, the initial buy freeze time cannot be zero.
     */
    error InvalidBuyFreezeTime();

    /**
     * @dev The BPS for the fee cannot exceed the `MAX_PLATFORM_FEE_BPS`.
     */
    error InvalidPlatformFeeBPS();

    /**
     * @dev The BPS for the fee cannot exceed the `MAX_ARTIST_FEE_BPS`.
     */
    error InvalidArtistFeeBPS();

    /**
     * @dev The BPS for the fee cannot exceed the `MAX_AFFILAITE_FEE_BPS`.
     */
    error InvalidAffiliateFeeBPS();

    /**
     * @dev The BPS for the fee cannot exceed the `MAX_GOLDEN_EGG_FEE_BPS`.
     */
    error InvalidGoldenEggFeeBPS();

    /**
     * @dev The `affiliate` provided is invalid for the given `affiliateProof`.
     */
    error InvalidAffiliate();

    /**
     * @dev Cannot buy.
     */
    error BuyIsFrozen();

    /**
     * @dev The purchase cannot exceed the max supply.
     * @param available The number of tokens remaining available for mint.
     */
    error ExceedsMaxSupply(uint32 available);

    /**
     * @dev The platform fee address cannot be zero.
     */
    error PlatformFeeAddressIsZero();

    /**
     * @dev There already is a Sound Automated Market for `edition`.
     */
    error SAMAlreadyExists();

    /**
     * @dev There is no Sound Automated Market for `edition`.
     */
    error SAMDoesNotExist();

    /**
     * @dev Cannot mint zero tokens.
     */
    error MintZeroQuantity();

    /**
     * @dev Cannot burn zero tokens.
     */
    error BurnZeroQuantity();

    /**
     * @dev The bytecode hash of the edition is not approved.
     */
    error UnapprovedEdition();

    // =============================================================
    //               PUBLIC / EXTERNAL WRITE FUNCTIONS
    // =============================================================

    /**
     * @dev Creates a Sound Automated Market on `edition`.
     * @param edition          The edition address.
     * @param basePrice        The price added to the bonding curve price.
     * @param linearPriceSlope The linear price slope of the bonding curve.
     * @param inflectionPrice  The sigmoid inflection price of the bonding curve.
     * @param inflectionPoint  The sigmoid inflection point of the bonding curve.
     * @param maxSupply        The supply cap for buying tokens.
     * @param buyFreezeTime    The cutoff time for buying tokens.
     * @param artistFeeBPS     The fee BPS (basis points) to pay the artist.
     * @param goldenEggFeeBPS  The fee BPS (basis points) to pay the golden egg holder.
     * @param affiliateFeeBPS  The fee BPS (basis points) to pay affiliates.
     * @param editionBy        The address which created the edition via the factory.
     * @param editionSalt      The salt used to create the edition via the factory.
     */
    function create(
        address edition,
        uint96 basePrice,
        uint128 linearPriceSlope,
        uint128 inflectionPrice,
        uint32 inflectionPoint,
        uint32 maxSupply,
        uint32 buyFreezeTime,
        uint16 artistFeeBPS,
        uint16 goldenEggFeeBPS,
        uint16 affiliateFeeBPS,
        address editionBy,
        bytes32 editionSalt
    ) external;

    /**
     * @dev Mints (buys) tokens for a given edition.
     * @param edition        The edition address.
     * @param to             The address to mint to.
     * @param quantity       Token quantity to mint in song `edition`.
     * @param affiliate      The affiliate address.
     * @param affiliateProof The Merkle proof needed for verifying the affiliate, if any.
     * @param attributionId  The attribution ID.
     */
    function buy(
        address edition,
        address to,
        uint32 quantity,
        address affiliate,
        bytes32[] calldata affiliateProof,
        uint256 attributionId
    ) external payable;

    /**
     * @dev Burns (sell) tokens for a given edition.
     * @param edition       The edition address.
     * @param tokenIds      The token IDs to burn.
     * @param minimumPayout The minimum payout for the transaction to succeed.
     * @param payoutTo      The address to send the payout to.
     * @param attributionId The attribution ID.
     */
    function sell(
        address edition,
        uint256[] calldata tokenIds,
        uint256 minimumPayout,
        address payoutTo,
        uint256 attributionId
    ) external;

    /**
     * @dev Sets the base price for `edition`.
     * This will be added to the bonding curve price.
     *
     * Calling conditions:
     * - The caller must be the edition's owner or admin.
     *
     * @param edition   The edition address.
     * @param basePrice The price added to the bonding curve price.
     */
    function setBasePrice(address edition, uint96 basePrice) external;

    /**
     * @dev Sets the linear price slope for `edition`.
     *
     * Calling conditions:
     * - The caller must be the edition's owner or admin.
     *
     * @param edition          The edition address.
     * @param linearPriceSlope The linear price slope of the bonding curve.
     */
    function setLinearPriceSlope(address edition, uint128 linearPriceSlope) external;

    /**
     * @dev Sets the bonding curve inflection price for `edition`.
     *
     * Calling conditions:
     * - The caller must be the edition's owner or admin.
     *
     * @param edition         The edition address.
     * @param inflectionPrice The sigmoid inflection price of the bonding curve.
     */
    function setInflectionPrice(address edition, uint128 inflectionPrice) external;

    /**
     * @dev Sets the bonding curve inflection point for `edition`.
     *
     * Calling conditions:
     * - The caller must be the edition's owner or admin.
     *
     * @param edition         The edition address.
     * @param inflectionPoint The sigmoid inflection point of the bonding curve.
     */
    function setInflectionPoint(address edition, uint32 inflectionPoint) external;

    /**
     * @dev Sets the artist fee for `edition`.
     *
     * Calling conditions:
     * - The caller must be the edition's owner or admin.
     *
     * @param edition The edition address.
     * @param bps     The artist fee in basis points.
     */
    function setArtistFee(address edition, uint16 bps) external;

    /**
     * @dev Sets the affiliate fee for `edition`.
     *
     * Calling conditions:
     * - The caller must be the edition's owner or admin.
     *
     * @param edition The edition address.
     * @param bps     The affiliate fee in basis points.
     */
    function setAffiliateFee(address edition, uint16 bps) external;

    /**
     * @dev Sets the affiliate Merkle root for (`edition`, `mintId`).
     *
     * Calling conditions:
     * - The caller must be the edition's owner or admin.
     *
     * @param edition The edition address.
     * @param root    The affiliate Merkle root, if any.
     */
    function setAffiliateMerkleRoot(address edition, bytes32 root) external;

    /**
     * @dev Sets the golden egg fee for `edition`.
     *
     * Calling conditions:
     * - The caller must be the edition's owner or admin.
     *
     * @param edition The edition address.
     * @param bps     The golden egg fee in basis points.
     */
    function setGoldenEggFee(address edition, uint16 bps) external;

    /**
     * @dev Sets the supply cap for `edition`.
     *
     * Calling conditions:
     * - The caller must be the edition's owner or admin.
     *
     * @param edition The edition address.
     */
    function setMaxSupply(address edition, uint32 maxSupply) external;

    /**
     * @dev Sets the buy freeze time for `edition`.
     *
     * Calling conditions:
     * - The caller must be the edition's owner or admin.
     *
     * @param edition The edition address.
     */
    function setBuyFreezeTime(address edition, uint32 buyFreezeTime) external;

    /**
     * @dev Withdraws all the accrued fees for `affiliate`.
     * @param affiliate The affiliate address.
     */
    function withdrawForAffiliate(address affiliate) external;

    /**
     * @dev Withdraws all the accrued fees for the platform.
     */
    function withdrawForPlatform() external;

    /**
     * @dev Withdraws all the accrued fees for the golden egg.
     * @param edition The edition address.
     */
    function withdrawForGoldenEgg(address edition) external;

    /**
     * @dev Sets the platform fee bps.
     *
     * Calling conditions:
     * - The caller must be the owner of the contract.
     *
     * @param bps The platform fee in basis points.
     */
    function setPlatformFee(uint16 bps) external;

    /**
     * @dev Sets the platform fee address.
     *
     * Calling conditions:
     * - The caller must be the owner of the contract.
     *
     * @param addr The platform fee address.
     */
    function setPlatformFeeAddress(address addr) external;

    /**
     * @dev Sets the list of approved edition factories.
     *
     * Calling conditions:
     * - The caller must be the owner of the contract.
     *
     * @param factories The list of approved edition factories.
     */
    function setApprovedEditionFactories(address[] calldata factories) external;

    // =============================================================
    //               PUBLIC / EXTERNAL VIEW FUNCTIONS
    // =============================================================

    /**
     * @dev This is the denominator, in basis points (BPS), for any of the fees.
     * @return The constant value.
     */
    function BPS_DENOMINATOR() external pure returns (uint16);

    /**
     * @dev The maximum basis points (BPS) limit allowed for the platform fees.
     * @return The constant value.
     */
    function MAX_PLATFORM_FEE_BPS() external pure returns (uint16);

    /**
     * @dev The maximum basis points (BPS) limit allowed for the artist fees.
     * @return The constant value.
     */
    function MAX_ARTIST_FEE_BPS() external pure returns (uint16);

    /**
     * @dev The maximum basis points (BPS) limit allowed for the affiliate fees.
     * @return The constant value.
     */
    function MAX_AFFILIATE_FEE_BPS() external pure returns (uint16);

    /**
     * @dev The maximum basis points (BPS) limit allowed for the golden egg fees.
     * @return The constant value.
     */
    function MAX_GOLDEN_EGG_FEE_BPS() external pure returns (uint16);

    /**
     * @dev Returns the platform fee basis points.
     * @return The configured value.
     */
    function platformFeeBPS() external returns (uint16);

    /**
     * @dev Returns the platform fee address.
     * @return The configured value.
     */
    function platformFeeAddress() external returns (address);

    /**
     * @dev Returns the information for the Sound Automated Market for `edition`.
     * @param edition The edition address.
     * @return The latest value.
     */
    function samInfo(address edition) external view returns (SAMInfo memory);

    /**
     * @dev Returns the total value under the bonding curve for `quantity`, from `fromSupply`.
     * @param edition    The edition address.
     * @param fromSupply The starting number of tokens in the bonding curve.
     * @param quantity   The number of tokens.
     * @return The computed value.
     */
    function totalValue(
        address edition,
        uint32 fromSupply,
        uint32 quantity
    ) external view returns (uint256);

    /**
     * @dev Returns the total amount of ETH required to buy from
     *      `supply + supplyForwardOffset` to `supply + supplyForwardOffset + quantity`.
     * @param edition             The edition address.
     * @param supplyForwardOffset The offset added to the current supply.
     * @param quantity            The number of tokens.
     * @return total        The total amount required to be paid, inclusive of all the buy fees.
     * @return platformFee  The platform fee.
     * @return artistFee    The artist fee.
     * @return goldenEggFee The golden egg fee.
     * @return affiliateFee The affiliate fee.
     */
    function totalBuyPriceAndFees(
        address edition,
        uint32 supplyForwardOffset,
        uint32 quantity
    )
        external
        view
        returns (
            uint256 total,
            uint256 platformFee,
            uint256 artistFee,
            uint256 goldenEggFee,
            uint256 affiliateFee
        );

    /**
     * @dev Returns the total amount of ETH required to sell from
     *      `supply - supplyBackwardOffset` to `supply - supplyBackwardOffset - quantity`.
     * @param edition              The edition address.
     * @param supplyBackwardOffset The offset added to the current supply.
     * @param quantity             The number of tokens.
     * @return The computed value.
     */
    function totalSellPrice(
        address edition,
        uint32 supplyBackwardOffset,
        uint32 quantity
    ) external view returns (uint256);

    /**
     * @dev The total fees accrued for the golden egg on `edition`.
     * @param edition The edition address.
     * @return The latest value.
     */
    function goldenEggFeesAccrued(address edition) external view returns (uint128);

    /**
     * @dev The receipient of the golden egg fees on `edition`.
     *      If there is no golden egg winner, the `receipient` will be the `edition`.
     * @param edition The edition address.
     * @return receipient The latest value.
     */
    function goldenEggFeeRecipient(address edition) external view returns (address receipient);

    /**
     * @dev The total fees accrued for `affiliate`.
     * @param affiliate The affiliate's address.
     * @return The latest value.
     */
    function affiliateFeesAccrued(address affiliate) external view returns (uint128);

    /**
     * @dev The total fees accrued for the platform.
     * @return The latest value.
     */
    function platformFeesAccrued() external view returns (uint128);

    /**
     * @dev Whether `affiliate` is affiliated for `edition`.
     * @param edition        The edition's address.
     * @param affiliate      The affiliate's address.
     * @param affiliateProof The Merkle proof needed for verifying the affiliate, if any.
     * @return The computed value.
     */
    function isAffiliatedWithProof(
        address edition,
        address affiliate,
        bytes32[] calldata affiliateProof
    ) external view returns (bool);

    /**
     * @dev Whether `affiliate` is affiliated for `edition`.
     * @param edition   The edition's address.
     * @param affiliate The affiliate's address.
     * @return The computed value.
     */
    function isAffiliated(address edition, address affiliate) external view returns (bool);

    /**
     * @dev Returns the list of approved edition factories.
     * @return The latest values.
     */
    function approvedEditionFactories() external view returns (address[] memory);

    /**
     * @dev Returns the affiliate Merkle root.
     * @param edition The edition's address.
     * @return The latest value.
     */
    function affiliateMerkleRoot(address edition) external view returns (bytes32);

    /**
     * @dev Returns the module's interface ID.
     * @return The constant value.
     */
    function moduleInterfaceId() external pure returns (bytes4);
}

// File: lib/ERC721A-Upgradeable/contracts/IERC721AUpgradeable.sol



// SPDX-License-Identifier: MIT
// ERC721A Contracts v4.2.3
// Creator: Chiru Labs

pragma solidity ^0.8.4;

/**
 * @dev Interface of ERC721A.
 */
interface IERC721AUpgradeable {
    /**
     * The caller must own the token or be an approved operator.
     */
    error ApprovalCallerNotOwnerNorApproved();

    /**
     * The token does not exist.
     */
    error ApprovalQueryForNonexistentToken();

    /**
     * Cannot query the balance for the zero address.
     */
    error BalanceQueryForZeroAddress();

    /**
     * Cannot mint to the zero address.
     */
    error MintToZeroAddress();

    /**
     * The quantity of tokens minted must be more than zero.
     */
    error MintZeroQuantity();

    /**
     * The token does not exist.
     */
    error OwnerQueryForNonexistentToken();

    /**
     * The caller must own the token or be an approved operator.
     */
    error TransferCallerNotOwnerNorApproved();

    /**
     * The token must be owned by `from`.
     */
    error TransferFromIncorrectOwner();

    /**
     * Cannot safely transfer to a contract that does not implement the
     * ERC721Receiver interface.
     */
    error TransferToNonERC721ReceiverImplementer();

    /**
     * Cannot transfer to the zero address.
     */
    error TransferToZeroAddress();

    /**
     * The token does not exist.
     */
    error URIQueryForNonexistentToken();

    /**
     * The `quantity` minted with ERC2309 exceeds the safety limit.
     */
    error MintERC2309QuantityExceedsLimit();

    /**
     * The `extraData` cannot be set on an unintialized ownership slot.
     */
    error OwnershipNotInitializedForExtraData();

    // =============================================================
    //                            STRUCTS
    // =============================================================

    struct TokenOwnership {
        // The address of the owner.
        address addr;
        // Stores the start time of ownership with minimal overhead for tokenomics.
        uint64 startTimestamp;
        // Whether the token has been burned.
        bool burned;
        // Arbitrary data similar to `startTimestamp` that can be set via {_extraData}.
        uint24 extraData;
    }

    // =============================================================
    //                         TOKEN COUNTERS
    // =============================================================

    /**
     * @dev Returns the total number of tokens in existence.
     * Burned tokens will reduce the count.
     * To get the total number of tokens minted, please see {_totalMinted}.
     */
    function totalSupply() external view returns (uint256);

    // =============================================================
    //                            IERC165
    // =============================================================

    /**
     * @dev Returns true if this contract implements the interface defined by
     * `interfaceId`. See the corresponding
     * [EIP section](https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified)
     * to learn more about how these ids are created.
     *
     * This function call must use less than 30000 gas.
     */
    function supportsInterface(bytes4 interfaceId) external view returns (bool);

    // =============================================================
    //                            IERC721
    // =============================================================

    /**
     * @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`,
     * checking first that contract recipients are aware of the ERC721 protocol
     * to prevent tokens from being forever locked.
     *
     * Requirements:
     *
     * - `from` cannot be the zero address.
     * - `to` cannot be the zero address.
     * - `tokenId` token must exist and be owned by `from`.
     * - If the caller is not `from`, it must be 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,
        bytes calldata data
    ) external payable;

    /**
     * @dev Equivalent to `safeTransferFrom(from, to, tokenId, '')`.
     */
    function safeTransferFrom(
        address from,
        address to,
        uint256 tokenId
    ) external payable;

    /**
     * @dev Transfers `tokenId` from `from` to `to`.
     *
     * WARNING: Usage of this method is discouraged, use {safeTransferFrom}
     * whenever possible.
     *
     * 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 payable;

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

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

    // =============================================================
    //                        IERC721Metadata
    // =============================================================

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

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

    /**
     * @dev Returns the Uniform Resource Identifier (URI) for `tokenId` token.
     */
    function tokenURI(uint256 tokenId) external view returns (string memory);

    // =============================================================
    //                           IERC2309
    // =============================================================

    /**
     * @dev Emitted when tokens in `fromTokenId` to `toTokenId`
     * (inclusive) is transferred from `from` to `to`, as defined in the
     * [ERC2309](https://eips.ethereum.org/EIPS/eip-2309) standard.
     *
     * See {_mintERC2309} for more details.
     */
    event ConsecutiveTransfer(uint256 indexed fromTokenId, uint256 toTokenId, address indexed from, address indexed to);
}

// File: lib/openzeppelin-contracts/contracts/utils/introspection/IERC165.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

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

// File: lib/openzeppelin-contracts-upgradeable/contracts/interfaces/IERC2981Upgradeable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (interfaces/IERC2981.sol)

pragma solidity ^0.8.0;

import "../utils/introspection/IERC165Upgradeable.sol";

/**
 * @dev Interface for the NFT Royalty Standard.
 *
 * A standardized way to retrieve royalty payment information for non-fungible tokens (NFTs) to enable universal
 * support for royalty payments across all NFT marketplaces and ecosystem participants.
 *
 * _Available since v4.5._
 */
interface IERC2981Upgradeable is IERC165Upgradeable {
    /**
     * @dev Returns how much royalty is owed and to whom, based on a sale price that may be denominated in any unit of
     * exchange. The royalty amount is denominated and should be paid in that same unit of exchange.
     */
    function royaltyInfo(uint256 tokenId, uint256 salePrice)
        external
        view
        returns (address receiver, uint256 royaltyAmount);
}

// File: lib/openzeppelin-contracts-upgradeable/contracts/utils/introspection/IERC165Upgradeable.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)

pragma solidity ^0.8.0;

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

// File: lib/solady/src/utils/SafeTransferLib.sol



// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Caution! This library won't check that a token has code, responsibility is delegated to the caller.
library SafeTransferLib {
    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       CUSTOM ERRORS                        */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev The ETH transfer has failed.
    error ETHTransferFailed();

    /// @dev The ERC20 `transferFrom` has failed.
    error TransferFromFailed();

    /// @dev The ERC20 `transfer` has failed.
    error TransferFailed();

    /// @dev The ERC20 `approve` has failed.
    error ApproveFailed();

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                         CONSTANTS                          */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Suggested gas stipend for contract receiving ETH
    /// that disallows any storage writes.
    uint256 internal constant _GAS_STIPEND_NO_STORAGE_WRITES = 2300;

    /// @dev Suggested gas stipend for contract receiving ETH to perform a few
    /// storage reads and writes, but low enough to prevent griefing.
    /// Multiply by a small constant (e.g. 2), if needed.
    uint256 internal constant _GAS_STIPEND_NO_GRIEF = 100000;

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                       ETH OPERATIONS                       */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Sends `amount` (in wei) ETH to `to`.
    /// Reverts upon failure.
    function safeTransferETH(address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and check if it succeeded or not.
            if iszero(call(gas(), to, amount, 0, 0, 0, 0)) {
                // Store the function selector of `ETHTransferFailed()`.
                mstore(0x00, 0xb12d13eb)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    /// The `gasStipend` can be set to a low enough value to prevent
    /// storage writes or gas griefing.
    ///
    /// If sending via the normal procedure fails, force sends the ETH by
    /// creating a temporary contract which uses `SELFDESTRUCT` to force send the ETH.
    ///
    /// Reverts if the current contract has insufficient balance.
    function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
        /// @solidity memory-safe-assembly
        assembly {
            // If insufficient balance, revert.
            if lt(selfbalance(), amount) {
                // Store the function selector of `ETHTransferFailed()`.
                mstore(0x00, 0xb12d13eb)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Transfer the ETH and check if it succeeded or not.
            if iszero(call(gasStipend, to, amount, 0, 0, 0, 0)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                // We can directly use `SELFDESTRUCT` in the contract creation.
                // Compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758
                pop(create(amount, 0x0b, 0x16))
            }
        }
    }

    /// @dev Force sends `amount` (in wei) ETH to `to`, with a gas stipend
    /// equal to `_GAS_STIPEND_NO_GRIEF`. This gas stipend is a reasonable default
    /// for 99% of cases and can be overriden with the three-argument version of this
    /// function if necessary.
    ///
    /// If sending via the normal procedure fails, force sends the ETH by
    /// creating a temporary contract which uses `SELFDESTRUCT` to force send the ETH.
    ///
    /// Reverts if the current contract has insufficient balance.
    function forceSafeTransferETH(address to, uint256 amount) internal {
        // Manually inlined because the compiler doesn't inline functions with branches.
        /// @solidity memory-safe-assembly
        assembly {
            // If insufficient balance, revert.
            if lt(selfbalance(), amount) {
                // Store the function selector of `ETHTransferFailed()`.
                mstore(0x00, 0xb12d13eb)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Transfer the ETH and check if it succeeded or not.
            if iszero(call(_GAS_STIPEND_NO_GRIEF, to, amount, 0, 0, 0, 0)) {
                mstore(0x00, to) // Store the address in scratch space.
                mstore8(0x0b, 0x73) // Opcode `PUSH20`.
                mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
                // We can directly use `SELFDESTRUCT` in the contract creation.
                // Compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758
                pop(create(amount, 0x0b, 0x16))
            }
        }
    }

    /// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
    /// The `gasStipend` can be set to a low enough value to prevent
    /// storage writes or gas griefing.
    ///
    /// Simply use `gasleft()` for `gasStipend` if you don't need a gas stipend.
    ///
    /// Note: Does NOT revert upon failure.
    /// Returns whether the transfer of ETH is successful instead.
    function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
        internal
        returns (bool success)
    {
        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and check if it succeeded or not.
            success := call(gasStipend, to, amount, 0, 0, 0, 0)
        }
    }

    /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
    /*                      ERC20 OPERATIONS                      */
    /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

    /// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for
    /// the current contract to manage.
    function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.

            // Store the function selector of `transferFrom(address,address,uint256)`.
            mstore(0x00, 0x23b872dd)
            mstore(0x20, from) // Store the `from` argument.
            mstore(0x40, to) // Store the `to` argument.
            mstore(0x60, amount) // Store the `amount` argument.

            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    // Set success to whether the call reverted, if not we check it either
                    // returned exactly 1 (can't just be non-zero data), or had no return data.
                    or(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `TransferFromFailed()`.
                mstore(0x00, 0x7939f424)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends all of ERC20 `token` from `from` to `to`.
    /// Reverts upon failure.
    ///
    /// The `from` account must have at least `amount` approved for
    /// the current contract to manage.
    function safeTransferAllFrom(address token, address from, address to)
        internal
        returns (uint256 amount)
    {
        /// @solidity memory-safe-assembly
        assembly {
            let m := mload(0x40) // Cache the free memory pointer.

            mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
            mstore(0x20, from) // Store the `from` argument.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
                )
            ) {
                // Store the function selector of `TransferFromFailed()`.
                mstore(0x00, 0x7939f424)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            // Store the function selector of `transferFrom(address,address,uint256)`.
            mstore(0x00, 0x23b872dd)
            mstore(0x40, to) // Store the `to` argument.
            // The `amount` argument is already written to the memory word at 0x6a.
            amount := mload(0x60)

            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    // Set success to whether the call reverted, if not we check it either
                    // returned exactly 1 (can't just be non-zero data), or had no return data.
                    or(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `TransferFromFailed()`.
                mstore(0x00, 0x7939f424)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            mstore(0x60, 0) // Restore the zero slot to zero.
            mstore(0x40, m) // Restore the free memory pointer.
        }
    }

    /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransfer(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x1a, to) // Store the `to` argument.
            mstore(0x3a, amount) // Store the `amount` argument.
            // Store the function selector of `transfer(address,uint256)`,
            // left by 6 bytes (enough for 8tb of memory represented by the free memory pointer).
            // We waste 6-3 = 3 bytes to save on 6 runtime gas (PUSH1 0x224 SHL).
            mstore(0x00, 0xa9059cbb000000000000)

            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    // Set success to whether the call reverted, if not we check it either
                    // returned exactly 1 (can't just be non-zero data), or had no return data.
                    or(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x16, 0x44, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `TransferFailed()`.
                mstore(0x00, 0x90b8ec18)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Restore the part of the free memory pointer that was overwritten,
            // which is guaranteed to be zero, if less than 8tb of memory is used.
            mstore(0x3a, 0)
        }
    }

    /// @dev Sends all of ERC20 `token` from the current contract to `to`.
    /// Reverts upon failure.
    function safeTransferAll(address token, address to) internal returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
            mstore(0x20, address()) // Store the address of the current contract.
            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                    staticcall(gas(), token, 0x1c, 0x24, 0x3a, 0x20)
                )
            ) {
                // Store the function selector of `TransferFailed()`.
                mstore(0x00, 0x90b8ec18)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }

            mstore(0x1a, to) // Store the `to` argument.
            // The `amount` argument is already written to the memory word at 0x3a.
            amount := mload(0x3a)
            // Store the function selector of `transfer(address,uint256)`,
            // left by 6 bytes (enough for 8tb of memory represented by the free memory pointer).
            // We waste 6-3 = 3 bytes to save on 6 runtime gas (PUSH1 0x224 SHL).
            mstore(0x00, 0xa9059cbb000000000000)

            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    // Set success to whether the call reverted, if not we check it either
                    // returned exactly 1 (can't just be non-zero data), or had no return data.
                    or(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x16, 0x44, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `TransferFailed()`.
                mstore(0x00, 0x90b8ec18)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Restore the part of the free memory pointer that was overwritten,
            // which is guaranteed to be zero, if less than 8tb of memory is used.
            mstore(0x3a, 0)
        }
    }

    /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract.
    /// Reverts upon failure.
    function safeApprove(address token, address to, uint256 amount) internal {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x1a, to) // Store the `to` argument.
            mstore(0x3a, amount) // Store the `amount` argument.
            // Store the function selector of `approve(address,uint256)`,
            // left by 6 bytes (enough for 8tb of memory represented by the free memory pointer).
            // We waste 6-3 = 3 bytes to save on 6 runtime gas (PUSH1 0x224 SHL).
            mstore(0x00, 0x095ea7b3000000000000)

            if iszero(
                and( // The arguments of `and` are evaluated from right to left.
                    // Set success to whether the call reverted, if not we check it either
                    // returned exactly 1 (can't just be non-zero data), or had no return data.
                    or(eq(mload(0x00), 1), iszero(returndatasize())),
                    call(gas(), token, 0, 0x16, 0x44, 0x00, 0x20)
                )
            ) {
                // Store the function selector of `ApproveFailed()`.
                mstore(0x00, 0x3e3f8f73)
                // Revert with (offset, size).
                revert(0x1c, 0x04)
            }
            // Restore the part of the free memory pointer that was overwritten,
            // which is guaranteed to be zero, if less than 8tb of memory is used.
            mstore(0x3a, 0)
        }
    }

    /// @dev Returns the amount of ERC20 `token` owned by `account`.
    /// Returns zero if the `token` does not exist.
    function balanceOf(address token, address account) internal view returns (uint256 amount) {
        /// @solidity memory-safe-assembly
        assembly {
            mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`.
            mstore(0x20, account) // Store the `account` argument.
            amount :=
                mul(
                    mload(0x20),
                    and( // The arguments of `and` are evaluated from right to left.
                        gt(returndatasize(), 0x1f), // At least 32 bytes returned.
                        staticcall(gas(), token, 0x1c, 0x24, 0x20, 0x20)
                    )
                )
        }
    }
}