// File: @openzeppelin/contracts/security/ReentrancyGuard.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (security/ReentrancyGuard.sol)

pragma solidity ^0.8.0;

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

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

    uint256 private _status;

    constructor() {
        _status = _NOT_ENTERED;
    }

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

    function _nonReentrantBefore() private {
        // On the first call to nonReentrant, _status will be _NOT_ENTERED
        require(_status != _ENTERED, "ReentrancyGuard: reentrant call");

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

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

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

// File: contracts/deca-collections/DecaMintOnDemand.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";

import {ISettlementValidator} from "../interfaces/internal/ISettlementValidator.sol";

import {IDecaCollectionFactory} from "../interfaces/internal/IDecaCollectionFactory.sol";
import {IDecaCollection} from "../interfaces/internal/IDecaCollection.sol";
import {IDecaMintOnDemand} from "../interfaces/internal/IDecaMintOnDemand.sol";

import {MintStructs} from "./libraries/MintStructs.sol";

/**
 * @notice Allows a creator to mint an NFT to a bidder, without having to pay gas.
 * @dev Utilises EIP712 off-chain signatures to grant a bidder permission to mint a specific token id,
 *      which corresponds to an off-chain upload on Deca.
 * @author 0x-jj, j6i
 */
contract DecaMintOnDemand is ReentrancyGuard, IDecaMintOnDemand {
  /*//////////////////////////////////////////////////////////////
                              STORAGE
    //////////////////////////////////////////////////////////////*/

  /**
   * @notice Deca collection factory contract to create collections.
   */
  IDecaCollectionFactory public immutable factory;

  /**
   * @notice SettlementValidator contract to validate off-chain signatures.
   */
  ISettlementValidator public immutable settlementValidator;

  /*//////////////////////////////////////////////////////////////
                              CONSTRUCTOR
    //////////////////////////////////////////////////////////////*/

  constructor(address _factory, address _settlementValidator) {
    factory = IDecaCollectionFactory(_factory);
    settlementValidator = ISettlementValidator(_settlementValidator);
  }

  /*//////////////////////////////////////////////////////////////
                                 EXTERNAL
    //////////////////////////////////////////////////////////////*/

  /**
   * @notice Creates a new collection via the factory, validates and executes the settlement. If successful, mints the NFT to the bidder.
   *         and sends the creator the Ether.
   * @param settlement Contains all the info required to validate and execute settlement on chain.
   * @param collectionInfo CollectionInfo struct.
   * @param collectionInfoSignature Signature to verify against collection info
   */
  function createCollectionAndSettleBid(
    MintStructs.Settlement calldata settlement,
    MintStructs.CollectionInfo calldata collectionInfo,
    bytes calldata collectionInfoSignature
  ) external payable {
    settlementValidator.validateCollectionInfo(collectionInfo, collectionInfoSignature);

    address collection = factory.createDecaCollection(
      IDecaCollectionFactory.CreateDecaCollectionParams({
        creator: settlement.acceptanceInfo.creatorAddress,
        nonce: collectionInfo.nonce,
        recipients: collectionInfo.royaltyRecipients,
        collectionName: collectionInfo.collectionName,
        collectionSymbol: collectionInfo.collectionSymbol
      })
    );

    if (collection != settlement.acceptanceInfo.collectionAddress) {
      revert CollectionMismatch();
    }

    settleBid(settlement);
  }

  /*//////////////////////////////////////////////////////////////
                                 PUBLIC
    //////////////////////////////////////////////////////////////*/

  /**
   * @notice Validates and executes a settlement. If successful, mints the NFT to the bidder.
   *         and sends the creator the Ether.
   * @param settlement Contains all the info required to validate and execute settlement on chain.
   */
  function settleBid(MintStructs.Settlement calldata settlement) public payable nonReentrant {
    // Validate bid and acceptance criteria and signatures
    bytes32 bidHash = settlementValidator.validateSettlement(settlement, msg.value, msg.sender);

    emit BidSettled(
      settlement.acceptanceInfo.collectionAddress,
      settlement.bidInfo.bidderAddress,
      settlement.acceptanceInfo.creatorAddress,
      settlement.acceptanceInfo.payslips,
      bidHash,
      settlement.bidInfo.tokenId,
      settlement.bidInfo.priceInWei
    );

    // Mint NFT to bidder address
    IDecaCollection(settlement.acceptanceInfo.collectionAddress).mint(
      settlement.bidInfo.bidderAddress,
      settlement.bidInfo.tokenId
    );

    // Transfer ETH to payees
    uint256 totalPayout = 0;

    for (uint256 i = 0; i < settlement.acceptanceInfo.payslips.length; ) {
      MintStructs.Payslip memory payslip = settlement.acceptanceInfo.payslips[i];
      totalPayout += payslip.amountInWei;
      (bool paymentSuccessful, ) = payslip.recipient.call{value: payslip.amountInWei}("");
      if (!paymentSuccessful) {
        revert TransferFailed();
      }
      unchecked {
        i++;
      }
    }

    // Ensure payouts are correct
    if (totalPayout != settlement.bidInfo.priceInWei) {
      revert PayoutMismatch();
    }

    uint256 excess = msg.value - settlement.bidInfo.priceInWei;

    if (excess > 0) {
      (bool excessReturned, ) = settlement.bidInfo.bidderAddress.call{value: excess}("");
      if (!excessReturned) {
        revert TransferFailed();
      }
    }
  }
}

// File: contracts/deca-collections/libraries/MintStructs.sol



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

import {Recipient} from "../../interfaces/internal/IDecaCollection.sol";

/**
 * @title MintStructs
 */
library MintStructs {
  /**
   * @notice BidInfo is the struct that provides info about a bid on a Deca Collection item.
   * @dev Token ids are globally unique across all DecaCollections. Contract address is not part of the bid
   *      struct so that creators can move a token's collection before minting it, after receiving a bid
   * @param nonce Global user order nonce for maker orders
   * @param tokenId Id of the token to mint
   * @param expiresAt Timestamp bid expires at
   * @param priceInWei Bid price in wei
   * @param bidderAddress Maker address
   */
  struct BidInfo {
    uint256 nonce;
    uint256 tokenId;
    uint256 expiresAt;
    uint256 priceInWei;
    address bidderAddress;
  }

  /**
   * @notice Payslip is the struct for a payout on settlement.
   * @dev Payslips are paid out in order, if there is not enough balance to pay out the next payslip, the settlement fails.
   * @param amountInWei Amount to pay out in wei
   * @param recipient Address to pay out to
   */
  struct Payslip {
    uint256 amountInWei;
    address recipient;
  }

  /**
   * @notice AcceptanceInfo is the struct for a taker ask/bid order. It contains the parameters required for a direct purchase.
   * @dev AcceptanceInfo struct is matched against a BidInfo struct at the protocol level during settlement.
   * @param nonce Nonce to ensure acceptance signature is not re-used
   * @param bidSignatureHash BidInfo hash signed by taker
   * @param creatorAddress Address of the creator accepting the bid
   * @param collectionAddress Address to mint the token on
   * @param payslips Array of payslips to be paid out on settlement
   */
  struct AcceptanceInfo {
    uint256 nonce;
    bytes32 bidSignatureHash;
    address creatorAddress;
    address collectionAddress;
    Payslip[] payslips;
  }

  /**
   * @notice To enable gasless cancellation of bids, we provide a fast expiring signature that is required during settlement and acts as an off-chain mint pass.
   *        If a user tells Deca they want to cancel their bid, we mark it as cancelled internally and refuse to provide a signature for settlement.
   *        In case a user wants to cancel their bid without going through Deca in case the expiry time isn't short enough, they can do so on chain.
   * @dev bidSignatureHash and acceptanceSignature hashes act as nonces, if either has already been used settlement fails.
   * @param expiresAt Timestamp signature expires at
   * @param bidSignatureHash BidInfo is the struct that provides info about a bid on a Deca Collection item.
   * @param acceptanceSignatureHash AcceptanceInfo struct is matched against a BidInfo struct at the protocol level during settlement.
   * @param signer Address owned by Deca used to sign the message
   */
  struct MintPass {
    uint256 expiresAt;
    bytes32 bidSignatureHash;
    bytes32 acceptanceSignatureHash;
    address signer;
  }

  /**
   * @notice A summary of the settlement data required to mint on demand.
   * @param bidSignature Signature for the bid info
   * @param acceptanceSignature Signature for the acceptance info
   * @param mintPassSignature Signature for the mint pass
   * @param bidInfo BidInfo struct, signed by bidder
   * @param acceptanceInfo AcceptanceInfo struct, signed by creator
   * @param mintPass MintPass struct, signed by Deca
   */
  struct Settlement {
    bytes bidSignature;
    bytes acceptanceSignature;
    bytes mintPassSignature;
    BidInfo bidInfo;
    AcceptanceInfo acceptanceInfo;
    MintPass mintPass;
  }

  /**
   * @notice CollectionInfo is the struct that provides info about a collection being created.
   * @param signer Address owned by Deca used to sign the message
   * @param nonce Nonce for the collection, used to generate the collection address
   * @param collectionName Name of the collection being created
   * @param collectionSymbol Symbol of the collection being created
   * @param royaltyRecipients Array of secondary market royalty recipients
   */
  struct CollectionInfo {
    address signer;
    uint96 nonce;
    string collectionName;
    string collectionSymbol;
    Recipient[] royaltyRecipients;
  }

  /**
   * @notice This is the type hash constant used to compute the maker order hash.
   */
  bytes32 internal constant _BIDINFO_TYPEHASH =
    keccak256(
      "BidInfo("
      "uint256 nonce,"
      "uint256 tokenId,"
      "address bidderAddress,"
      "uint256 expiresAt,"
      "uint256 priceInWei"
      ")"
    );

  /**
   * @dev This is the type hash constant used to compute the taker order hash.
   */
  bytes32 internal constant _ACCEPTANCEINFO_TYPEHASH =
    keccak256(
      "AcceptanceInfo("
      "uint256 nonce,"
      "address creatorAddress,"
      "address collectionAddress,"
      "bytes32 bidSignatureHash,"
      "Payslip[] payslips"
      ")"
      "Payslip(address recipient,uint256 amountInWei)"
    );

  /**
   * @dev This is the type hash constant used to compute the payslip hash.
   */
  bytes32 internal constant _PAYSLIP_TYPEHASH =
    keccak256(
      "Payslip("
      "address recipient,"
      "uint256 amountInWei"
      ")"
    );

  /**
   * @dev This is the type hash constant used to compute the mint pass hash.
   */
  bytes32 internal constant _MINTPASS_TYPEHASH =
    keccak256(
      "MintPass("
      "address signer,"
      "uint256 expiresAt,"
      "bytes32 bidSignatureHash,"
      "bytes32 acceptanceSignatureHash"
      ")"
    );

  /**
   * @dev This is the type hash constant used to compute the collection info hash.
   */
  bytes32 internal constant _COLLECTIONINFO_TYPEHASH =
    keccak256(
      "CollectionInfo("
      "string collectionName,"
      "string collectionSymbol,"
      "uint96 nonce,"
      "address signer,"
      "Recipient[] royaltyRecipients"
      ")"
      "Recipient(address recipient,uint16 bps)"
    );

  /**
   * @dev This is the type hash constant used to compute the recipient hash.
   */
  bytes32 internal constant _RECIPIENT_TYPEHASH =
    keccak256(
      "Recipient("
      "address recipient,"
      "uint16 bps"
      ")"
    );

  /**
   * @notice This function is used to compute the EIP712 hash for a BidInfo struct.
   * @param bidInfo BidInfo struct
   * @return bidInfoHash Hash of the BidInfo struct
   */
  function hash(BidInfo memory bidInfo) internal pure returns (bytes32) {
    return
      keccak256(
        abi.encode(
          _BIDINFO_TYPEHASH,
          bidInfo.nonce,
          bidInfo.tokenId,
          bidInfo.bidderAddress,
          bidInfo.expiresAt,
          bidInfo.priceInWei
        )
      );
  }

  /**
   * @notice This function is used to compute the EIP712 hash for an AcceptanceInfo struct.
   * @param acceptanceInfo AcceptanceInfo struct
   * @return acceptanceInfoHash Hash of the AcceptanceInfo struct
   */
  function hash(AcceptanceInfo memory acceptanceInfo) internal pure returns (bytes32) {
    return
      keccak256(
        abi.encode(
          _ACCEPTANCEINFO_TYPEHASH,
          acceptanceInfo.nonce,
          acceptanceInfo.creatorAddress,
          acceptanceInfo.collectionAddress,
          acceptanceInfo.bidSignatureHash,
          _encodePayslips(acceptanceInfo.payslips)
        )
      );
  }

  /**
   * @notice This function is used to compute the EIP712 hash for a Payslip struct.
   * @param payslip Payslip struct
   * @return payslipHash Hash of the Payslip struct
   */
  function _encodePayslip(Payslip memory payslip) internal pure returns (bytes32) {
    return keccak256(abi.encode(_PAYSLIP_TYPEHASH, payslip.recipient, payslip.amountInWei));
  }

  /**
   * @notice This function is used to compute the EIP712 hash for an array of Payslip structs.
   * @param payslips Array of Payslip structs
   * @return payslipsHash Hash of the Payslip structs
   */
  function _encodePayslips(Payslip[] memory payslips) internal pure returns (bytes32) {
    bytes32[] memory encodedPayslips = new bytes32[](payslips.length);
    for (uint256 i = 0; i < payslips.length; i++) {
      encodedPayslips[i] = _encodePayslip(payslips[i]);
    }

    return keccak256(abi.encodePacked(encodedPayslips));
  }

  /**
   * @notice This function is used to compute the EIP712 hash for a MintPass struct.
   * @param mintPass MintPass struct
   * @return mintPassHash Hash of the MintPass struct
   */
  function hash(MintPass memory mintPass) internal pure returns (bytes32) {
    return
      keccak256(
        abi.encode(
          _MINTPASS_TYPEHASH,
          mintPass.signer,
          mintPass.expiresAt,
          mintPass.bidSignatureHash,
          mintPass.acceptanceSignatureHash
        )
      );
  }

  /**
   * @notice This function is used to compute the EIP712 hash for a CollectionInfo struct.
   * @param collectionInfo CollectionInfo struct
   * @return collectionInfoHash Hash of the CollectionInfo struct
   */
  function hash(CollectionInfo memory collectionInfo) internal pure returns (bytes32) {
    return
      keccak256(
        abi.encode(
          _COLLECTIONINFO_TYPEHASH,
          keccak256(bytes(collectionInfo.collectionName)),
          keccak256(bytes(collectionInfo.collectionSymbol)),
          collectionInfo.nonce,
          collectionInfo.signer,
          _encodeRecipients(collectionInfo.royaltyRecipients)
        )
      );
  }

  /**
   * @notice This function is used to compute the EIP712 hash for a Recipient struct.
   * @param recipient Recipient struct
   * @return recipientHash Hash of the Recipient struct
   */
  function _encodeRecipient(Recipient memory recipient) internal pure returns (bytes32) {
    return keccak256(abi.encode(_RECIPIENT_TYPEHASH, recipient.recipient, recipient.bps));
  }

  /**
   * @notice This function is used to compute the EIP712 hash for an array of Recipient structs.
   * @param recipients Array of Recipient structs
   * @return recipientsHash Hash of the Recipient structs
   */
  function _encodeRecipients(Recipient[] memory recipients) internal pure returns (bytes32) {
    bytes32[] memory encodedRecipients = new bytes32[](recipients.length);
    for (uint256 i = 0; i < recipients.length; i++) {
      encodedRecipients[i] = _encodeRecipient(recipients[i]);
    }

    return keccak256(abi.encodePacked(encodedRecipients));
  }
}

// File: contracts/interfaces/internal/IDecaCollection.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

struct Recipient {
  address payable recipient;
  uint16 bps;
}

interface IDecaCollection {
  error InvalidTokenId();
  error OnlyCreator();
  error OnlyMinterOrCreator();
  error NotTokenOwnerOrApproved();
  error ERC20SplitFailed();
  error TotalBpsMustBe10000();
  error EthTransferFailed();

  /**
   * @notice Emitted when ETH is transferred.
   * @param account The address of the account which received the ETH.
   * @param amount The amount of ETH transferred.
   */
  event ETHTransferred(address indexed account, uint256 amount);

  /**
   * @notice Emitted when an ERC20 token is transferred.
   * @param erc20Contract The address of the ERC20 contract.
   * @param account The address of the account which received the ERC20.
   * @param amount The amount of ERC20 transferred.
   */
  event ERC20Transferred(address indexed erc20Contract, address indexed account, uint256 amount);

  /**
   * @notice Emitted when the token URI is set on a token.
   * @param tokenId The id of the token.
   * @param tokenURI The token URI of the token.
   */
  event TokenUriSet(uint256 indexed tokenId, string tokenURI);

  /**
   * @notice Emitted when the treasury address is updated.
   * @param treasury The address of the new treasury.
   */
  event TreasuryUpdated(address indexed treasury);

  /**
   * @notice Emitted when the royalty bps is updated.
   * @param royaltyBps The royalty bps.
   */
  event RoyaltyBpsUpdated(uint256 royaltyBps);

  function initialize(
    address factory_,
    address creator_,
    address roleAuthority_,
    string calldata name_,
    string calldata symbol_,
    Recipient[] calldata recipients
  ) external;

  function creator() external view returns (address);

  function exists(uint256 tokenId) external view returns (bool);

  function mint(address to, uint256 tokenId) external;

  function burn(uint256 tokenId) external;

  function mintTimestamps(uint256 tokenId) external view returns (uint256);

  function setRecipients(Recipient[] calldata recipients) external;

  function setTreasuryAddress(address treasury_) external;

  function setRoyaltyBps(uint256 royaltyBps_) external;

  function getRecipients() external view returns (Recipient[] memory);

  function royaltyInfo(uint256, uint256 _salePrice) external view returns (address receiver, uint256 royaltyAmount);
}

// File: contracts/interfaces/internal/IDecaCollectionFactory.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

import {Recipient} from "./IDecaCollection.sol";

interface IDecaCollectionFactory {
  error CreatorCannotBeTheZeroAddress();
  error OnlyCreator();
  error OnlyMinter();
  error InterfaceUnsupported();

  /**
   * @notice Emitted when a new DecaCollection is created from this factory.
   * @param collection The address of the new NFT collection contract.
   * @param creator The address of the creator which owns the new collection.
   * @param collectionName The name of the collection.
   */
  event DecaCollectionCreated(address indexed collection, address indexed creator, string collectionName);

  /**
   * @notice Emitted when the metadata resolver is updated.
   * @param metadataResolver The address of the new metadata resolver.
   */
  event MetadataResolverUpdated(address indexed metadataResolver);

  /**
   * @notice Emitted when the implementation of DecaCollection is updated.
   * @param decaCollectionImplementation The address of the new implementation.
   * @param newVersion The new version of the implementation.
   */
  event DecaCollectionImplementationUpdated(address decaCollectionImplementation, uint256 newVersion);

  struct CreateDecaCollectionParams {
    address creator;
    uint96 nonce;
    Recipient[] recipients;
    string collectionName;
    string collectionSymbol;
  }

  function createDecaCollection(CreateDecaCollectionParams memory params) external returns (address);

  function tokenUri(address contractAddress, uint256 tokenId) external view returns (string memory);
}

// File: contracts/interfaces/internal/IDecaMintOnDemand.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

import {MintStructs} from "../../deca-collections/libraries/MintStructs.sol";

interface IDecaMintOnDemand {
  error TransferFailed();
  error CollectionMismatch();
  error PayoutMismatch();

  /**
   * @notice Creates a new collection via the factory, validates and executes the settlement. If successful, mints the NFT to the bidder.
   * @param collection Collection address.
   * @param bidder Bidder address.
   * @param creator Creator address.
   * @param payslips Payslips array.
   * @param bidId Bid id.
   * @param tokenId Token id.
   * @param price Price of the bid.
   */
  event BidSettled(
    address indexed collection,
    address indexed bidder,
    address indexed creator,
    MintStructs.Payslip[] payslips,
    bytes32 bidId,
    uint256 tokenId,
    uint256 price
  );
}

// File: contracts/interfaces/internal/ISettlementValidator.sol



// SPDX-License-Identifier: MIT

pragma solidity ^0.8.17;

import {MintStructs} from "../../deca-collections/libraries/MintStructs.sol";

interface ISettlementValidator {
  error ChainIdInvalid();
  error SignatureRepeated();
  error BidSignatureMismatch();
  error AcceptanceSignatureMismatch();
  error BidExpired();
  error MintPassExpired();
  error UnauthorizedMintPassSigner();
  error UnauthorizedAcceptanceSigner();
  error MintPassSignatureInvalid();
  error BidSignatureInvalid();
  error AcceptanceSignatureInvalid();
  error NotAuthorized();
  error TokenAlreadyCreated();
  error BidPriceNotMet();
  error NonBidderSettle();
  error UnauthorizedCollectionInfoSigner();
  error CollectionInfoSignatureInvalid();

  function usedBidInfoHashes(bytes32 _hash) external view returns (bool);

  function usedAcceptanceInfoHashes(bytes32 _hash) external view returns (bool);

  function invalidateBid(MintStructs.BidInfo calldata bidInfo) external;

  function validateSettlement(
    MintStructs.Settlement calldata settlement,
    uint256 msgValue,
    address msgSender
  ) external returns (bytes32 hash);

  function validateCollectionInfo(
    MintStructs.CollectionInfo calldata collectionInfo,
    bytes calldata signature
  ) external view;
}