// File: src/KronosMultiSig.sol



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

import "./interfaces/IKronosSeedSale.sol";

/// @title KronosMultiSig
/// @notice This contract is used to create a multi-sig wallet
contract KronosMultiSig {
    event Deposit(address indexed sender, uint256 amount, uint256 balance);
    event SubmitTransaction(
        address indexed owner,
        uint256 indexed txIndex,
        address indexed to,
        uint256 value,
        bytes data
    );
    event ConfirmTransaction(address indexed owner, uint256 indexed txIndex);
    event RevokeConfirmation(address indexed owner, uint256 indexed txIndex);
    event ExecuteTransaction(address indexed owner, uint256 indexed txIndex);

    IKronosSeedSale public seedSale;

    address public requiredConfirmationAddress;
    address[] public owners;
    mapping(address => bool) public isOwner;
    uint256 public numConfirmationsRequired;

    /// @notice Transaction struct
    /// @param to The address to send the transaction to
    /// @param value The amount of ether to send
    /// @param data The data to send
    /// @param executed Whether or not the transaction has been executed
    /// @param numConfirmations The number of confirmations the transaction has
    struct Transaction {
        address to;
        uint256 value;
        bytes data;
        bool executed;
        uint256 numConfirmations;
    }

    // mapping from tx index => owner => bool
    mapping(uint256 => mapping(address => bool)) public isConfirmed;

    Transaction[] public transactions;

    /// @notice Modifier to check if the sender is an owner
    modifier onlyOwner() {
        require(isOwner[msg.sender], "not owner");
        _;
    }

    /// @notice Modifier to check if a transaction exists
    /// @param _txIndex The index of the transaction
    modifier txExists(uint256 _txIndex) {
        require(_txIndex < transactions.length, "tx does not exist");
        _;
    }

    /// @notice Modifier to check if a transaction has not been executed
    /// @param _txIndex The index of the transaction
    modifier notExecuted(uint256 _txIndex) {
        require(!transactions[_txIndex].executed, "tx already executed");
        _;
    }

    /// @notice Modifier to check if a transaction has not been confirmed by the sender
    /// @param _txIndex The index of the transaction
    modifier notConfirmed(uint256 _txIndex) {
        require(!isConfirmed[_txIndex][msg.sender], "tx already confirmed");
        _;
    }

    /// @notice Contract constructor
    /// @param _owners The addresses of the owners
    /// @param _numConfirmationsRequired The number of confirmations required for a transaction
    constructor(
        address[] memory _owners,
        address _requiredConfirmationAddress,
        uint256 _numConfirmationsRequired
    ) {
        require(_owners.length > 0, "owners required");
        require(_requiredConfirmationAddress != address(0), "invalid required confirmation address");
        require(
            _numConfirmationsRequired > 0 && _numConfirmationsRequired <= _owners.length,
            "invalid number of required confirmations"
        );

        for (uint256 i = 0; i < _owners.length; i++) {
            address owner = _owners[i];

            require(owner != address(0), "invalid owner");
            require(!isOwner[owner], "owner not unique");

            isOwner[owner] = true;
            owners.push(owner);
        }

        numConfirmationsRequired = _numConfirmationsRequired;
        requiredConfirmationAddress = _requiredConfirmationAddress;
    }

    /// @notice Fallback function
    receive() external payable {
        emit Deposit(msg.sender, msg.value, address(this).balance);
    }

    /// @notice Set the KRONOS seed sale contract
    /// @param _seedSale The address of the seed sale contract
    /// @dev This function is called by an owner
    function setSeedSaleAddress(address _seedSale) public onlyOwner {
        require(_seedSale != address(0), "invalid seed sale address");
        seedSale = IKronosSeedSale(_seedSale);
    }

    /// @notice Withdraw tokens from the KRONOS seed sale contract
    /// @param token The address of the token to withdraw
    /// @dev This function is called by an owner
    function withdrawTokensFromKronosSeedSale(address token) public onlyOwner {
        require(address(seedSale) != address(0), "seed sale address not set");
        seedSale.withdrawTokens(token);
    }

    /// @notice Submit a transaction
    /// @param _to The address to send the transaction to
    /// @param _value The amount of ether to send
    /// @param _data The data to send
    /// @dev This function is called by an owner
    function submitTransaction(address _to, uint256 _value, bytes memory _data) public onlyOwner {
        uint256 txIndex = transactions.length;

        transactions.push(
            Transaction({to: _to, value: _value, data: _data, executed: false, numConfirmations: 0})
        );

        emit SubmitTransaction(msg.sender, txIndex, _to, _value, _data);
    }

    /// @notice Confirm a transaction
    /// @param _txIndex The index of the transaction
    /// @dev This function is called by an owner
    /// @dev This function can only be called if the transaction has not been executed
    function confirmTransaction(uint256 _txIndex)
        public
        onlyOwner
        txExists(_txIndex)
        notExecuted(_txIndex)
        notConfirmed(_txIndex)
    {
        Transaction storage transaction = transactions[_txIndex];
        transaction.numConfirmations += 1;
        isConfirmed[_txIndex][msg.sender] = true;

        emit ConfirmTransaction(msg.sender, _txIndex);
    }

    /// @notice Execute a transaction
    /// @param _txIndex The index of the transaction
    /// @dev This function is called by an owner
    /// @dev This function can only be called if the transaction has enough confirmations
    function executeTransaction(uint256 _txIndex)
        public
        onlyOwner
        txExists(_txIndex)
        notExecuted(_txIndex)
    {
        Transaction storage transaction = transactions[_txIndex];

        require(transaction.numConfirmations >= numConfirmationsRequired, "cannot execute tx");

        // Check if one of the confirmations is from the required special address
        bool hasCertifiedConfirmation = isConfirmed[_txIndex][requiredConfirmationAddress];
        require(
            hasCertifiedConfirmation,
            "at least one confirmation from the certified address is required"
        );

        transaction.executed = true;

        (bool success,) = transaction.to.call{value: transaction.value}(transaction.data);
        require(success, "tx failed");

        emit ExecuteTransaction(msg.sender, _txIndex);
    }

    /// @notice Revoke a confirmation for a transaction
    /// @param _txIndex The index of the transaction
    /// @dev This function is called by an owner
    function revokeConfirmation(uint256 _txIndex)
        public
        onlyOwner
        txExists(_txIndex)
        notExecuted(_txIndex)
    {
        Transaction storage transaction = transactions[_txIndex];

        require(isConfirmed[_txIndex][msg.sender], "tx not confirmed");

        transaction.numConfirmations -= 1;
        isConfirmed[_txIndex][msg.sender] = false;

        emit RevokeConfirmation(msg.sender, _txIndex);
    }

    /// @notice Get the owners of the wallet
    /// @return The owners of the wallet
    function getOwners() public view returns (address[] memory) {
        return owners;
    }

    /// @notice Get the transaction count
    /// @return The number of transactions
    function getTransactionCount() public view returns (uint256) {
        return transactions.length;
    }

    /// @notice Get a transaction
    /// @param _txIndex The index of the transaction
    /// @return to The address to send the transaction to
    /// @return value The amount of ether to send
    /// @return data The data to send
    /// @return executed Whether or not the transaction has been executed
    /// @return numConfirmations The number of confirmations the transaction has
    function getTransaction(uint256 _txIndex)
        public
        view
        returns (
            address to,
            uint256 value,
            bytes memory data,
            bool executed,
            uint256 numConfirmations
        )
    {
        Transaction storage transaction = transactions[_txIndex];

        return (
            transaction.to,
            transaction.value,
            transaction.data,
            transaction.executed,
            transaction.numConfirmations
        );
    }
}

// File: src/interfaces/IKronosSeedSale.sol



// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.21;

interface IKronosSeedSale {
    function flipSeedSaleActive() external;

    function addToWhitelist(address[] calldata wallets, uint256 metadataId) external;

    function payWithUSDT(uint256 amount) external;

    function payWithUSDC(uint256 amount) external;

    function mint() external;

    function setBaseURI(string calldata newURI) external;

    function tokenURI(uint256 tokenID) external view returns (string memory);

    function withdrawTokens(address token) external;

    // Events
    event Payment(address from, uint256 amount, bool USDT);

    // State variables (you can include these if you want to access them directly)
    function seedSaleActive() external view returns (bool);

    function USDT() external view returns (address);

    function USDC() external view returns (address);

    function totalSupply() external view returns (uint256);

    function totalUSDTokenAmountCommitted() external view returns (uint256);

    function USDTokenAmountCommitted(address user) external view returns (uint256);

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

    function metaIDForAddress(address user) external view returns (uint256);
}