Accounts
0xaa...7c27
0xAa...7C27

0xAa...7C27

$500
This contract's source code is verified!
Contract Metadata
Compiler
0.8.17+commit.8df45f5f
Language
Solidity
Contract Source Code
File 1 of 5: BipzyStaking.sol
//SPDX-License-Identifier:MIT

pragma solidity ^0.8.17;

import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

/**
 * @title BipzyStaking
 * @notice This contract allows holders of Bipzy and BipzyPass NFTs to stake their tokens for predetermined durations.
 * @dev The contract supports two types of NFTs: Bipzy and BipzyPass, and allows staking for either 180 or 360 days.
 */
contract BipzyStaking is Ownable {
    /**
     * @notice Custom errors for various error scenarios.
     * @dev Using custom errors helps save gas compared to revert strings.
     */
    error BPZ__InvalidPrevId();
    error BPZ__StakePeriodNotOver();
    error BPZ__MustOwn1NFTAtLeast();
    error BPZ__InvalidStakeDuration();
    error BPZ__ContractInstanceLocked();
    error BPZ__NFTContractNotSupported();
    error BPZ__ZeroStakePreferencesFound();
    error BPZ__OnlyStakerCanWithdrawTheirStake();

    /**
     * @notice The Bipzy NFT contract instance.
     */
    IERC721 public i_bipzy;
    /**
     * @notice The BipzyPass NFT contract instance.
     */
    IERC721 public i_bipzyPass;

    /**
     * @notice This boolean flag indicates whether the contract instances (i_bipzy and i_bipzyPass) are locked.
     * @dev If set to true, the contract instances cannot be updated by calling setNewBipzyInstance or setNewBipzyPassInstance.
     */
    bool public contractInstancesLocked;

    /**
     * @notice The duration of minimum stake period days in seconds.
     */
    uint64 public minDuration = 3 * 30 days;

    /**
     * @notice The duration of maximum stake period days in seconds.
     */
    uint64 public maxDuration = 6 * 30 days;

    /**
     * @notice The booster value for a staked NFT with a 180-day duration.
     */
    uint256 public boosterValueOne = 10000;

    /**
     * @notice The booster value for a staked NFT with a 360-day duration.
     */
    uint256 public boosterValueTwo = 15000;

    /**
     * @notice The sentinel token ID used as a marker in the linked list.
     * @dev This value is derived from the string "Bipzys" converted to binary and then decimal.
     */
    uint256 public constant SENTINAL_TOKEN_ID = 73020626073971;

    /**
     * @notice Struct to store stake information for each staked token.
     * @param staker The address of the staker.
     * @param tokenId The ID of the staked token.
     * @param tokenContract The contract instance of the staked token (Bipzy or BipzyPass).
     * @param stakeStartedOn The timestamp when the stake started.
     * @param stakeEndsOn The timestamp when the stake ends.
     */
    struct StakeInfo {
        address staker;
        uint96 tokenId;
        IERC721 tokenContract;
        uint32 stakeStartedOn;
        uint32 stakeEndsOn;
    }

    /**
     * @notice Struct to store stake preference information for staking NFTs.
     * @param tokenId The ID of the token to be staked.
     * @param stakeDuration The duration of the stake (180 or 360 days).
     * @param nftContract The contract instance of the NFT (Bipzy or BipzyPass).
     */
    struct StakePreference {
        uint96 tokenId;
        uint32 stakeDuration;
        IERC721 nftContract;
    }

    /**
     * @notice Struct to store stake withdrawal preference information.
     * @param tokenId The ID of the token to be withdrawn.
     * @param prevTokenId The ID of the token preceding the token to be withdrawn in the linked list.
     */
    struct StakeWithdrawalPreference {
        uint96 tokenId;
        uint96 prevTokenId;
    }

    /**
     * @notice Mapping to store stake information for each token ID.
     */
    mapping(uint256 => StakeInfo) public idBipzyToStakeInfo;
    mapping(uint256 => StakeInfo) public idBipzyPassToStakeInfo;

    /**
     * @notice Mapping to store token IDs in a linked list for each staker.
     * @dev The linked list is used to efficiently traverse the staker's staked tokens during booster calculation.
     */
    mapping(address => mapping(uint256 => uint256)) public ownerToBipzyTokenIds;
    mapping(address => mapping(uint256 => uint256))
        public ownerToBipzyPassTokenIds;

    /**
     * @notice Mapping to store Bipzy NFT Token IDs that are rare
     * @dev Only Owner can update the mapping
     * only Bipzy NFT will have rare NFTs hence only Bipzy NFT are going to be stored here
     */
    mapping(uint256 => bool) public rareTokenIds;
    
    /**
     * @notice Event emitted when a Bipzy token is staked.
     * @param staker The address of the staker.
     * @param tokenId The ID of the staked token.
     * @param nftContractInstance The contract instance of the staked token (Bipzy or BipzyPass).
     * @param stakingStartedOn The timestamp when the stake started.
     * @param stakingEndTimeStamp The timestamp when the stake ends.
     */
    event BipzyTokenStaked(
        address indexed staker,
        uint96 indexed tokenId,
        IERC721 indexed nftContractInstance,
        uint32 stakingStartedOn,
        uint32 stakingEndTimeStamp
    );
    /**
     * @notice Event emitted when a Bipzy Pass token is staked.
     * @param staker The address of the staker.
     * @param tokenId The ID of the staked token.
     * @param nftContractInstance The contract instance of the staked token (Bipzy or BipzyPass).
     * @param stakingStartedOn The timestamp when the stake started.
     * @param stakingEndTimeStamp The timestamp when the stake ends.
     */
    event BipzyPassTokenStaked(
        address indexed staker,
        uint96 indexed tokenId,
        IERC721 indexed nftContractInstance,
        uint32 stakingStartedOn,
        uint32 stakingEndTimeStamp
    );

    /**
     * @notice Event emitted when a staked token is withdrawn.
     * @param staker The address of the staker.
     * @param tokenId The ID of the withdrawn token.
     * @param nftContractInstance The contract instance of the withdrawn token (Bipzy or BipzyPass).
     */
    event BipzyStakeWithdrawn(
        address indexed staker,
        uint96 indexed tokenId,
        IERC721 indexed nftContractInstance
    );

    /**
     * @notice Event emitted when a staked token is withdrawn.
     * @param staker The address of the staker.
     * @param tokenId The ID of the withdrawn token.
     * @param nftContractInstance The contract instance of the withdrawn token (Bipzy or BipzyPass).
     */
    event BipzyPassStakeWithdrawn(
        address indexed staker,
        uint96 indexed tokenId,
        IERC721 indexed nftContractInstance
    );

    /**
     * @notice Event emitteed when owner force unstakes the token
     * @param tokenId token ID that was force unstaked
     */
    event ForceUnstaked(
        uint96 indexed tokenId
    );

    /**
     * @notice Constructor to set the Bipzy and BipzyPass NFT contract instances.
     * @param bipzyNftAddr The address of the Bipzy NFT contract.
     * @param bipzyPassNftAddr The address of the BipzyPass NFT contract.
     */
    constructor(IERC721 bipzyNftAddr, IERC721 bipzyPassNftAddr) {
        i_bipzy = bipzyNftAddr;
        i_bipzyPass = bipzyPassNftAddr;
        contractInstancesLocked = false;
    }

    /**
     * @dev Allows the staking of BIPZY tokens.
     * @param stakePreferences Array of `StakePreference` structs containing stake preferences.
     */
    function stakeBipzy(StakePreference[] calldata stakePreferences) public {
        // Ensure that the caller owns at least one Bipzy or BipzyPass NFT
        if (i_bipzy.balanceOf(msg.sender) == 0)
            revert BPZ__MustOwn1NFTAtLeast();

        // Ensure that at least one stake preference is provided
        if (stakePreferences.length == 0)
            revert BPZ__ZeroStakePreferencesFound();

        uint32 currentTimestamp = uint32(block.timestamp);

        if(ownerToBipzyTokenIds[msg.sender][SENTINAL_TOKEN_ID] == 0) {
            ownerToBipzyTokenIds[msg.sender][SENTINAL_TOKEN_ID] = SENTINAL_TOKEN_ID;
        }

        // Iterate through the stake preferences and process each one
        for (uint i = 0; i < stakePreferences.length; i++) {
            StakePreference memory preference = stakePreferences[i];
            uint96 tokenId = preference.tokenId;
            uint32 duration = preference.stakeDuration;
            IERC721 nftContractInstance = preference.nftContract;

            // Ensure that the stake duration is either minDuration or maxDuration days
            if (duration != minDuration && duration != maxDuration)
                revert BPZ__InvalidStakeDuration();

            uint32 stakeEndsOn = currentTimestamp + duration;

            // Ensure that the NFT contract is either Bipzy or BipzyPass
            if (nftContractInstance != i_bipzy) {
                revert BPZ__NFTContractNotSupported();
            }
            // Transfer the NFT from the staker to the contract
            nftContractInstance.transferFrom(
                msg.sender,
                address(this),
                tokenId
            );

            // Update the linked list of token IDs for the staker
            ownerToBipzyTokenIds[msg.sender][tokenId] = ownerToBipzyTokenIds[msg.sender][SENTINAL_TOKEN_ID];
            ownerToBipzyTokenIds[msg.sender][SENTINAL_TOKEN_ID] = tokenId;

            // Store the stake information for the token ID
            idBipzyToStakeInfo[tokenId] = StakeInfo(
                msg.sender,
                tokenId,
                nftContractInstance,
                currentTimestamp,
                stakeEndsOn
            );

            // Emit the TokenStaked event
            emit BipzyTokenStaked(
                msg.sender,
                tokenId,
                nftContractInstance,
                currentTimestamp,
                stakeEndsOn
            );
        }
    }

    /**
     * @dev Allows the staking of BIPZY PASS tokens.
     * @param stakePreferences Array of `StakePreference` structs containing stake preferences.
     */
    function stakeBipzyPass(
        StakePreference[] calldata stakePreferences
    ) public {
        // Ensure that the caller owns at least one Bipzy or BipzyPass NFT
        if (i_bipzyPass.balanceOf(msg.sender) == 0)
            revert BPZ__MustOwn1NFTAtLeast();

        // Ensure that at least one stake preference is provided
        if (stakePreferences.length == 0)
            revert BPZ__ZeroStakePreferencesFound();

        uint32 currentTimestamp = uint32(block.timestamp);

        if(ownerToBipzyPassTokenIds[msg.sender][SENTINAL_TOKEN_ID] == 0) {
            ownerToBipzyPassTokenIds[msg.sender][SENTINAL_TOKEN_ID] = SENTINAL_TOKEN_ID;
        }

        // Iterate through the stake preferences and process each one
        for (uint i = 0; i < stakePreferences.length; i++) {
            StakePreference memory preference = stakePreferences[i];
            uint96 tokenId = preference.tokenId;
            uint32 duration = preference.stakeDuration;
            IERC721 nftContractInstance = preference.nftContract;

            // Ensure that the stake duration is either minDuration or maxDuration days
            if (duration != minDuration && duration != maxDuration)
                revert BPZ__InvalidStakeDuration();

            uint32 stakeEndsOn = currentTimestamp + duration;

            // Ensure that the NFT contract is either Bipzy or BipzyPass
            if (nftContractInstance != i_bipzyPass) {
                revert BPZ__NFTContractNotSupported();
            }
            // Transfer the NFT from the staker to the contract
            nftContractInstance.transferFrom(
                msg.sender,
                address(this),
                tokenId
            );

            // Update the linked list of token IDs for the staker
            ownerToBipzyPassTokenIds[msg.sender][tokenId] = ownerToBipzyPassTokenIds[msg.sender][SENTINAL_TOKEN_ID];
            ownerToBipzyPassTokenIds[msg.sender][SENTINAL_TOKEN_ID] = tokenId;

            // Store the stake information for the token ID
            idBipzyPassToStakeInfo[tokenId] = StakeInfo(
                msg.sender,
                tokenId,
                nftContractInstance,
                currentTimestamp,
                stakeEndsOn
            );

            // Emit the TokenStaked event
            emit BipzyPassTokenStaked(
                msg.sender,
                tokenId,
                nftContractInstance,
                currentTimestamp,
                stakeEndsOn
            );
        }
    }

    /**
     * @dev Allows the withdrawal of stakes for BIPZY tokens.
     * @param stakeWithdrawalPreferences Array of `StakeWithdrawalPreference` structs containing withdrawal preferences.
     */
    function stakeBipzyWithdrawal(
        StakeWithdrawalPreference[] calldata stakeWithdrawalPreferences
    ) public {
        // Iterate through the withdrawal preferences
        for (uint96 i = 0; i < stakeWithdrawalPreferences.length; i++) {
            uint96 tokenId = stakeWithdrawalPreferences[i].tokenId;
            uint96 prevId = stakeWithdrawalPreferences[i].prevTokenId;
            StakeInfo memory stakeInfo = idBipzyToStakeInfo[tokenId];
            IERC721 stakedContractInstance = stakeInfo.tokenContract;

            // Ensure that the caller is the staker of the token
            if (stakeInfo.staker != msg.sender)
                revert BPZ__OnlyStakerCanWithdrawTheirStake();
            if (ownerToBipzyTokenIds[msg.sender][prevId] != tokenId)
                revert BPZ__InvalidPrevId();

            // Ensure that the stake period is over
            if (block.timestamp < stakeInfo.stakeEndsOn)
                revert BPZ__StakePeriodNotOver();

            // Update the linked list of token IDs for the staker
            ownerToBipzyTokenIds[msg.sender][prevId] = ownerToBipzyTokenIds[
                msg.sender
            ][tokenId];

            // Delete this token ID from the linked list
            delete ownerToBipzyTokenIds[msg.sender][tokenId];

            // Delete the stake information for the token ID
            delete idBipzyToStakeInfo[tokenId];

            // Transfer the NFT back to the staker
            stakedContractInstance.transferFrom(
                address(this),
                msg.sender,
                tokenId
            );

            // Emit the StakeWithdrawn event
            emit BipzyStakeWithdrawn(msg.sender, tokenId, stakedContractInstance);
        }
    }

    /**
     * @dev Allows the withdrawal of stakes for BIPZY PASS tokens.
     * @param stakeWithdrawalPreferences Array of `StakeWithdrawalPreference` structs containing withdrawal preferences.
     */
    function stakeBipzyPassWithdrawal(
        StakeWithdrawalPreference[] calldata stakeWithdrawalPreferences
    ) public {
        // Iterate through the withdrawal preferences
        for (uint96 i = 0; i < stakeWithdrawalPreferences.length; i++) {
            uint96 tokenId = stakeWithdrawalPreferences[i].tokenId;
            uint96 prevId = stakeWithdrawalPreferences[i].prevTokenId;
            StakeInfo memory stakeInfo = idBipzyPassToStakeInfo[tokenId];
            IERC721 stakedContractInstance = stakeInfo.tokenContract;

            // Ensure that the caller is the staker of the token
            if (stakeInfo.staker != msg.sender)
                revert BPZ__OnlyStakerCanWithdrawTheirStake();

            if (ownerToBipzyPassTokenIds[msg.sender][prevId] != tokenId)
                revert BPZ__InvalidPrevId();

            // Ensure that the stake period is over
            if (block.timestamp < stakeInfo.stakeEndsOn)
                revert BPZ__StakePeriodNotOver();

            // Update the linked list of token IDs for the staker
            ownerToBipzyPassTokenIds[msg.sender][
                prevId
            ] = ownerToBipzyPassTokenIds[msg.sender][tokenId];

            // Delete this token ID from the linked list
            delete ownerToBipzyPassTokenIds[msg.sender][tokenId];

            // Delete the stake information for the token ID
            delete idBipzyPassToStakeInfo[tokenId];

            // Transfer the NFT back to the staker
            stakedContractInstance.transferFrom(
                address(this),
                msg.sender,
                tokenId
            );

            // Emit the StakeWithdrawn event
            emit BipzyPassStakeWithdrawn(msg.sender, tokenId, stakedContractInstance);
        }
    }

    /**
     * @notice Retrieves the total booster amount for a given owner's BipzyPass stakes.
     * @param owner The address of the BipzyPass owner.
     * @return The total booster amount calculated based on the active BipzyPass stakes and their durations.
     */

    function getBipzyPassBoosterInfo(
        address owner
    ) internal view returns (uint256) {
        if (ownerToBipzyPassTokenIds[owner][SENTINAL_TOKEN_ID] != 0) {
            uint256 currentTokenId = ownerToBipzyPassTokenIds[owner][
                SENTINAL_TOKEN_ID
            ];

            uint256 prevId = SENTINAL_TOKEN_ID;
            uint32 _numDurationOneStakes;
            uint32 _numDurationTwoStakes;

            // Traverse the linked list of token IDs for the staker
            while (currentTokenId != SENTINAL_TOKEN_ID) {
                prevId = currentTokenId;

                uint64 stakeDuration = idBipzyPassToStakeInfo[prevId]
                    .stakeEndsOn -
                    idBipzyPassToStakeInfo[prevId].stakeStartedOn;

                // If the stake is still active, increment the respective counter
                if (
                    idBipzyPassToStakeInfo[prevId].stakeEndsOn > block.timestamp
                )
                    if (stakeDuration == minDuration) {
                        _numDurationOneStakes += 1;
                    } else {
                        _numDurationTwoStakes += 1;
                    }
                currentTokenId = ownerToBipzyPassTokenIds[owner][prevId];
            }

            // Calculate the total booster amount based on the stake durations
            uint256 totalBoosterAmount = (_numDurationOneStakes *
                boosterValueOne) + (_numDurationTwoStakes * boosterValueTwo);
            return totalBoosterAmount;
        } else {
            return 0;
        }
    }

    /**
     * @notice Retrieves the total booster amount for a given owner's Bipzy stakes.
     * @param owner The address of the Bipzy owner.
     * @return The total booster amount calculated based on the active Bipzy stakes and their durations.
     */
    function getBipzyBoosterInfo(
        address owner
    ) internal view returns (uint256) {
        if (ownerToBipzyTokenIds[owner][SENTINAL_TOKEN_ID] != 0) {
            uint256 currentTokenId = ownerToBipzyTokenIds[owner][
                SENTINAL_TOKEN_ID
            ];

            uint256 prevId = SENTINAL_TOKEN_ID;
            uint32 _numDurationOneStakes;
            uint32 _numDurationTwoStakes;

            // Traverse the linked list of token IDs for the staker
            while (currentTokenId != SENTINAL_TOKEN_ID) {
                prevId = currentTokenId;

                uint64 stakeDuration = idBipzyToStakeInfo[prevId].stakeEndsOn -
                    idBipzyToStakeInfo[prevId].stakeStartedOn;

                // If the stake is still active, increment the respective counter
                if (idBipzyToStakeInfo[prevId].stakeEndsOn > block.timestamp)
                    if (stakeDuration == minDuration) {
                        _numDurationOneStakes += 1;
                    } else {
                        _numDurationTwoStakes += 1;
                    }
                currentTokenId = ownerToBipzyTokenIds[owner][prevId];
            }

            // Calculate the total booster amount based on the stake durations
            uint256 totalBoosterAmount = (_numDurationOneStakes *
                boosterValueOne) + (_numDurationTwoStakes * boosterValueTwo);
            return totalBoosterAmount;
        } else {
            return 0;
        }
    }

    /* @notice Allows the contract owner to forcibly unstake one or more Bipzy NFTs.
     * @dev This function sets the `stakeEndsOn` timestamp of each specified token ID to the current block timestamp, effectively ending the stake.
     *      It emits a `ForceUnstaked` event for each token ID. This function can only be called by the contract owner.
     * @param _tokenIds An array of token IDs to be forcibly unstaked.
     */
    function forceUnstakeBipzy(uint96[] calldata _tokenIds) external onlyOwner {
        for(uint256 i; i<_tokenIds.length; i++) {
            idBipzyToStakeInfo[_tokenIds[i]].stakeEndsOn = uint32(block.timestamp);
            emit ForceUnstaked(_tokenIds[i]);
        }
    }

    /* @notice Allows the contract owner to forcibly unstake one or more Bipzy Pass NFTs.
     * @dev This function sets the `stakeEndsOn` timestamp of each specified token ID to the current block timestamp, effectively ending the stake.
     *      It emits a `ForceUnstaked` event for each token ID. This function can only be called by the contract owner.
     * @param _tokenIds An array of token IDs to be forcibly unstaked.
     */
    function forceUnstakeBipzyPass(uint96[] calldata _tokenIds) external onlyOwner {
        for(uint256 i; i<_tokenIds.length; i++) {
            idBipzyPassToStakeInfo[_tokenIds[i]].stakeEndsOn = uint32(block.timestamp);
            emit ForceUnstaked(_tokenIds[i]);
        }
    }

    /**
     * @notice Calculates the booster amount for a given staker based on their currently staked NFTs.
     * @dev The booster amount is calculated by traversing the linked list of token IDs and summing the booster values for each active stake.
     * @param owner The address of the staker for whom the booster amount is being calculated.
     * @return The total booster amount for the staker.
     */

    function getBoosterInfo(address owner) public view returns (uint256) {
        uint256 bipzyBoosterAmt = getBipzyBoosterInfo(owner);
        uint256 bipzyPassBoosterAmt = getBipzyPassBoosterInfo(owner);

        // Calculate the total booster amount based on the stake durations
        return bipzyBoosterAmt + bipzyPassBoosterAmt;
    }

    /**
     * @notice check if user has staked any rare token
     *      To get if a user is actively staking a rare NFT or not,
     *      check for each token ID in ownerToTokenIds linked list,
     *      if token ID is rare, then return true
     *      else return false
     * @param _account the account that needs to be checked
     */
    function isUserStakingRare(address _account) external view returns (bool) {
        uint256 currentTokenId = ownerToBipzyTokenIds[_account][
            SENTINAL_TOKEN_ID
        ];

        uint256 prevId = SENTINAL_TOKEN_ID;

        // Traverse the linked list of token IDs for the staker
        while (currentTokenId != SENTINAL_TOKEN_ID) {
            prevId = currentTokenId;

            // If the stake is still active, increment the respective counter
            if (
                idBipzyToStakeInfo[prevId].stakeEndsOn > block.timestamp &&
                idBipzyToStakeInfo[currentTokenId].tokenContract == i_bipzy &&
                rareTokenIds[currentTokenId]
            ) return true;

            currentTokenId = ownerToBipzyTokenIds[_account][prevId];
        }
        return false;
    }

    /**
     * @notice Only owner can update the token IDs that are rare
     * @dev by default rare token ID can only exist of Bipzy NFT and not for Bipzy Pass.
     * @param tokenId the token ID, that will be rare
     * @param isRare rare staus
     */
    function updateRareTokenId(uint96 tokenId, bool isRare) external onlyOwner {
        rareTokenIds[tokenId] = isRare;
    }

    /**
     * @notice Allows the owner to lock the contract instances (i_bipzy and i_bipzyPass).
     * @dev Once locked, the contract instances cannot be updated by calling setNewBipzyInstance or setNewBipzyPassInstance.
     */
    function lockContractInstances() public onlyOwner {
        contractInstancesLocked = true;
    }

    /**
     * @notice Updates the Bipzy NFT contract instance.
     * @param _newBipzyInstance The new Bipzy NFT contract instance.
     * @dev This function can only be called by the contract owner and if the contract instances are not locked.
     */
    function setNewBipzyInstance(IERC721 _newBipzyInstance) public onlyOwner {
        if (contractInstancesLocked) revert BPZ__ContractInstanceLocked();
        i_bipzy = _newBipzyInstance;
    }

    /**
     * @notice Updates the BipzyPass NFT contract instance.
     * @param _newBipzyPassInstance The new BipzyPass NFT contract instance.
     * @dev This function can only be called by the contract owner and if the contract instances are not locked.
     */
    function setNewBipzyPassInstance(
        IERC721 _newBipzyPassInstance
    ) public onlyOwner {
        if (contractInstancesLocked) revert BPZ__ContractInstanceLocked();

        i_bipzyPass = _newBipzyPassInstance;
    }

    /**
     * @notice Updates the minimum and maximum stake durations for the contract.
     * @dev This function can only be called by the contract owner.
     * @param _minDuration The new minimum stake duration in seconds.
     * @param _maxDuration The new maximum stake duration in seconds.
     */

    function setNewStakeDurations(
        uint64 _minDuration,
        uint64 _maxDuration
    ) public onlyOwner {
        minDuration = _minDuration;
        maxDuration = _maxDuration;
    }

    /**
     * @notice Updates the booster values for the two different stake durations.
     * @dev This function can only be called by the contract owner.
     * @param _boosterAmtOneEightyDays The new booster value for the 180-day stake duration.
     * @param _boosterAmtThreeSixtyDays The new booster value for the 360-day stake duration.
     */

    function setBoosterAmt(
        uint256 _boosterAmtOneEightyDays,
        uint256 _boosterAmtThreeSixtyDays
    ) public onlyOwner {
        boosterValueOne = _boosterAmtOneEightyDays;
        boosterValueTwo = _boosterAmtThreeSixtyDays;
    }
}
Contract Source Code
File 2 of 5: Context.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)

