文件 1 的 7:Context.sol
pragma solidity ^0.8.0;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
文件 2 的 7:IERC165.sol
pragma solidity ^0.8.0;
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
文件 3 的 7:IERC20.sol
pragma solidity ^0.8.0;
interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}
文件 4 的 7:IERC721.sol
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
interface IERC721 is IERC165 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external;
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external;
function transferFrom(
address from,
address to,
uint256 tokenId
) external;
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool _approved) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
文件 5 的 7:IERC721Enumerable.sol
pragma solidity ^0.8.0;
import "../IERC721.sol";
interface IERC721Enumerable is IERC721 {
function totalSupply() external view returns (uint256);
function tokenOfOwnerByIndex(address owner, uint256 index) external view returns (uint256);
function tokenByIndex(uint256 index) external view returns (uint256);
}
文件 6 的 7:Ownable.sol
pragma solidity ^0.8.0;
import "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
_transferOwnership(_msgSender());
}
modifier onlyOwner() {
_checkOwner();
_;
}
function owner() public view virtual returns (address) {
return _owner;
}
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
文件 7 的 7:STAKING.sol
pragma solidity ^0.8.7;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract Staking is Ownable {
uint256 public NFT_BASE_RATE = 1000000000000000000;
address public NFT_ADDRESS;
address public TOKEN_ADDRESS;
bool public stakingLive = false;
bool public locked = false;
mapping(uint256 => uint256) internal NftTimeStaked;
mapping(uint256 => address) internal NftToStaker;
mapping(address => uint256[]) internal StakerToNft;
mapping(uint256 => uint256) private NftToType;
mapping(address => uint256) public claimable;
uint256 type1Multiplier = 3;
uint256 type2Multiplier = 5;
uint256 type3Multiplier = 5;
event ClaimVirtual(address indexed staker, uint256 amount);
IERC721Enumerable private nft;
constructor(address nft_address, address token_address) {
if (token_address != address(0)) {
TOKEN_ADDRESS = token_address;
}
NFT_ADDRESS = nft_address;
nft = IERC721Enumerable(NFT_ADDRESS);
}
function getTokenIDsStaked(address staker)
public
view
returns (uint256[] memory)
{
return StakerToNft[staker];
}
function stakeCount() public view returns (uint256) {
return nft.balanceOf(address(this));
}
function removeIdFromArray(uint256[] storage arr, uint256 tokenId)
internal
{
uint256 length = arr.length;
for (uint256 i = 0; i < length; i++) {
if (arr[i] == tokenId) {
length--;
if (i < length) {
arr[i] = arr[length];
}
arr.pop();
break;
}
}
}
function stake(uint256[] calldata tokenIds) public {
require(stakingLive, "Staking not Live!");
uint256 id;
for (uint256 i = 0; i < tokenIds.length; i++) {
id = tokenIds[i];
require(
nft.ownerOf(id) == msg.sender && NftToStaker[id] == address(0),
"Token not owned by staker"
);
if (NftToType[id] == 0) {
NftToType[id] = 1;
}
nft.transferFrom(msg.sender, address(this), id);
StakerToNft[msg.sender].push(id);
NftTimeStaked[id] = block.timestamp;
NftToStaker[id] = msg.sender;
}
}
function unstakeAll() public {
require(
StakerToNft[msg.sender].length > 0,
"Need at least 1 staked to unstake"
);
uint256 total = 0;
for (uint256 i = StakerToNft[msg.sender].length; i > 0; i--) {
uint256 tokenId = StakerToNft[msg.sender][i - 1];
nft.transferFrom(address(this), msg.sender, tokenId);
total += calculateRewardsByTokenId(tokenId);
StakerToNft[msg.sender].pop();
NftToStaker[tokenId] = address(0);
NftTimeStaked[tokenId] = 0;
}
claimable[msg.sender] += total;
}
function unstake(uint256[] calldata tokenIds) public {
uint256 total = 0;
for (uint256 i = 0; i < tokenIds.length; i++) {
uint256 id = tokenIds[i];
require(NftToStaker[id] == msg.sender, "NOT the staker");
nft.transferFrom(address(this), msg.sender, id);
total += calculateRewardsByTokenId(id);
removeIdFromArray(StakerToNft[msg.sender], id);
NftToStaker[id] = address(0);
NftTimeStaked[id] = 0;
}
claimable[msg.sender] += total;
}
function claim(uint256 tokenId) external {
require(NftToStaker[tokenId] == msg.sender, "NOT the staker");
require(TOKEN_ADDRESS != address(0), "Token Withdraw disabled");
uint256 total = calculateRewardsByTokenId(tokenId);
NftTimeStaked[tokenId] = block.timestamp;
if (claimable[msg.sender] > 0) {
total += claimable[msg.sender];
claimable[msg.sender] = 0;
}
IERC20(TOKEN_ADDRESS).transfer(msg.sender, total);
}
function claimAll() external {
require(TOKEN_ADDRESS != address(0), "Token Withdraw disabled");
uint256 total = 0;
uint256[] memory TokenIds = StakerToNft[msg.sender];
for (uint256 i = 0; i < TokenIds.length; i++) {
uint256 id = TokenIds[i];
require(NftToStaker[id] == msg.sender, "Sender not staker");
total += calculateRewardsByTokenId(id);
NftTimeStaked[id] = block.timestamp;
}
if (claimable[msg.sender] > 0) {
total += claimable[msg.sender];
claimable[msg.sender] = 0;
}
IERC20(TOKEN_ADDRESS).transfer(msg.sender, total);
}
function claimVirtual() external {
uint256 total = 0;
uint256[] memory TokenIds = StakerToNft[msg.sender];
for (uint256 i = 0; i < TokenIds.length; i++) {
uint256 id = TokenIds[i];
require(NftToStaker[id] == msg.sender, "Sender not staker");
total += calculateRewardsByTokenId(id);
NftTimeStaked[id] = block.timestamp;
}
if (claimable[msg.sender] > 0) {
total += claimable[msg.sender];
claimable[msg.sender] = 0;
}
emit ClaimVirtual(msg.sender, total);
}
function getNftStaker(uint256 tokenId) public view returns (address) {
return NftToStaker[tokenId];
}
function isStaked(uint256 tokenId) public view returns (bool) {
return (NftToStaker[tokenId] != address(0));
}
function getType(uint256 tokenId) public view returns (uint256) {
return NftToType[tokenId];
}
function calculateRewardsByTokenId(uint256 tokenId)
public
view
returns (uint256 _rewards)
{
uint256 total = 0;
uint256 tempRewards = (block.timestamp - NftTimeStaked[tokenId]);
if (NftToType[tokenId] == 1) {
tempRewards = (tempRewards * type1Multiplier);
}
if (NftToType[tokenId] == 2) {
tempRewards = (tempRewards * type2Multiplier);
}
if (NftToType[tokenId] == 3) {
tempRewards = (tempRewards * type3Multiplier);
}
total += (((tempRewards * NFT_BASE_RATE) / 86400));
return (total);
}
function getAllRewards(address staker) public view returns (uint256) {
uint256 total = 0;
uint256[] memory tokenIds = StakerToNft[staker];
for (uint256 i = 0; i < tokenIds.length; i++) {
total += (calculateRewardsByTokenId(tokenIds[i]));
}
total += claimable[staker];
return total;
}
function getRewardsPerDay(uint256[] calldata tokenId)
public
view
returns (uint256)
{
uint256 total = 0;
for (uint256 i = 0; i < tokenId.length; i++) {
if (NftToType[tokenId[i]] == 1) {
total += type1Multiplier;
}
if (NftToType[tokenId[i]] == 2) {
total += type2Multiplier;
}
if (NftToType[tokenId[i]] == 3) {
total += type3Multiplier;
}
}
return (total * (NFT_BASE_RATE / 1 ether));
}
function setTypeList(uint256 tokenId, uint256 typeNumber)
external
onlyOwner
{
NftToType[tokenId] = typeNumber;
}
function setFullTypeList(uint256[] calldata idList, uint256 typeNumber)
external
onlyOwner
{
for (uint256 i = 0; i < idList.length; i++) {
NftToType[idList[i]] = typeNumber;
}
}
function setTypeMultiplier(uint256 typeNumber, uint256 multiplier)
external
onlyOwner
{
if (typeNumber == 1) {
type1Multiplier = multiplier;
}
if (typeNumber == 2) {
type2Multiplier = multiplier;
}
if (typeNumber == 3) {
type3Multiplier = multiplier;
}
}
function setBaseRate(uint256 baseRate) external onlyOwner {
NFT_BASE_RATE = baseRate;
}
function setTokenAddress(address tokenAddress) external onlyOwner {
TOKEN_ADDRESS = tokenAddress;
}
function emergencyUnstake() external payable onlyOwner {
require(locked == true, "lock is on");
uint256 currSupply = nft.totalSupply();
for (uint256 i = 0; i < currSupply; i++) {
if (NftToStaker[i] != address(0)) {
address sendAddress = NftToStaker[i];
nft.transferFrom(address(this), sendAddress, i);
}
}
}
function returnLockToggle() public onlyOwner {
locked = !locked;
}
function toggle() external onlyOwner {
stakingLive = !stakingLive;
}
function withdraw(uint256 bal) external onlyOwner {
uint256 balance = bal;
if (balance == 0) {
balance = IERC20(TOKEN_ADDRESS).balanceOf(address(this));
}
IERC20(TOKEN_ADDRESS).transfer(msg.sender, balance);
}
}
{
"compilationTarget": {
"contracts/STAKING.sol": "Staking"
},
"evmVersion": "london",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"nft_address","type":"address"},{"internalType":"address","name":"token_address","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"staker","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ClaimVirtual","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":"NFT_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"NFT_BASE_RATE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKEN_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"calculateRewardsByTokenId","outputs":[{"internalType":"uint256","name":"_rewards","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claimVirtual","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"claimable","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"emergencyUnstake","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"getAllRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getNftStaker","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenId","type":"uint256[]"}],"name":"getRewardsPerDay","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"staker","type":"address"}],"name":"getTokenIDsStaked","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getType","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"isStaked","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"locked","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":[],"name":"returnLockToggle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"baseRate","type":"uint256"}],"name":"setBaseRate","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"idList","type":"uint256[]"},{"internalType":"uint256","name":"typeNumber","type":"uint256"}],"name":"setFullTypeList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"setTokenAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"typeNumber","type":"uint256"}],"name":"setTypeList","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"typeNumber","type":"uint256"},{"internalType":"uint256","name":"multiplier","type":"uint256"}],"name":"setTypeMultiplier","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stakeCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stakingLive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"toggle","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"tokenIds","type":"uint256[]"}],"name":"unstake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unstakeAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"bal","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"}]