This contract's source code is verified! Compiler
0.8.26+commit.8a97fa7a
File 2 of 8: HeistAchievements.sol
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/Ownable.sol";
contract HeistAchievements is Ownable {
struct Achievement {
string name;
string description;
uint256 requirement;
uint256 rewardMultiplier;
bool exists;
}
mapping(uint256 => Achievement) public achievements;
mapping(address => mapping(uint256 => bool)) public userAchievements;
mapping(address => uint256[]) public userAchievementsList;
event AchievementUnlocked(address user, uint256 achievementId);
event AchievementCreated(uint256 achievementId, string name);
constructor() Ownable(msg.sender) {
createAchievement(
1,
"First Time Heister",
"Complete your first heist",
1,
110
);
createAchievement(
2,
"Diamond Hands",
"Hold through a high-risk event",
1,
120
);
createAchievement(
3,
"Master Thief",
"Complete 10 successful heists",
10,
150
);
createAchievement(
4,
"Whale Alert",
"Stake more than 100,000 SAPU",
100000 * 10**18,
200
);
createAchievement(
5,
"OG Crew Member",
"Participate in heists for over 30 days",
30 days,
130
);
}
function createAchievement(
uint256 id,
string memory name,
string memory description,
uint256 requirement,
uint256 rewardMultiplier
) public onlyOwner {
require(!achievements[id].exists, "Achievement already exists");
achievements[id] = Achievement(name, description, requirement, rewardMultiplier, true);
emit AchievementCreated(id, name);
}
function unlockAchievement(address user, uint256 achievementId) external onlyOwner {
require(achievements[achievementId].exists, "Achievement doesn't exist");
require(!userAchievements[user][achievementId], "Achievement already unlocked");
userAchievements[user][achievementId] = true;
userAchievementsList[user].push(achievementId);
emit AchievementUnlocked(user, achievementId);
}
function getUserAchievements(address user) external view returns (uint256[] memory) {
return userAchievementsList[user];
}
function getAchievement(uint256 id) external view returns (Achievement memory) {
require(achievements[id].exists, "Achievement doesn't exist");
return achievements[id];
}
}
File 3 of 8: HeistEvents.sol
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/Ownable.sol";
contract HeistEvents is Ownable {
struct Event {
uint256 startTime;
uint256 duration;
uint256 rewardMultiplier;
uint256 riskLevel;
bool isActive;
}
Event public currentEvent;
uint256 public constant MIN_DURATION = 1 hours;
uint256 public constant MAX_DURATION = 24 hours;
event HeistEventCreated(uint256 startTime, uint256 duration, uint256 rewardMultiplier);
event HeistEventEnded(uint256 endTime);
constructor() Ownable(msg.sender) {
}
function scheduleEvent(
uint256 startTime,
uint256 duration,
uint256 rewardMultiplier,
uint256 riskLevel
) external onlyOwner {
require(startTime > block.timestamp, "Start time must be in future");
require(duration >= MIN_DURATION && duration <= MAX_DURATION, "Invalid duration");
require(!currentEvent.isActive, "Event already active");
currentEvent = Event(startTime, duration, rewardMultiplier, riskLevel, true);
emit HeistEventCreated(startTime, duration, rewardMultiplier);
}
function endEvent() external onlyOwner {
require(currentEvent.isActive, "No active event");
currentEvent.isActive = false;
emit HeistEventEnded(block.timestamp);
}
function getCurrentMultiplier() public view returns (uint256) {
if (!currentEvent.isActive) return 100;
if (block.timestamp < currentEvent.startTime) return 100;
if (block.timestamp > currentEvent.startTime + currentEvent.duration) return 100;
return currentEvent.rewardMultiplier;
}
}
File 4 of 8: HeistOperations.sol
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./HeistRoles.sol";
import "./HeistEvents.sol";
import "./HeistAchievements.sol";
interface IHeistAchievements {
struct Achievement {
string name;
uint256 requirement;
uint256 rewardMultiplier;
bool exists;
}
function getUserAchievements(address user) external view returns (uint256[] memory);
function getAchievement(uint256 id) external view returns (Achievement memory);
}
contract HeistOperations is Ownable, ReentrancyGuard {
struct Heist {
uint256 minStake;
uint256 maxStake;
uint256 duration;
uint256 rewardRate;
bool active;
}
struct UserPosition {
uint256 amount;
uint256 startTime;
uint256 heistTier;
}
IERC20 public token;
HeistRoles public roles;
HeistEvents public events;
HeistAchievements public achievements;
mapping(uint256 => Heist) public heistTiers;
mapping(address => UserPosition) public userPositions;
address[] public activeParticipants;
mapping(address => bool) public isActiveParticipant;
uint256 public constant EARLY_EXIT_PENALTY = 10;
uint256 public maxDailyHeists = 100;
uint256 public dailyHeistCount;
uint256 public lastResetTimestamp;
uint256 public totalStaked;
uint256 public constant PRECISION = 1e12;
event HeistJoined(address user, uint256 amount, uint256 tier);
event HeistExited(address user, uint256 amount);
event DailyHeistLimitUpdated(uint256 newLimit);
constructor () Ownable(msg.sender) {
token = IERC20(0xDbB2E40d67C1C5C3119f69282f425d3e7764D6DB);
roles = HeistRoles(0x03C8026c6474B8b4fb75c6542420D27c9138a5c3);
events = HeistEvents(0x809b41A349a3d7dDe820dDc69e23BA8a32Fc5743);
achievements = HeistAchievements(0xC233B0AAE8f6A06c6deFd28b1B079497AeF98f73);
heistTiers[1] = Heist(
100 * 10**18,
1000 * 10**18,
4 hours,
50,
true
);
heistTiers[2] = Heist(
500 * 10**18,
2500 * 10**18,
12 hours,
75,
true
);
heistTiers[3] = Heist(
1000 * 10**18,
5000 * 10**18,
1 days,
100,
true
);
heistTiers[4] = Heist(
2500 * 10**18,
10000 * 10**18,
3 days,
150,
true
);
heistTiers[5] = Heist(
5000 * 10**18,
25000 * 10**18,
7 days,
200,
true
);
heistTiers[6] = Heist(
10000 * 10**18,
50000 * 10**18,
14 days,
300,
true
);
}
function setMaxDailyHeists(uint256 _newLimit) external onlyOwner {
maxDailyHeists = _newLimit;
emit DailyHeistLimitUpdated(_newLimit);
}
function joinHeist(uint256 amount, uint256 tier) external nonReentrant {
if (block.timestamp >= lastResetTimestamp + 24 hours) {
dailyHeistCount = 0;
lastResetTimestamp = block.timestamp;
}
require(dailyHeistCount < maxDailyHeists, "Daily heist limit reached");
require(heistTiers[tier].active, "Invalid heist tier");
require(amount >= heistTiers[tier].minStake, "Below minimum stake");
require(amount <= heistTiers[tier].maxStake, "Above maximum stake");
UserPosition storage position = userPositions[msg.sender];
require(position.amount == 0, "Already in a heist");
token.transferFrom(msg.sender, address(this), amount);
position.amount = amount;
position.startTime = block.timestamp;
position.heistTier = tier;
if (!isActiveParticipant[msg.sender]) {
activeParticipants.push(msg.sender);
isActiveParticipant[msg.sender] = true;
}
dailyHeistCount++;
totalStaked += amount;
emit HeistJoined(msg.sender, amount, tier);
}
function exitHeistEarly() external nonReentrant {
UserPosition storage position = userPositions[msg.sender];
require(position.amount > 0, "No active heist");
uint256 amount = position.amount;
uint256 penalty = (amount * EARLY_EXIT_PENALTY) / 100;
uint256 rewards = calculateRewards(msg.sender);
position.amount = 0;
totalStaked -= amount;
removeFromActiveParticipants(msg.sender);
token.transfer(msg.sender, (amount - penalty) + rewards);
emit HeistExited(msg.sender, (amount - penalty) + rewards);
}
function removeFromActiveParticipants(address user) internal {
if (isActiveParticipant[user]) {
for (uint i = 0; i < activeParticipants.length; i++) {
if (activeParticipants[i] == user) {
activeParticipants[i] = activeParticipants[activeParticipants.length - 1];
activeParticipants.pop();
isActiveParticipant[user] = false;
break;
}
}
}
}
function calculateRewards(address user) public view returns (uint256) {
UserPosition storage position = userPositions[user];
if (position.amount == 0) return 0;
uint256 timeStaked = block.timestamp - position.startTime;
uint256 rewardRate = heistTiers[position.heistTier].rewardRate;
uint256 reward = (position.amount * rewardRate * timeStaked) / (365 days * 100);
return reward;
}
function getTotalMultiplier(address user) public view returns (uint256) {
uint256 totalMultiplier = 100;
HeistRoles.Role memory userRole = roles.getUserRole(user);
uint256 roleMultiplier = userRole.rewardMultiplier;
if (roleMultiplier == 0) roleMultiplier = 100;
totalMultiplier = totalMultiplier * roleMultiplier / 100;
uint256 eventMultiplier = events.getCurrentMultiplier();
totalMultiplier = totalMultiplier * eventMultiplier / 100;
uint256[] memory userAchievements = achievements.getUserAchievements(user);
for (uint i = 0; i < userAchievements.length; i++) {
IHeistAchievements.Achievement memory achievement = IHeistAchievements(address(achievements)).getAchievement(userAchievements[i]);
totalMultiplier = totalMultiplier * achievement.rewardMultiplier / 100;
}
return totalMultiplier;
}
function getAllActiveHeists() external view returns (
address[] memory users,
uint256[] memory amounts,
uint256[] memory tiers,
uint256[] memory startTimes,
uint256[] memory endTimes
) {
uint256 count = 0;
for (uint256 i = 0; i < activeParticipants.length; i++) {
if (userPositions[activeParticipants[i]].amount > 0) {
count++;
}
}
users = new address[](count);
amounts = new uint256[](count);
tiers = new uint256[](count);
startTimes = new uint256[](count);
endTimes = new uint256[](count);
uint256 index = 0;
for (uint256 i = 0; i < activeParticipants.length; i++) {
address participant = activeParticipants[i];
UserPosition storage position = userPositions[participant];
if (position.amount > 0) {
users[index] = participant;
amounts[index] = position.amount;
tiers[index] = position.heistTier;
startTimes[index] = position.startTime;
endTimes[index] = position.startTime + heistTiers[position.heistTier].duration;
index++;
}
}
}
function updateHeistTier(
uint256 tier,
uint256 minStake,
uint256 maxStake,
uint256 duration,
uint256 rewardRate,
bool active
) external onlyOwner {
heistTiers[tier] = Heist(minStake, maxStake, duration, rewardRate, active);
}
function getUserPosition(address user) external view returns (
uint256 amount,
uint256 startTime,
uint256 heistTier,
uint256 pendingRewards
) {
UserPosition storage position = userPositions[user];
return (
position.amount,
position.startTime,
position.heistTier,
calculateRewards(user)
);
}
function emergencyWithdraw() external nonReentrant {
UserPosition storage position = userPositions[msg.sender];
require(position.amount > 0, "No stake to withdraw");
uint256 amount = position.amount;
position.amount = 0;
totalStaked -= amount;
removeFromActiveParticipants(msg.sender);
token.transfer(msg.sender, amount);
}
function getActiveParticipantsCount() external view returns (uint256) {
return activeParticipants.length;
}
function recoverERC20(address tokenAddress, uint256 amount) external onlyOwner {
require(tokenAddress != address(token), "Cannot recover staking token");
IERC20(tokenAddress).transfer(owner(), amount);
}
function exitHeist() external nonReentrant {
UserPosition storage position = userPositions[msg.sender];
require(position.amount > 0, "No active heist");
require(
block.timestamp >= position.startTime + heistTiers[position.heistTier].duration,
"Heist duration not completed"
);
uint256 amount = position.amount;
uint256 rewards = calculateRewards(msg.sender);
position.amount = 0;
totalStaked -= amount;
removeFromActiveParticipants(msg.sender);
token.transfer(msg.sender, amount + rewards);
emit HeistExited(msg.sender, amount + rewards);
}
function depositTokens(uint256 amount) external onlyOwner {
require(amount > 0, "Amount must be greater than 0");
token.transferFrom(msg.sender, address(this), amount);
}
function withdrawTokens(uint256 amount) external onlyOwner {
require(amount > 0, "Amount must be greater than 0");
require(
token.balanceOf(address(this)) - totalStaked >= amount,
"Cannot withdraw staked tokens"
);
token.transfer(msg.sender, amount);
}
function debugRewards(address user) public view returns (
uint256 timeStaked,
uint256 rewardRate,
uint256 blockTime,
uint256 stakedTokens,
uint256 rewardTokens
) {
UserPosition storage position = userPositions[user];
timeStaked = block.timestamp - position.startTime;
rewardRate = heistTiers[position.heistTier].rewardRate;
uint256 reward = (position.amount * rewardRate * timeStaked) / (365 days * 100);
return (
timeStaked,
rewardRate,
block.timestamp,
position.amount / 1e18,
reward / 1e18
);
}
}
File 5 of 8: HeistRoles.sol
pragma solidity ^0.8.19;
import "@openzeppelin/contracts/access/Ownable.sol";
contract HeistRoles is Ownable {
struct Role {
string name;
uint256 minStake;
uint256 rewardMultiplier;
uint256 cooldownPeriod;
}
mapping(uint256 => Role) public roles;
mapping(address => uint256) public userRoles;
uint256 public constant LOOKOUT = 1;
uint256 public constant SAFECRACKER = 2;
uint256 public constant MASTERMIND = 3;
event RoleAssigned(address user, uint256 roleId);
event RoleUpdated(uint256 roleId, string name, uint256 minStake, uint256 rewardMultiplier);
constructor() Ownable(msg.sender) {
roles[LOOKOUT] = Role("Lookout", 1000 * 10**18, 100, 1 days);
roles[SAFECRACKER] = Role("Safecracker", 5000 * 10**18, 150, 3 days);
roles[MASTERMIND] = Role("Mastermind", 10000 * 10**18, 200, 7 days);
}
function assignRole(address user, uint256 roleId) external onlyOwner {
require(roleId > 0 && roleId <= 3, "Invalid role");
userRoles[user] = roleId;
emit RoleAssigned(user, roleId);
}
function updateRole(
uint256 roleId,
string memory name,
uint256 minStake,
uint256 rewardMultiplier,
uint256 cooldownPeriod
) external onlyOwner {
require(roleId > 0 && roleId <= 3, "Invalid role");
roles[roleId] = Role(name, minStake, rewardMultiplier, cooldownPeriod);
emit RoleUpdated(roleId, name, minStake, rewardMultiplier);
}
function getRole(uint256 roleId) external view returns (Role memory) {
return roles[roleId];
}
function getUserRole(address user) external view returns (Role memory) {
uint256 roleId = userRoles[user];
return roles[roleId];
}
}
File 8 of 8: ReentrancyGuard.sol
{
"compilationTarget": {
"contracts/HeistOperations.sol": "HeistOperations"
},
"evmVersion": "cancun",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": [
":@openzeppelin/contracts/=./.deps/npm/@openzeppelin/contracts/",
":@uniswap/v3-core/=./.deps/npm/@uniswap/v3-core/"
]
}
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"newLimit","type":"uint256"}],"name":"DailyHeistLimitUpdated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"HeistExited","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"tier","type":"uint256"}],"name":"HeistJoined","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":"EARLY_EXIT_PENALTY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PRECISION","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"achievements","outputs":[{"internalType":"contract HeistAchievements","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"activeParticipants","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"calculateRewards","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dailyHeistCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"debugRewards","outputs":[{"internalType":"uint256","name":"timeStaked","type":"uint256"},{"internalType":"uint256","name":"rewardRate","type":"uint256"},{"internalType":"uint256","name":"blockTime","type":"uint256"},{"internalType":"uint256","name":"stakedTokens","type":"uint256"},{"internalType":"uint256","name":"rewardTokens","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"depositTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"emergencyWithdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"events","outputs":[{"internalType":"contract HeistEvents","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"exitHeist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"exitHeistEarly","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getActiveParticipantsCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getAllActiveHeists","outputs":[{"internalType":"address[]","name":"users","type":"address[]"},{"internalType":"uint256[]","name":"amounts","type":"uint256[]"},{"internalType":"uint256[]","name":"tiers","type":"uint256[]"},{"internalType":"uint256[]","name":"startTimes","type":"uint256[]"},{"internalType":"uint256[]","name":"endTimes","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getTotalMultiplier","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"}],"name":"getUserPosition","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"heistTier","type":"uint256"},{"internalType":"uint256","name":"pendingRewards","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"heistTiers","outputs":[{"internalType":"uint256","name":"minStake","type":"uint256"},{"internalType":"uint256","name":"maxStake","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"rewardRate","type":"uint256"},{"internalType":"bool","name":"active","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isActiveParticipant","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"tier","type":"uint256"}],"name":"joinHeist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"lastResetTimestamp","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxDailyHeists","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"recoverERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"roles","outputs":[{"internalType":"contract HeistRoles","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_newLimit","type":"uint256"}],"name":"setMaxDailyHeists","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"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":"tier","type":"uint256"},{"internalType":"uint256","name":"minStake","type":"uint256"},{"internalType":"uint256","name":"maxStake","type":"uint256"},{"internalType":"uint256","name":"duration","type":"uint256"},{"internalType":"uint256","name":"rewardRate","type":"uint256"},{"internalType":"bool","name":"active","type":"bool"}],"name":"updateHeistTier","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"userPositions","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"startTime","type":"uint256"},{"internalType":"uint256","name":"heistTier","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdrawTokens","outputs":[],"stateMutability":"nonpayable","type":"function"}]