pragma solidity ^0.8.0;

/**
 * @dev Provides information about the current execution context, including the
 * sender of the transaction and its data. While these are generally available
 * via msg.sender and msg.data, they should not be accessed in such a direct
 * manner, since when dealing with meta-transactions the account sending and
 * paying for execution may not be the actual sender (as far as an application
 * is concerned).
 *
 * This contract is only required for intermediate, library-like contracts.
 */
abstract contract Context {
    function _msgSender() internal view virtual returns (address) {
        return msg.sender;
    }

    function _msgData() internal view virtual returns (bytes calldata) {
        return msg.data;
    }

    function _contextSuffixLength() internal view virtual returns (uint256) {
        return 0;
    }
}
Contract Source Code
File 3 of 5: 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);
}
Contract Source Code
File 4 of 5: IERC721.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (token/ERC721/IERC721.sol)

pragma solidity ^0.8.0;

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

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

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

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

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

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

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

    /**
     * @dev Safely transfers `tokenId` token from `from` to `to`, checking first that contract recipients
     * are aware of the 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 have been allowed to move this token by either {approve} or {setApprovalForAll}.
     * - If `to` refers to a smart contract, it must implement {IERC721Receiver-onERC721Received}, which is called upon a safe transfer.
     *
     * Emits a {Transfer} event.
     */
    function safeTransferFrom(address from, address to, uint256 tokenId) external;

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

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

    /**
     * @dev Approve or remove `operator` as an operator for the caller.
     * Operators can call {transferFrom} or {safeTransferFrom} for any token owned by the caller.
     *
     * Requirements:
     *
     * - The `operator` cannot be the 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);
}
Contract Source Code
File 5 of 5: Ownable.sol
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)

pragma solidity ^0.8.0;

import "../utils/Context.sol";

/**
 * @dev Contract module which provides a basic access control mechanism, where
 * there is an account (an owner) that can be granted exclusive access to
 * specific functions.
 *
 * By default, the owner account will be the one that deploys the contract. This
 * can later be changed with {transferOwnership}.
 *
 * This module is used through inheritance. It will make available the modifier
 * `onlyOwner`, which can be applied to your functions to restrict their use to
 * the owner.
 */
