// File: ERC1155Receiver.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC1155/utils/ERC1155Receiver.sol)

pragma solidity ^0.8.0;

import "./IERC1155Receiver.sol";
import "./ERC165.sol";

/**
 * @dev _Available since v3.1._
 */
abstract contract ERC1155Receiver is ERC165, IERC1155Receiver {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
        return interfaceId == type(IERC1155Receiver).interfaceId || super.supportsInterface(interfaceId);
    }
}
// File: ERC165.sol



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

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Implementation of the {IERC165} interface.
 *
 * Contracts that want to implement ERC165 should inherit from this contract and override {supportsInterface} to check
 * for the additional interface id that will be supported. For example:
 *
 * ```solidity
 * function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
 *     return interfaceId == type(MyInterface).interfaceId || super.supportsInterface(interfaceId);
 * }
 * ```
 *
 * Alternatively, {ERC165Storage} provides an easier to use but more expensive implementation.
 */
abstract contract ERC165 is IERC165 {
    /**
     * @dev See {IERC165-supportsInterface}.
     */
    function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
        return interfaceId == type(IERC165).interfaceId;
    }
}
// File: IERC1155.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC1155/IERC1155.sol)

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev Required interface of an ERC1155 compliant contract, as defined in the
 * https://eips.ethereum.org/EIPS/eip-1155[EIP].
 *
 * _Available since v3.1._
 */
interface IERC1155 is IERC165 {
    /**
     * @dev Emitted when `value` tokens of token type `id` are transferred from `from` to `to` by `operator`.
     */
    event TransferSingle(address indexed operator, address indexed from, address indexed to, uint256 id, uint256 value);

    /**
     * @dev Equivalent to multiple {TransferSingle} events, where `operator`, `from` and `to` are the same for all
     * transfers.
     */
    event TransferBatch(
        address indexed operator,
        address indexed from,
        address indexed to,
        uint256[] ids,
        uint256[] values
    );

    /**
     * @dev Emitted when `account` grants or revokes permission to `operator` to transfer their tokens, according to
     * `approved`.
     */
    event ApprovalForAll(address indexed account, address indexed operator, bool approved);

    /**
     * @dev Emitted when the URI for token type `id` changes to `value`, if it is a non-programmatic URI.
     *
     * If an {URI} event was emitted for `id`, the standard
     * https://eips.ethereum.org/EIPS/eip-1155#metadata-extensions[guarantees] that `value` will equal the value
     * returned by {IERC1155MetadataURI-uri}.
     */
    event URI(string value, uint256 indexed id);

    /**
     * @dev Returns the amount of tokens of token type `id` owned by `account`.
     *
     * Requirements:
     *
     * - `account` cannot be the zero address.
     */
    function balanceOf(address account, uint256 id) external view returns (uint256);

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {balanceOf}.
     *
     * Requirements:
     *
     * - `accounts` and `ids` must have the same length.
     */
    function balanceOfBatch(
        address[] calldata accounts,
        uint256[] calldata ids
    ) external view returns (uint256[] memory);

    /**
     * @dev Grants or revokes permission to `operator` to transfer the caller's tokens, according to `approved`,
     *
     * Emits an {ApprovalForAll} event.
     *
     * Requirements:
     *
     * - `operator` cannot be the caller.
     */
    function setApprovalForAll(address operator, bool approved) external;

    /**
     * @dev Returns true if `operator` is approved to transfer ``account``'s tokens.
     *
     * See {setApprovalForAll}.
     */
    function isApprovedForAll(address account, address operator) external view returns (bool);

    /**
     * @dev Transfers `amount` tokens of token type `id` from `from` to `to`.
     *
     * Emits a {TransferSingle} event.
     *
     * Requirements:
     *
     * - `to` cannot be the zero address.
     * - If the caller is not `from`, it must have been approved to spend ``from``'s tokens via {setApprovalForAll}.
     * - `from` must have a balance of tokens of type `id` of at least `amount`.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155Received} and return the
     * acceptance magic value.
     */
    function safeTransferFrom(address from, address to, uint256 id, uint256 amount, bytes calldata data) external;

    /**
     * @dev xref:ROOT:erc1155.adoc#batch-operations[Batched] version of {safeTransferFrom}.
     *
     * Emits a {TransferBatch} event.
     *
     * Requirements:
     *
     * - `ids` and `amounts` must have the same length.
     * - If `to` refers to a smart contract, it must implement {IERC1155Receiver-onERC1155BatchReceived} and return the
     * acceptance magic value.
     */
    function safeBatchTransferFrom(
        address from,
        address to,
        uint256[] calldata ids,
        uint256[] calldata amounts,
        bytes calldata data
    ) external;
}
// File: IERC1155Receiver.sol



// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC1155/IERC1155Receiver.sol)

pragma solidity ^0.8.0;

import "./IERC165.sol";

/**
 * @dev _Available since v3.1._
 */
interface IERC1155Receiver is IERC165 {
    /**
     * @dev Handles the receipt of a single ERC1155 token type. This function is
     * called at the end of a `safeTransferFrom` after the balance has been updated.
     *
     * NOTE: To accept the transfer, this must return
     * `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))`
     * (i.e. 0xf23a6e61, or its own function selector).
     *
     * @param operator The address which initiated the transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param id The ID of the token being transferred
     * @param value The amount of tokens being transferred
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` if transfer is allowed
     */
    function onERC1155Received(
        address operator,
        address from,
        uint256 id,
        uint256 value,
        bytes calldata data
    ) external returns (bytes4);

    /**
     * @dev Handles the receipt of a multiple ERC1155 token types. This function
     * is called at the end of a `safeBatchTransferFrom` after the balances have
     * been updated.
     *
     * NOTE: To accept the transfer(s), this must return
     * `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))`
     * (i.e. 0xbc197c81, or its own function selector).
     *
     * @param operator The address which initiated the batch transfer (i.e. msg.sender)
     * @param from The address which previously owned the token
     * @param ids An array containing ids of each token being transferred (order and length must match values array)
     * @param values An array containing amounts of each token being transferred (order and length must match ids array)
     * @param data Additional data with no specified format
     * @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` if transfer is allowed
     */
    function onERC1155BatchReceived(
        address operator,
        address from,
        uint256[] calldata ids,
        uint256[] calldata values,
        bytes calldata data
    ) external returns (bytes4);
}
// File: 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: NFTStaking.sol



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

import "./ERC1155Receiver.sol";
import "./IERC1155.sol";
import "./SafeMath.sol";


contract ERC1155Holder is ERC1155Receiver {
    function onERC1155Received(
        address,
        address,
        uint256,
        uint256,
        bytes memory
    ) public virtual override returns (bytes4) {
        return this.onERC1155Received.selector;
    }

    function onERC1155BatchReceived(
        address,
        address,
        uint256[] memory,
        uint256[] memory,
        bytes memory
    ) public virtual override returns (bytes4) {
        return this.onERC1155BatchReceived.selector;
    }
}

interface ERC20 {
    function BurnCrab(uint256 amt) external;

    function StakeTokens(uint256 amt, address _referrer) external;

    function ClaimStakeInterest() external;

