// SPDX-License-Identifier: MIT
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;
}
}
/**
* @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() {
_setOwner(_msgSender());
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_setOwner(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");
_setOwner(newOwner);
}
function _setOwner(address newOwner) private {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
interface MP {
function balanceOf(address wallet) external view returns(uint256);
function transferFrom(address from, address to, uint256 tokenId) external;
function isApprovedForAll(address wallet, address stakingAddress) external view returns(bool);
}
interface Shard {
function determineYield(uint256 timestamp) external view returns(uint256);
function mintShards(address wallet, uint256 amount) external;
}
/*
⠀*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀*⠀⠀⠀⠀⢀⣀⣀⣠⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣄⣀⣀⡀⠀⠀⠀⠀⠀
⠀*⠀⠀⠀⠀⢸⣿⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⠛⣿⡇⠀⠀⠀⠀⠀
⠀*⠀⠀⠀⠀⢸⣿⠀⢸⣿⣿⣿⣿⡿⠟⠁⠀⠀⣀⣾⣿⣿⠀⣿⡇⠀⠀⠀⠀⠀
⠀*⠀⠀⠀⠀⢸⣿⠀⢸⣿⣿⡿⠋⠀⠀⠀⣠⣾⣿⡿⠋⠁⠀⣿⡇⠀⠀⠀⠀⠀
⠀*⠀⠀⠀⠀⢸⣿⠀⢸⠟⠉⠀⠀⢀⣴⣾⣿⠿⠋⠀⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀
⠀*⠀⠀⠀⠀⢸⣿⠀⠀⠀⠀⣠⣴⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀
⠀*⠀⠀⠀⠀⢸⣿⠀⠀⣠⣾⣿⡿⠋⠁⠀⠀⠀⠀⠀⣠⣶⠀⣿⡇⠀⠀⠀⠀⠀
⠀*⠀⠀⠀⠀⢸⣿⠀⢸⣿⠿⠋⠀⠀⠀⠀⠀⢀⣠⣾⡿⠟⠀⣿⡇⠀⠀⠀⠀⠀
⠀*⠀⠀⠀⠀⢸⣿⠀⠘⠁⠀⠀⠀⠀⠀⢀⣴⣿⡿⠋⣠⣴⠀⣿⡇⠀⠀⠀⠀⠀
⠀*⠀⠀⠀⠀⢸⣿⠀⠀⠀⠀⠀⠀⣠⣾⣿⠟⢁⣠⣾⣿⣿⠀⣿⡇⠀⠀⠀⠀⠀
⠀*⠀⠀⠀⠀⢸⣿⠀⠀⠀⢀⣠⣾⡿⠋⢁⣴⣿⣿⣿⣿⣿⠀⣿⡇⠀⠀⠀⠀⠀
⠀*⠀⠀⠀⠀⢸⣿⣀⣀⣀⣈⣉⣉⣀⣀⣉⣉⣉⣉⣉⣉⣉⣀⣿⡇⠀⠀⠀⠀⠀
⠀*⠀⠀⠀⠀⠘⠛⠛⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠛⠛⠛⠃⠀⠀⠀⠀⠀
⠀*⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠛⠛⠛⠛⠋⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
* MIRRORPASS.XYZ
*/
contract Frame is Ownable {
MP private mp;
Shard private shards;
struct LockedUp {
address owner;
uint256 until;
uint256 token;
bool hasToken;
}
bool public stakingAvailable = false;
bool public lockupAvailable = false;
bool public claimingAvailable = false;
uint256 public totalStaked = 0;
mapping(address => uint256[]) private tokensHeld;
mapping(address => LockedUp) private lockedup;
mapping(uint256 => uint256) private tokenYield;
mapping(uint256 => address) private tokenToOwner;
event StakedToken(address wallet, uint256[] tokens, uint256 timestamp);
event WithdrewToken(address wallet, uint256 tokenId, uint256 timestamp);
event LockedToken(address wallet, uint256 tokenId, uint256 timestamp);
event FreeToken(address wallet, uint256 tokenId);
modifier isNotContract() {
require(tx.origin == msg.sender, "No contracts allowed");
_;
}
modifier isStakingAvailable() {
require(stakingAvailable, "Staking is currently disabled");
_;
}
modifier isLockupAvailable() {
require(lockupAvailable, "Lock Up is currently disabled");
_;
}
// this returns the index of the token we're looking for in the deposited wallets
function findToken(uint256 token, uint256[] memory tokens) private pure returns(uint256) {
uint256 index = 0;
while (tokens[index] != token) {
index++;
}
return index;
}
// this allows the user to start staking their tokens and it keeps track of the
// tokens that are staked, as well as the timestamp that they were deposited with
function stake(uint256[] memory tokens) public isStakingAvailable isNotContract {
require(tokens.length >= 1 && tokens.length <= 5, "Invalid token amount");
uint256[] storage _deposits = tokensHeld[msg.sender];
for (uint256 x = 0;x < tokens.length;x++) {
uint256 _token = tokens[x];
mp.transferFrom(msg.sender, address(this), _token);
_deposits.push(_token);
tokenYield[_token] = block.timestamp;
tokenToOwner[_token] = msg.sender;
}
totalStaked += tokens.length;
emit StakedToken(msg.sender, tokens, block.timestamp);
}
// this withdraws the staked tokens and claims any shards that weren't claimed
function withdraw(uint256[] memory tokens) public isNotContract {
require(tokens.length >= 1 && tokens.length <= 5, "Invalid token amount");
uint256 shardsGained = 0;
uint256[] storage _deposits = tokensHeld[msg.sender];
for (uint256 x = 0;x < tokens.length;x++) {
uint256 _token = tokens[x];
address _owner = tokenToOwner[_token];
require(_owner == msg.sender, "You didn't deposit these");
mp.transferFrom(address(this), _owner, _token);
uint256 index = findToken(_token, _deposits);
delete _deposits[index];
// this accumulates the shards the wallet gained from all the tokens
if (claimingAvailable) {
shardsGained += shards.determineYield(tokenYield[_token]);
}
emit WithdrewToken(_owner, _token, tokenYield[_token]);
tokenYield[_token] = 0;
delete _owner;
}
if (claimingAvailable) {
shards.mintShards(msg.sender, shardsGained);
}
totalStaked -= tokens.length;
delete shardsGained;
}
// this allows you to "withdraw" your erc20 tokens from the staked tokens
function claimShardsFromTokens(uint256[] memory tokens) public isNotContract {
require(claimingAvailable, "You're not able to withdraw your shards right now");
uint256 shardsGained = 0;
for (uint256 x = 0;x < tokens.length;x++) {
uint256 _token = tokens[x];
require(tokenToOwner[_token] == msg.sender, "You didn't deposit these");
shardsGained += shards.determineYield(tokenYield[_token]);
tokenYield[_token] = block.timestamp;
delete _token;
}
shards.mintShards(msg.sender, shardsGained);
delete shardsGained;
}
// this returns the timestamp of when the token was staked, used for determining
// the yield each token gives
function getTimeFromToken(uint256 token) public view returns (uint256) {
return tokenYield[token];
}
// returns the total amount of tokens that are staked, used on the UI & for calculating yield
function getTokensStaked(address wallet) public view returns (uint256[] memory) {
return tokensHeld[wallet];
}
// this locks in the pass into the staking contract for X amount of time, this will give access
// to the application until the lock up period is over
function lockIn(uint256 tokenId, uint256 period) public isLockupAvailable isNotContract {
LockedUp storage _lockedup = lockedup[msg.sender];
require(!_lockedup.hasToken, "You need to withdraw your current token first");
require(period > 0 && period <= 3, "You can only lock in your token for 30 to 90 days!");
mp.transferFrom(msg.sender, address(this), tokenId);
_lockedup.owner = msg.sender;
_lockedup.until = block.timestamp + (30 days * period);
_lockedup.token = tokenId;
_lockedup.hasToken = true;
totalStaked += 1;
emit LockedToken(msg.sender, tokenId, _lockedup.until);
}
// once the users lock in period is over, they are able to withdraw the token using this
function withdrawLockedUp() public isNotContract {
LockedUp storage _lockedup = lockedup[msg.sender];
require(block.timestamp >= _lockedup.until, "Your lock in period is not over yet");
mp.transferFrom(address(this), _lockedup.owner, _lockedup.token);
_lockedup.hasToken = false;
_lockedup.until = 0;
totalStaked -= 1;
emit FreeToken(_lockedup.owner, _lockedup.token);
}
// this returns the timestamp of when the locked up period ends
function getLockedInTime(address wallet) public view returns (uint256) {
LockedUp storage _lockedup = lockedup[wallet];
return _lockedup.until;
}
// this is more of a emergency case incase anything happens that requires
// people to withdraw their tokens
function clearLockedupUntil(address[] memory addresses) public onlyOwner {
for (uint x = 0;x < addresses.length;x++) {
LockedUp storage _lockedup = lockedup[addresses[x]];
_lockedup.until = 0;
}
}
// this is a emergency withdraw for locked in tokens if we need to do this any reason
function emergencyWithdrawLockedup(address[] memory addresses) public onlyOwner {
for (uint x = 0;x < addresses.length;x++) {
LockedUp storage _lockedup = lockedup[addresses[x]];
if (_lockedup.hasToken) {
mp.transferFrom(address(this), _lockedup.owner, _lockedup.token);
_lockedup.until = 0;
_lockedup.hasToken = false;
totalStaked -= 1;
}
}
}
// this is used on the dashboard to calculate the pending shards
function calculateTotalPendingShards(uint256[] memory tokens) public view returns(uint256) {
uint256 possibleShards = 0;
for (uint256 x = 0;x < tokens.length;x++) {
uint256 _token = tokens[x];
possibleShards += shards.determineYield(tokenYield[_token]);
delete _token;
}
return possibleShards;
}
// this is the erc721 contract that holds the OG mirror pass
function setTokenContract(address tokenContract) public onlyOwner {
mp = MP(tokenContract);
}
// this is the erc20 token that interacts with the ECOSYSTEM
function setShardsContract(address shardsContract) public onlyOwner {
shards = Shard(shardsContract);
}
// this enables / dsiables stake
function setStakingState(bool available) public onlyOwner {
stakingAvailable = available;
}
// this enables / disables lockIn
function setLockupState(bool available) public onlyOwner {
lockupAvailable = available;
}
// this enables / disable erc20 token minting incase something occurs
// where we need to disable this
function setShardMinting(bool available) public onlyOwner {
claimingAvailable = available;
}
}
{
"compilationTarget": {
"Frame.sol": "Frame"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"FreeToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"LockedToken","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"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"tokens","type":"uint256[]"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"StakedToken","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"wallet","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"WithdrewToken","type":"event"},{"inputs":[{"internalType":"uint256[]","name":"tokens","type":"uint256[]"}],"name":"calculateTotalPendingShards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokens","type":"uint256[]"}],"name":"claimShardsFromTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimingAvailable","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"addresses","type":"address[]"}],"name":"clearLockedupUntil","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address[]","name":"addresses","type":"address[]"}],"name":"emergencyWithdrawLockedup","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"getLockedInTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"token","type":"uint256"}],"name":"getTimeFromToken","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"wallet","type":"address"}],"name":"getTokensStaked","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"period","type":"uint256"}],"name":"lockIn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lockupAvailable","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"available","type":"bool"}],"name":"setLockupState","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"available","type":"bool"}],"name":"setShardMinting","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"shardsContract","type":"address"}],"name":"setShardsContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"available","type":"bool"}],"name":"setStakingState","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenContract","type":"address"}],"name":"setTokenContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokens","type":"uint256[]"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stakingAvailable","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalStaked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokens","type":"uint256[]"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawLockedUp","outputs":[],"stateMutability":"nonpayable","type":"function"}]