abstract contract Ownable is Context {
    address private _owner;

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

    /**
     * @dev Initializes the contract setting the deployer as the initial owner.
     */
    constructor() {
        _transferOwnership(_msgSender());
    }

    /**
     * @dev Throws if called by any account other than the owner.
     */
    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    /**
     * @dev Returns the address of the current owner.
     */
    function owner() public view virtual returns (address) {
        return _owner;
    }

    /**
     * @dev Throws if the sender is not the owner.
     */
    function _checkOwner() internal view virtual {
        require(owner() == _msgSender(), "Ownable: caller is not the owner");
    }

    /**
     * @dev Leaves the contract without owner. It will not be possible to call
     * `onlyOwner` functions. Can only be called by the current owner.
     *
     * NOTE: Renouncing ownership will leave the contract without an owner,
     * thereby disabling any functionality that is only available to the owner.
     */
    function renounceOwnership() public virtual onlyOwner {
        _transferOwnership(address(0));
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Can only be called by the current owner.
     */
    function transferOwnership(address newOwner) public virtual onlyOwner {
        require(newOwner != address(0), "Ownable: new owner is the zero address");
        _transferOwnership(newOwner);
    }

    /**
     * @dev Transfers ownership of the contract to a new account (`newOwner`).
     * Internal function without access restriction.
     */
    function _transferOwnership(address newOwner) internal virtual {
        address oldOwner = _owner;
        _owner = newOwner;
        emit OwnershipTransferred(oldOwner, newOwner);
    }
}
Settings
{
  "compilationTarget": {
    "contracts/Staking/BipzyStaking.sol": "BipzyStaking"
  },
  "evmVersion": "london",
  "libraries": {},
  "metadata": {
    "bytecodeHash": "ipfs",
    "useLiteralContent": true
  },
  "optimizer": {
    "enabled": true,
    "runs": 200
  },
  "remappings": []
}
ABI
[{"inputs":[{"internalType":"contract IERC721","name":"bipzyNftAddr","type":"address"},{"internalType":"contract IERC721","name":"bipzyPassNftAddr","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"BPZ__ContractInstanceLocked","type":"error"},{"inputs":[],"name":"BPZ__InvalidPrevId","type":"error"},{"inputs":[],"name":"BPZ__InvalidStakeDuration","type":"error"},{"inputs":[],"name":"BPZ__MustOwn1NFTAtLeast","type":"error"},{"inputs":[],"name":"BPZ__NFTContractNotSupported","type":"error"},{"inputs":[],"name":"BPZ__OnlyStakerCanWithdrawTheirStake","type":"error"},{"inputs":[],"name":"BPZ__StakePeriodNotOver","type":"error"},{"inputs":[],"name":"BPZ__ZeroStakePreferencesFound","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":true,"internalType":"uint96","name":"tokenId","type":"uint96"},{"indexed":true,"internalType":"contract IERC721","name":"nftContractInstance","type":"address"}],"name":"BipzyPassStakeWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":true,"internalType":"uint96","name":"tokenId","type":"uint96"},{"indexed":true,"internalType":"contract IERC721","name":"nftContractInstance","type":"address"},{"indexed":false,"internalType":"uint32","name":"stakingStartedOn","type":"uint32"},{"indexed":false,"internalType":"uint32","name":"stakingEndTimeStamp","type":"uint32"}],"name":"BipzyPassTokenStaked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":true,"internalType":"uint96","name":"tokenId","type":"uint96"},{"indexed":true,"internalType":"contract IERC721","name":"nftContractInstance","type":"address"}],"name":"BipzyStakeWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":true,"internalType":"uint96","name":"tokenId","type":"uint96"},{"indexed":true,"internalType":"contract IERC721","name":"nftContractInstance","type":"address"},{"indexed":false,"internalType":"uint32","name":"stakingStartedOn","type":"uint32"},{"indexed":false,"internalType":"uint32","name":"stakingEndTimeStamp","type":"uint32"}],"name":"BipzyTokenStaked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint96","name":"tokenId","type":"uint96"}],"name":"ForceUnstaked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"inputs":[],"name":"SENTINAL_TOKEN_ID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"boosterValueOne","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"boosterValueTwo","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"contractInstancesLocked","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint96[]","name":"_tokenIds","type":"uint96[]"}],"name":"forceUnstakeBipzy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96[]","name":"_tokenIds","type":"uint96[]"}],"name":"forceUnstakeBipzyPass","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"getBoosterInfo","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"i_bipzy","outputs":[{"internalType":"contract IERC721","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"i_bipzyPass","outputs":[{"internalType":"contract IERC721","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"idBipzyPassToStakeInfo","outputs":[{"internalType":"address","name":"staker","type":"address"},{"internalType":"uint96","name":"tokenId","type":"uint96"},{"internalType":"contract IERC721","name":"tokenContract","type":"address"},{"internalType":"uint32","name":"stakeStartedOn","type":"uint32"},{"internalType":"uint32","name":"stakeEndsOn","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"idBipzyToStakeInfo","outputs":[{"internalType":"address","name":"staker","type":"address"},{"internalType":"uint96","name":"tokenId","type":"uint96"},{"internalType":"contract IERC721","name":"tokenContract","type":"address"},{"internalType":"uint32","name":"stakeStartedOn","type":"uint32"},{"internalType":"uint32","name":"stakeEndsOn","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"isUserStakingRare","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"lockContractInstances","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"maxDuration","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"minDuration","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"ownerToBipzyPassTokenIds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"ownerToBipzyTokenIds","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"rareTokenIds","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_boosterAmtOneEightyDays","type":"uint256"},{"internalType":"uint256","name":"_boosterAmtThreeSixtyDays","type":"uint256"}],"name":"setBoosterAmt","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC721","name":"_newBipzyInstance","type":"address"}],"name":"setNewBipzyInstance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC721","name":"_newBipzyPassInstance","type":"address"}],"name":"setNewBipzyPassInstance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"_minDuration","type":"uint64"},{"internalType":"uint64","name":"_maxDuration","type":"uint64"}],"name":"setNewStakeDurations","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint96","name":"tokenId","type":"uint96"},{"internalType":"uint32","name":"stakeDuration","type":"uint32"},{"internalType":"contract IERC721","name":"nftContract","type":"address"}],"internalType":"struct BipzyStaking.StakePreference[]","name":"stakePreferences","type":"tuple[]"}],"name":"stakeBipzy","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint96","name":"tokenId","type":"uint96"},{"internalType":"uint32","name":"stakeDuration","type":"uint32"},{"internalType":"contract IERC721","name":"nftContract","type":"address"}],"internalType":"struct BipzyStaking.StakePreference[]","name":"stakePreferences","type":"tuple[]"}],"name":"stakeBipzyPass","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint96","name":"tokenId","type":"uint96"},{"internalType":"uint96","name":"prevTokenId","type":"uint96"}],"internalType":"struct BipzyStaking.StakeWithdrawalPreference[]","name":"stakeWithdrawalPreferences","type":"tuple[]"}],"name":"stakeBipzyPassWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint96","name":"tokenId","type":"uint96"},{"internalType":"uint96","name":"prevTokenId","type":"uint96"}],"internalType":"struct BipzyStaking.StakeWithdrawalPreference[]","name":"stakeWithdrawalPreferences","type":"tuple[]"}],"name":"stakeBipzyWithdrawal","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint96","name":"tokenId","type":"uint96"},{"internalType":"bool","name":"isRare","type":"bool"}],"name":"updateRareTokenId","outputs":[],"stateMutability":"nonpayable","type":"function"}]