    function calcStakingRewards(address _user) external view returns (uint256);

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

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

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

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

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

contract ContractEvents {
    //when user stakes nfts
    event NFTstake(address indexed user, uint256 value);

    //when user unstakes nfts
    event NFTunstake(address indexed user, uint256 value);

    //when a user distributes interest tokens
    event TokenDistribution(address indexed user, uint256 value);
    //when a user claims interest tokens
    event TokenClaim(address indexed user, uint256 value);

    //when contract stakes tokens
    event TokenStake(address indexed user, uint256 value);

    //when contract rolls tokens
    event TokenRoll(address indexed user, uint256 value);

    //when contract burns tokens
    event TokenBurn(address indexed user, uint256 value);
}

contract CrabStakingNFT is ContractEvents, ERC1155Holder {
    using SafeMath for uint256;

    bool private sync;

    //protects against potential reentrancy
    modifier synchronized() {
        require(!sync, "Sync lock");
        sync = true;
        _;
        sync = false;
    }

    // Contract deployer
    address dev;
    // The address of the CRAB contract.
    address crabContract = 0x24BCeC1AFda63E622a97F17cFf9a61FFCfd9b735; 
    //The address of the Genesis NFT contract.
    address nftContract = 0x45036a8C1234A2d42992688c5EAf83d2F7E3496A; 

    //Array of active stakers
    address[] public activeStakers; // Array count (max 32)
    // Mapping from user address to number of staked NFTs.
    mapping(address => uint256) public usersStakedNFTs;
    // Mapping from user to amount of interest available to claim.
    mapping(address => uint256) public stakerInterest;
    // Yield data
    uint256 public totalStakedNfts;
    uint256 public availableToBurn;
    uint256 public availableToRoll;

    constructor() {
        dev = msg.sender;
    }

    // Stakes an NFT.
    function StakeNFT(uint256 _nfts) external synchronized {
        // Ensure the contract has been approved to receive the NFT.
        require(
            IERC1155(nftContract).isApprovedForAll(msg.sender, address(this)),
            "Contract has not been approved to receive the NFT."
        );
        require(_nfts > 0, "Define amount of Genesis NFTs to stake");
        // Do not allow yield dist on first ever NFT stake.
        if (totalStakedNfts > 0) {
            distributeYield();
        }
        //Add user to active array if not already.
        if (usersStakedNFTs[msg.sender] == 0) {
            activeStakers.push(msg.sender);
        }
        // Increase value of total NFTs staked at current
        totalStakedNfts += _nfts;
        // Increase value of NFTs staked by user
        usersStakedNFTs[msg.sender] += _nfts;
        // Transfer the appropriate amount of NFTs to the contract.
        IERC1155(nftContract).safeTransferFrom(
            msg.sender,
            address(this),
            1,
            _nfts,
            ""
        );
        emit NFTstake(msg.sender, _nfts);
    }

    // Unstakes an NFT.
    function UnstakeNFT(uint256 _nfts) external synchronized {
        require(_nfts > 0, "Define amount of Genesis NFTs to unstake");
        // Ensure the caller cannot unstake NFTs they do not own.
        require(
            usersStakedNFTs[msg.sender] >= _nfts,
            "Input amount is larger than users staked NFTs."
        );
        distributeYield();
        // Decrease value of total NFTs staked at current
        totalStakedNfts -= _nfts;
        // Decrease value of NFTs staked by user
        usersStakedNFTs[msg.sender] -= _nfts;
        // Remove user from active staker array if NFT stake count 0.
        if (usersStakedNFTs[msg.sender] == 0) {
            for (uint256 i; i < activeStakers.length; i++) {
                // Find staker
                if (activeStakers[i] == msg.sender) {
                    // Remove staker
                    delete activeStakers[i];
                    removeElement(i);
                    break;
                }
            }
        }
        // Send the NFT/s to user
        IERC1155(nftContract).safeTransferFrom(
            address(this),
            msg.sender,
            1,
            _nfts,
            ""
        );
        if (stakerInterest[msg.sender] > 0) {
            claimYield();
        }
        emit NFTunstake(msg.sender, _nfts);
    }

    // Reset staker array
    function removeElement(uint256 index) internal {
        activeStakers[index] = activeStakers[activeStakers.length - 1];
        activeStakers.pop();
    }

    // Distributes the yield accrued from the NFT staking contract.
    function DistributeYield() external synchronized {
        distributeYield();
    }

    function distributeYield() internal {
        // Calculate the total yield.
        uint256 yield = ERC20(crabContract).calcStakingRewards(address(this));
        // Calculate distributions
        if (yield > 0) {
            uint256 distributable = yield.mul(85).div(100); //85% to stakers
            uint256 yieldPerNft = distributable.div(totalStakedNfts); //equal yield per Genesis NFT
            availableToBurn += yield.mul(10).div(100); //10% burn to raise NFT staking APY
            availableToRoll += yield.mul(5).div(100); //5% roll to compound stake while maintaining APY rise(add to stake)
            // Distribute the yield equally among all staked NFTs
            for (uint256 i = 0; i < activeStakers.length; i++) {
                stakerInterest[activeStakers[i]] += yieldPerNft.mul(
                    usersStakedNFTs[activeStakers[i]]
                );
            }
            ERC20(crabContract).ClaimStakeInterest();
            emit TokenDistribution(msg.sender, yield);
        }
    }

    //Claims the yield allocated for an individual user
    function ClaimYield() external synchronized {
        claimYield();
    }

    function claimYield() internal {
        uint256 yield = stakerInterest[msg.sender];
        stakerInterest[msg.sender] = 0;
        require(yield > 0, "Nothing to claim");
        ERC20(crabContract).transfer(msg.sender, yield);
        emit TokenClaim(msg.sender, yield);
    }

    //Adds CRAB from callers wallet to the NFT contract stake, also claims any interest
    function AddToStake(uint256 _amt) external synchronized {
        //send tokens from user wallet to NFT contract (approval needed)
        require(
            ERC20(crabContract).transferFrom(msg.sender, address(this), _amt)
        );
        distributeYield();
        //stake _amt to CRAB contract on behalf of NFT contract (dev is ref)
        ERC20(crabContract).StakeTokens(_amt, dev);
        emit TokenStake(msg.sender, _amt);
    }

    //Rolls allocated CRAB into the NFT contract stake
    function RollAllocated() external synchronized {
        uint256 toRoll = availableToRoll;
        require(availableToRoll > 0, "Nothing to roll");
        availableToRoll = 0;
        distributeYield();
        // Stake tokens to CRAB contract on behalf of NFT contract, also claims any interest (dev is ref)
        ERC20(crabContract).StakeTokens(toRoll, dev);
        emit TokenRoll(msg.sender, toRoll);
    }

    // Burn allocated CRAB on behalf of NFT contract to increase NFT staking APY
    function BurnAllocated() external synchronized {
         uint256 toBurn = availableToBurn;
        require(availableToBurn > 0, "Nothing to burn");
        availableToBurn = 0;
        distributeYield();
        ERC20(crabContract).BurnCrab(toBurn);
        emit TokenBurn(msg.sender, toBurn);
    }

    // Retrieves the staked NFTs for a given user.
    function getUserStakedNFTs(address _staker)
        external
        view
        returns (uint256)
    {
        return usersStakedNFTs[_staker];
    }
}

// File: SafeMath.sol



pragma solidity 0.8.4;
// SPDX-License-Identifier: UNLICENSED
/**
 * @dev Wrappers over Solidity's arithmetic operations with added overflow
 * checks.
 *
 * Arithmetic operations in Solidity wrap on overflow. This can easily result
 * in bugs, because programmers usually assume that an overflow raises an
 * error, which is the standard behavior in high level programming languages.
 * `SafeMath` restores this intuition by reverting the transaction when an
 * operation overflows.
 *
 * Using this library instead of the unchecked operations eliminates an entire
 * class of bugs, so it's recommended to use it always.
 */
library SafeMath {
    /**
     * @dev Returns the addition of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `+` operator.
     *
     * Requirements:
     * - Addition cannot overflow.
     */
    function add(uint256 a, uint256 b) internal pure returns (uint256) {
        uint256 c = a + b;
        require(c >= a, "SafeMath: addition overflow");

        return c;
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     */
    function sub(uint256 a, uint256 b) internal pure returns (uint256) {
        return sub(a, b, "SafeMath: subtraction overflow");
    }

    /**
     * @dev Returns the subtraction of two unsigned integers, reverting with custom message on
     * overflow (when the result is negative).
     *
     * Counterpart to Solidity's `-` operator.
     *
     * Requirements:
     * - Subtraction cannot overflow.
     *
     * _Available since v2.4.0._
     */
    function sub(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b <= a, errorMessage);
        uint256 c = a - b;

        return c;
    }

    /**
     * @dev Returns the multiplication of two unsigned integers, reverting on
     * overflow.
     *
     * Counterpart to Solidity's `*` operator.
     *
     * Requirements:
     * - Multiplication cannot overflow.
     */
    function mul(uint256 a, uint256 b) internal pure returns (uint256) {
        // Gas optimization: this is cheaper than requiring 'a' not being zero, but the
        // benefit is lost if 'b' is also tested.
        // See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
        if (a == 0) {
            return 0;
        }

        uint256 c = a * b;
        require(c / a == b, "SafeMath: multiplication overflow");

        return c;
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function div(uint256 a, uint256 b) internal pure returns (uint256) {
        return div(a, b, "SafeMath: division by zero");
    }

    /**
     * @dev Returns the integer division of two unsigned integers. Reverts with custom message on
     * division by zero. The result is rounded towards zero.
     *
     * Counterpart to Solidity's `/` operator. Note: this function uses a
     * `revert` opcode (which leaves remaining gas untouched) while Solidity
     * uses an invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     *
     * _Available since v2.4.0._
     */
    function div(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        // Solidity only automatically asserts when dividing by 0
        require(b > 0, errorMessage);
        uint256 c = a / b;
        // assert(a == b * c + a % b); // There is no case in which this doesn't hold

        return c;
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     */
    function mod(uint256 a, uint256 b) internal pure returns (uint256) {
        return mod(a, b, "SafeMath: modulo by zero");
    }

    /**
     * @dev Returns the remainder of dividing two unsigned integers. (unsigned integer modulo),
     * Reverts with custom message when dividing by zero.
     *
     * Counterpart to Solidity's `%` operator. This function uses a `revert`
     * opcode (which leaves remaining gas untouched) while Solidity uses an
     * invalid opcode to revert (consuming all remaining gas).
     *
     * Requirements:
     * - The divisor cannot be zero.
     *
     * _Available since v2.4.0._
     */
    function mod(uint256 a, uint256 b, string memory errorMessage) internal pure returns (uint256) {
        require(b != 0, errorMessage);
        return a % b;
    }
}