6.1 SMART CONTRACTS
Below are the main contracts for the game, besides these, there's also a Smart Contract for the Blindbox System, NFTs, Airdrop and Market Place.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/[email protected]/token/ERC20/ERC20.sol";
import "@openzeppelin/[email protected]/access/Ownable.sol";
/// @title Cryptofoot98 Token (CFOOT) by e.t.
contract Cryptofoot98 is ERC20, Ownable {
uint256 public constant MAX_SUPPLY = 100_000_000 * 10 ** 18;
bytes3 public constant FOOTPRINT = "e.t"; // Fixed to bytes3
constructor() ERC20("Cryptofoot98", "CFOOT") {
_mint(msg.sender, MAX_SUPPLY);
}
function burn(uint256 amount) public {
_burn(msg.sender, amount);
}
function decimals() public pure override returns (uint8) {
return 18;
}
// Restore renounceOwnership for decentralization
function renounceOwnership() public override onlyOwner {
super.renounceOwnership();
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/v4.9.2/contracts/access/Ownable.sol";
import "https://raw.githubusercontent.com/OpenZeppelin/openzeppelin-contracts/v4.9.2/contracts/token/ERC20/IERC20.sol";
contract CryptofootBank is Ownable {
IERC20 public cfoot;
mapping(address => uint256) public balances;
mapping(address => bool) public authorized;
uint256 public withdrawalFee;
uint256 public feeCap;
uint256 public maxWithdrawal;
uint256 public feePool;
address public stadiumContract;
uint256 public withdrawalCooldown;
mapping(address => uint256) public lastWithdrawal;
bool public withdrawalsEnabled;
mapping(address => bool) public blacklist;
uint256 public totalUserBalances;
event Deposit(address indexed user, uint256 amount);
event Withdraw(address indexed user, uint256 amount, uint256 fee);
event BalanceCredited(address indexed user, uint256 amount);
event BalanceDeducted(address indexed user, uint256 amount);
event AuthorizedAddressSet(address indexed addr, bool status);
event WithdrawalFeeSet(uint256 fee);
event MaxWithdrawalSet(uint256 maxWithdrawal);
event FeeCapSet(uint256 feeCap);
event FeePoolForwarded(uint256 amount, address to);
event StadiumContractSet(address indexed stadium);
event WithdrawalCooldownSet(uint256 cooldown);
event WithdrawalsEnabledSet(bool enabled);
event WalletBlacklisted(address indexed user, bool blacklisted);
event TotalUserBalancesUpdated(uint256 totalUserBalances);
// Developed by e.t.
constructor(IERC20 _cfoot) {
cfoot = _cfoot;
withdrawalsEnabled = true;
withdrawalCooldown = 24 hours;
}
modifier onlyAuthorized() {
require(authorized[msg.sender] || msg.sender == owner(), "Not authorized");
_;
}
modifier canWithdraw(address user) {
require(withdrawalsEnabled, "Withdrawals disabled");
require(!blacklist[user], "Blacklisted");
require(block.timestamp >= lastWithdrawal[user] + withdrawalCooldown, "Cooldown");
_;
}
function setAuthorizedAddress(address _addr, bool _status) external onlyOwner {
authorized[_addr] = _status;
emit AuthorizedAddressSet(_addr, _status);
}
function creditBalance(address _user, uint256 _amount) external onlyAuthorized {
balances[_user] += _amount;
totalUserBalances += _amount;
emit BalanceCredited(_user, _amount);
emit TotalUserBalancesUpdated(totalUserBalances);
}
function deductBalance(address _user, uint256 _amount) external onlyAuthorized {
require(balances[_user] >= _amount, "Insufficient");
balances[_user] -= _amount;
totalUserBalances -= _amount;
emit BalanceDeducted(_user, _amount);
emit TotalUserBalancesUpdated(totalUserBalances);
}
function deposit(uint256 _amount) external {
require(cfoot.transferFrom(msg.sender, address(this), _amount), "Fail");
balances[msg.sender] += _amount;
totalUserBalances += _amount;
emit Deposit(msg.sender, _amount);
emit TotalUserBalancesUpdated(totalUserBalances);
}
function withdraw(uint256 _amount) external canWithdraw(msg.sender) {
require(balances[msg.sender] >= _amount, "Insufficient");
require(_amount <= maxWithdrawal, "Exceeds max");
uint256 fee = (_amount * withdrawalFee) / 100;
if (fee > feeCap) fee = feeCap;
uint256 payout = _amount - fee;
balances[msg.sender] -= _amount;
totalUserBalances -= _amount;
feePool += fee;
lastWithdrawal[msg.sender] = block.timestamp;
require(cfoot.transfer(msg.sender, payout), "Fail");
emit Withdraw(msg.sender, payout, fee);
emit TotalUserBalancesUpdated(totalUserBalances);
}
function setWithdrawalFee(uint256 _fee) external onlyOwner {
withdrawalFee = _fee;
emit WithdrawalFeeSet(_fee);
}
function setMaxWithdrawal(uint256 _maxWithdrawal) external onlyOwner {
maxWithdrawal = _maxWithdrawal;
emit MaxWithdrawalSet(_maxWithdrawal);
}
function setFeeCap(uint256 _feeCap) external onlyOwner {
feeCap = _feeCap;
emit FeeCapSet(_feeCap);
}
function setWithdrawalCooldown(uint256 _cooldown) external onlyOwner {
withdrawalCooldown = _cooldown;
emit WithdrawalCooldownSet(_cooldown);
}
function setWithdrawalsEnabled(bool _enabled) external onlyOwner {
withdrawalsEnabled = _enabled;
emit WithdrawalsEnabledSet(_enabled);
}
function blacklistWallet(address _user, bool _bl) external onlyOwner {
blacklist[_user] = _bl;
emit WalletBlacklisted(_user, _bl);
}
function setStadiumContract(address _stadium) external onlyOwner {
stadiumContract = _stadium;
emit StadiumContractSet(_stadium);
}
function forwardFeePool() external onlyOwner {
require(stadiumContract != address(0), "Not set");
uint256 amt = feePool;
require(amt > 0, "No fees");
feePool = 0;
require(cfoot.transfer(stadiumContract, amt), "Fail");
emit FeePoolForwarded(amt, stadiumContract);
}
function contractTokenBalance() external view returns (uint256) {
return cfoot.balanceOf(address(this));
}
function checkInvariant() external view returns (bool) {
return totalUserBalances + feePool == cfoot.balanceOf(address(this));
}
function getUserLastWithdrawal(address _user) external view returns (uint256) {
return lastWithdrawal[_user];
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/access/Ownable.sol";
interface ICryptoFootBank {
function deductBalance(address user, uint256 amount) external;
function creditBalance(address user, uint256 amount) external;
function balances(address user) external view returns (uint256);
}
/**
* @title CryptoFootStakingInGame
* @notice Staking contract that uses the in‑game balance (from the Bank contract).
* Users stake their CFOOT tokens (from their in‑game balance) into one of the pre-configured pools,
* and after the lock period they can unstake to receive their original stake plus rewards.
* Pools are pre-configured as:
* - 30-day pool at 12% APR,
* - 90-day pool at 24% APR,
* - 180-day pool at 34% APR.
* @dev Developed by e.t.
*/
contract CryptoFootStakingInGame is Ownable {
ICryptoFootBank public bank;
struct Pool {
uint256 lockPeriod; // in seconds
uint256 APR; // Annual Percentage Rate (e.g., 12 means 12%)
bool exists;
}
mapping(uint256 => Pool) public pools;
uint256 public nextPoolId;
struct Stake {
uint256 amount;
uint256 startTime;
uint256 poolId;
bool withdrawn;
}
mapping(address => Stake[]) public stakes;
event PoolAdded(uint256 poolId, uint256 lockPeriod, uint256 APR);
event PoolUpdated(uint256 poolId, uint256 lockPeriod, uint256 APR);
event Staked(address indexed user, uint256 stakeIndex, uint256 poolId, uint256 amount, uint256 startTime);
event Unstaked(address indexed user, uint256 stakeIndex, uint256 poolId, uint256 amount, uint256 reward);
/**
* @notice Constructor sets the Bank contract address and pre-configures the three staking pools.
* @param _bank Address of the deployed Bank contract.
*/
constructor(ICryptoFootBank _bank) Ownable(msg.sender) {
require(address(_bank) != address(0), "Invalid Bank address");
bank = _bank;
// Pre-configure the three pools:
// 30-Day Pool: 30 days = 30 * 86400 = 2,592,000 seconds, 12% APR.
pools[nextPoolId] = Pool({ lockPeriod: 2592000, APR: 12, exists: true });
emit PoolAdded(nextPoolId, 2592000, 12);
nextPoolId++;
// 90-Day Pool: 90 days = 7,776,000 seconds, 24% APR.
pools[nextPoolId] = Pool({ lockPeriod: 7776000, APR: 24, exists: true });
emit PoolAdded(nextPoolId, 7776000, 24);
nextPoolId++;
// 180-Day Pool: 180 days = 15,552,000 seconds, 34% APR.
pools[nextPoolId] = Pool({ lockPeriod: 15552000, APR: 34, exists: true });
emit PoolAdded(nextPoolId, 15552000, 34);
nextPoolId++;
}
/**
* @notice Updates an existing staking pool.
* @param poolId The id of the pool to update.
* @param lockPeriod New lock period in seconds.
* @param APR New annual percentage rate.
*/
function updatePool(uint256 poolId, uint256 lockPeriod, uint256 APR) external onlyOwner {
require(pools[poolId].exists, "Pool does not exist");
pools[poolId].lockPeriod = lockPeriod;
pools[poolId].APR = APR;
emit PoolUpdated(poolId, lockPeriod, APR);
}
/**
* @notice Stake a specified amount from the user's in‑game balance into a pool.
* @param poolId The id of the staking pool.
* @param amount The amount of CFOOT to stake.
*/
function stake(uint256 poolId, uint256 amount) external {
require(pools[poolId].exists, "Pool does not exist");
require(amount > 0, "Amount must be > 0");
// Deduct the staked amount from the user's in‑game balance.
bank.deductBalance(msg.sender, amount);
stakes[msg.sender].push(Stake({
amount: amount,
startTime: block.timestamp,
poolId: poolId,
withdrawn: false
}));
emit Staked(msg.sender, stakes[msg.sender].length - 1, poolId, amount, block.timestamp);
}
/**
* @notice Unstake after the lock period has expired. Rewards are calculated and credited to the user's in‑game balance.
* @param stakeIndex The index of the stake in the user's stakes array.
*/
function unstake(uint256 stakeIndex) external {
require(stakeIndex < stakes[msg.sender].length, "Invalid stake index");
Stake storage s = stakes[msg.sender][stakeIndex];
require(!s.withdrawn, "Already unstaked");
Pool memory pool = pools[s.poolId];
require(block.timestamp >= s.startTime + pool.lockPeriod, "Lock period not over");
s.withdrawn = true;
// Reward calculation:
// reward = amount * APR * (lockPeriod / 365 days) / 100.
uint256 reward = (s.amount * pool.APR * pool.lockPeriod) / (365 days * 100);
uint256 totalAmount = s.amount + reward;
// Credit the user's in‑game balance with the original stake plus the reward.
bank.creditBalance(msg.sender, totalAmount);
emit Unstaked(msg.sender, stakeIndex, s.poolId, s.amount, reward);
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/math/Math.sol";
/// @title Dev Vesting Wallet for CryptoFoot
/// @dev Developed by e.t
contract CfootVesting {
address public immutable beneficiary;
IERC20 public immutable cfoot;
uint256 public immutable cliff;
uint256 public immutable start;
uint256 public immutable duration;
uint256 public released;
constructor(
address _beneficiary,
IERC20 _cfoot,
uint256 _startTimestamp,
uint256 _cliffDuration,
uint256 _vestingDuration
) {
require(_beneficiary != address(0), "Invalid beneficiary");
require(address(_cfoot) != address(0), "Invalid token address");
require(_vestingDuration > 0, "Invalid vesting duration");
beneficiary = _beneficiary;
cfoot = _cfoot;
start = _startTimestamp;
cliff = _startTimestamp + _cliffDuration;
duration = _vestingDuration;
}
function releasable() public view returns (uint256) {
return vestedAmount() - released;
}
function vestedAmount() public view returns (uint256) {
uint256 totalBalance = cfoot.balanceOf(address(this)) + released;
if (block.timestamp < cliff) {
return 0;
} else if (block.timestamp >= start + duration) {
return totalBalance;
} else {
return (totalBalance * (block.timestamp - start)) / duration;
}
}
function release() external {
uint256 amount = releasable();
require(amount > 0, "No tokens to release");
released += amount;
require(cfoot.transfer(beneficiary, amount), "Transfer failed");
}
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
interface IPancakeRouter02 {
function addLiquidity(
address tokenA,
address tokenB,
uint amountADesired,
uint amountBDesired,
uint amountAMin,
uint amountBMin,
address to,
uint deadline
) external returns (uint amountA, uint amountB, uint liquidity);
function getAmountsOut(uint amountIn, address[] calldata path) external view returns (uint[] memory amounts);
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
}
/**
* @title LiquidityManager
* @notice Manages CFOOT–USDT liquidity additions and rebalancing on PancakeSwap
* @dev Developed by e.t
*/
contract LiquidityManager is Ownable, ReentrancyGuard {
using SafeERC20 for IERC20;
IERC20 public immutable cfoot;
IERC20 public immutable usdt;
IPancakeRouter02 public immutable router;
event LiquidityAdded(uint256 amountCFOOT, uint256 amountUSDT, uint256 liquidity);
event RebalanceSell(uint256 amountCFOOT, uint256 amountUSDT);
/**
* @param _cfoot Address of the CFOOT token
* @param _usdt Address of the USDT token
* @param _router Address of the PancakeSwap V2 router
*/
constructor(address _cfoot, address _usdt, address _router) Ownable(msg.sender) {
require(_cfoot != address(0) && _usdt != address(0) && _router != address(0), "Invalid address");
cfoot = IERC20(_cfoot);
usdt = IERC20(_usdt);
router = IPancakeRouter02(_router);
}
/**
* @notice Adds CFOOT and matching USDT to the PancakeSwap pool at current price
* @param cfootAmount Amount of CFOOT from this contract's reserve
* @param amountAMin Minimum accepted CFOOT (slippage protection)
* @param amountBMin Minimum accepted USDT (slippage protection)
* @param deadline UNIX deadline timestamp for the transaction
*/
function addLiquidity(
uint256 cfootAmount,
uint256 amountAMin,
uint256 amountBMin,
uint256 deadline
) external onlyOwner nonReentrant {
require(cfootAmount > 0, "CFOOT amount must be > 0");
require(cfoot.balanceOf(address(this)) >= cfootAmount, "Insufficient CFOOT in contract");
// Fetch current USDT needed for given CFOOT at pool's price
address[] memory path = new address[](2);
path[0] = address(cfoot);
path[1] = address(usdt);
uint256 usdtAmount = router.getAmountsOut(cfootAmount, path)[1];
require(usdtAmount > 0, "Calculated USDT amount is zero");
// Pull USDT from owner into contract
SafeERC20.safeTransferFrom(usdt, msg.sender, address(this), usdtAmount);
// Approve router to pull both tokens
cfoot.approve(address(router), cfootAmount);
usdt.approve(address(router), usdtAmount);
// Add liquidity; LP tokens go to owner
(uint256 usedCFOOT, uint256 usedUSDT, uint256 liquidity) = router.addLiquidity(
address(cfoot),
address(usdt),
cfootAmount,
usdtAmount,
amountAMin,
amountBMin,
owner(),
deadline
);
emit LiquidityAdded(usedCFOOT, usedUSDT, liquidity);
}
/**
* @notice Sells CFOOT from this contract for USDT to rebalance the pool
* @param cfootAmount Amount of CFOOT to sell
* @param amountOutMin Minimum USDT expected (slippage protection)
* @param deadline UNIX deadline timestamp for the transaction
*/
function rebalanceSell(
uint256 cfootAmount,
uint256 amountOutMin,
uint256 deadline
) external onlyOwner nonReentrant {
require(cfootAmount > 0, "CFOOT amount must be > 0");
require(cfoot.balanceOf(address(this)) >= cfootAmount, "Insufficient CFOOT in contract");
// Approve router to pull CFOOT
cfoot.approve(address(router), cfootAmount);
// Perform swap: CFOOT -> USDT, sending USDT to owner
address[] memory path = new address[](2);
path[0] = address(cfoot);
path[1] = address(usdt);
uint256[] memory amounts = router.swapExactTokensForTokens(
cfootAmount,
amountOutMin,
path,
owner(),
deadline
);
emit RebalanceSell(cfootAmount, amounts[1]);
}
/**
* @notice Rescue any ERC-20 sent here by mistake
* @param token Token address to rescue
* @param to Recipient of rescued tokens
* @param amount Amount to rescue
*/
function rescueTokens(address token, address to, uint256 amount) external onlyOwner {
require(token != address(0) && to != address(0), "Invalid address");
SafeERC20.safeTransfer(IERC20(token), to, amount);
}
}
Last updated