wnmContract.sol
The core staking and governance token contract implementing the wShares reward system, XEN burning multipliers, and daily inflation mechanics.
// SPDX-License-Identifier: Unlicensed
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
interface IMintableERC20 is IERC20 {
function mint(address to, uint256 amount) external;
function burn(uint256 amount) external;
function burnFrom(address account, uint256 amount) external;
}
interface IBurnableToken {
function burn(address user, uint256 amount) external;
}
interface IBurnRedeemable {
event Redeemed(
address indexed user,
address indexed xenContract,
address indexed tokenContract,
uint256 xenAmount,
uint256 tokenAmount
);
function onTokenBurned(address user, uint256 amount) external;
}
interface IWENBlocksManager {
function updateDailyPenalty() external;
}
contract Stakeable {
/// @notice Precision factor used for reward calculations.
/// @dev Used to scale integer math to handle decimal precision.
uint256 constant PRECISION_RATE = 1E18;
/// @notice Daily inflation amount of WNT tokens
uint256 public WNT_INFLATION = 25000 * PRECISION_RATE;
/// @notice The last day on which halving was triggered
uint256 public lastHalvingDay;
/// @dev Minimum amount of days to stake the WNM for.
uint256 constant MIN_STAKE_DAYS = 7;
/// @dev Maximum amount of days to stake the WNM for.
uint256 constant MAX_STAKE_DAYS = 730;
/// @dev The rate of which wShare price is increased - 1 per day.
uint256 constant WSHARE_PRICE_INCREMENT = 1;
/// @notice WBLK token contract used for bonus multipliers and burning.
IMintableERC20 public wblk;
/// @notice XEN token contract used for bonus multipliers and burning.
IERC20 public xen;
/// @notice Reference to the WENBlocksManager contract.
/// @dev Used to update airdrop penalties during `incrementDay`.
IWENBlocksManager public wenblocksmanager;
/// @notice Minimum amount of XEN to burn.
uint256 public constant minXENamount = 1000000000 * PRECISION_RATE;
/// @notice Amount of external WNM rewards queued for next day.
uint256 public wnmRewards;
/// @notice Amount of external WBLK rewards queued for next day.
uint256 public wblkRewards;
/// @notice Amount of external WUNI rewards queued for next day.
uint256 public wuniRewards;
/// @notice Current active WEN day.
/// @dev Used for reward and stake duration tracking.
uint256 public currentDay;
/// @notice Timestamp when the contract was deployed.
/// @dev Used to determine the passing of a "WEN Day".
uint256 public immutable launchTimestamp; //Launch time in timestamp
/// @notice Number of seconds in a single WEN Day.
/// @dev Used to validate `incrementDay` trigger.
uint256 public constant DAY_IN_SECONDS = 86400; //Day in seconds
/// @notice Total WNM tokens staked by all users.
uint256 public totalStaked;
/// @notice Current price of a wShare in USD cents.
/// @dev Used in wShare calculation logic.
uint256 public wsharePrice;
/// @notice Total number of wShares currently allocated to all active stakes.
uint256 public totalwshares;
/// @notice Total number of burnt XEN.
uint256 public totalXENburnt;
/// @notice Total number of staked W.BLK.
uint256 public totalwblkstaked;
/// @notice Initializes the staking system on deployment.
/// @dev Sets the initial WEN day, initial wShare price, and pushes a dummy stakeholder to avoid index 0.
/// Also sets default external rewards to 1 to avoid division by zero.
constructor() {
stakeholders.push(); //push the list of stake holders (allow staking). Do not remove this dummy entry — used to prevent index=0 edge case
currentDay = 1; //Set the initial WEN Day to 1. Called at launch
wsharePrice = 200; //Set the initial wshare price of $200.00
launchTimestamp = block.timestamp; // Save the deployment block timestamp
lastHalvingDay = 0;
wnmRewards = 0;
wblkRewards = 0;
wuniRewards = 0;
}
/// @notice Global staking metrics per WEN day.
/// @dev Used for daily inflation and reward tracking.
/// @param inflationAmount Daily inflation reference (used for WNT).
/// @param wnmAmount Daily distributed WNM per share unit.
/// @param wblkAmount Daily distributed WBLK per share unit.
/// @param wuniAmount Daily distributed WUNI per share unit.
/// @param sharesEnding Number of wShares expiring on this day.
/// @param totalwshares Total active wShares on this day.
/// @param totalWNMstaked Total WNM staked till this day.
struct StakingInfo {
uint256 inflationAmount;
uint256 wnmAmount;
uint256 wblkAmount;
uint256 wuniAmount;
uint256 sharesEnding;
uint256 wSharesInExistence;
uint256 totalWNMstaked;
}
/// @notice Summary of a user's total staked tokens and all active stakes.
/// @dev Used by frontend to render staking dashboard.
/// @param total_amount Sum of all staked amounts by the user.
/// @param stakes Array of detailed `Stake` structs.
struct StakingSummary {
uint256 total_amount;
Stake[] stakes;
}
/// @notice Represents a single user's stake.
/// @dev Tracks stake metadata, associated user, durations, rewards, WBLK balance, and wShares.
/// @param user The address of the staker.
/// @param amount The amount of tokens staked (WNM).
/// @param stakename Custom name/tag for the stake.
/// @param startDay The WEN day the stake started.
/// @param endDay The WEN day the stake ends.
/// @param claimableWNT Accumulated WNT rewards for this stake.
/// @param claimableWNM Accumulated WNM rewards for this stake.
/// @param claimableWBLK Accumulated WBLK rewards for this stake.
/// @param claimableWUNI Accumulated WUNI rewards for this stake.
/// @param wblkamount WBLK held at the time of stake (used for bonus).
/// @param wShares Number of wShares associated with this stake.
struct Stake {
address user;
uint256 amount;
string stakename;
uint256 startDay;
uint256 endDay;
uint256 claimableWNT;
uint256 claimableWNM;
uint256 claimableWBLK;
uint256 claimableWUNI;
uint256 wblkamount;
uint256 wShares;
}
/// @notice Groups stakes per user address.
/// @dev Allows multiple stakes per user.
/// @param user Address of the stakeholder.
/// @param address_stakes Array of active and historical stakes by the user.
struct Stakeholder {
address user;
Stake[] address_stakes;
}
/// @notice Dynamic array storing all registered stakeholders.
/// @dev Each element holds the user's address and their array of individual stakes. Index 0 is reserved as a dummy.
Stakeholder[] internal stakeholders;
/// @notice Maps a user address to their stakeholder index in the `stakeholders` array.
mapping(address => uint256) internal stakes;
/// @notice Maps a user address to their burnt XEN.
mapping(address => uint256) internal xenburnt;
/// @notice Tracks staking-related data per WEN day.
/// @dev Keyed by WEN day index.
mapping(uint256 => StakingInfo) public stinfo;
/// @notice Emitted when a new stake is created.
/// @param user The address of the staker.
/// @param amount Amount of tokens staked.
/// @param stakename Name of the stake.
/// @param startDay WEN day the stake began.
/// @param endDay WEN day the stake ends.
/// @param index Index of the user in the stakeholders array.
/// @param wblkamount WBLK held at the time of staking (used for bonus).
/// @param wShares Number of wShares assigned.
event Staked(
address indexed user,
uint256 amount,
string stakename,
uint256 startDay,
uint256 endDay,
uint256 index,
uint256 wblkamount,
uint256 wShares
);
/// @notice Emitted when a stake is withdrawn and rewards claimed.
/// @param user The address of the user withdrawing.
/// @param stakeIndex Index of the stake in user's stake array.
/// @param wnmAmount Amount of WNM returned (stake + reward).
/// @param wntRewards Amount of WNT rewards returned.
/// @param wblkRewards Amount of WBLK rewards returned.
/// @param wuniRewards Amount of WUNI rewards returned.
event Withdrawn(
address indexed user,
uint256 stakeIndex,
uint256 wnmAmount,
uint256 wntRewards,
uint256 wblkRewards,
uint256 wuniRewards
);
/// @notice Emitted when a new day is triggered. Emitted by incrementDay().
/// @param day The day that has just funished.
event NewDay(uint256 day);
/// @notice Sets the WBLK token contract used for bonus calculations and burning.
/// @dev This should be set once by the WNM token contract.
/// @param _wblk The address of the deployed WBLK token (must implement `burn()`).
function _setWBLK(address _wblk) internal {
require(_wblk != address(0), "WBLK address cannot be zero");
wblk = IMintableERC20(_wblk);
}
/// @notice Sets the XEN contract used for wShares bonus.
/// @dev Called by the WNM token contract during initialization.
/// @param _address The address of the XEN token.
function _setXEN(address _address) internal {
xen = IERC20(_address);
}
/// @notice Sets the WENBlocksManager contract used for penalty updates.
/// @dev Called by the WNM token contract during initialization.
/// @param _manager The address of the deployed WENBlocksManager contract.
function _setWenblocksmanager(address _manager) internal {
wenblocksmanager = IWENBlocksManager(_manager);
}
/// @notice Returns inflation, reward data, staked and wShares for a given WEN day.
/// @param _day The WEN day to retrieve data for.
/// @return day The input day.
/// @return wntrewards The inflation value used for WNT reward calculations.
/// @return wnmrewards The WNM reward value for the day.
/// @return wblkrewards The WBLK reward value for the day.
/// @return wunirewards The WUNI reward value for the day.
/// @return expiredShares The number of wShares expiring on this day.
/// @return wSharesInExistence The number of total wShares in existance on this day.
/// @return totalWNMstaked The number of total WNM staked on this day.
function getDayInfo(uint256 _day) public view returns(uint256 day, uint256 wntrewards, uint256 wnmrewards, uint256 wblkrewards, uint256 wunirewards, uint256 expiredShares, uint256 wSharesInExistence, uint256 totalWNMstaked) {
StakingInfo storage info = stinfo[_day];
return (_day, info.inflationAmount, info.wnmAmount, info.wblkAmount, info.wuniAmount, info.sharesEnding, info.wSharesInExistence, info.totalWNMstaked);
}
/// @notice Returns the total WNM currently staked across all users.
/// @return Total amount of WNM staked.
function getTotalStaked() public view returns (uint256) {
return totalStaked;
}
/// @notice Returns the total XEN burnt by the user.
/// @return Total amount of XEN burnt by the user.
function getUserXENburnt(address user) public view returns (uint256) {
return xenburnt[user];
}
/// @notice Returns the current active WEN day.
/// @return The current day counter.
function getCurrentDay() public view returns (uint256) {
return currentDay;
}
/// @notice Returns the current USD price of 1 wShare.
/// @return The wShare price scaled by USD cents (integer).
function getwSharePrice() public view returns (uint256) {
return wsharePrice;
}
/// @notice Advances the staking system by one WEN day.
/// @dev Recalculates inflation metrics and invokes external penalty update.
/// @custom:require At least 1 full WEN day must have passed since the last update.
function incrementDay() public {
require(block.timestamp >= (currentDay * DAY_IN_SECONDS) + launchTimestamp, "24 hours have not passed yet.");
uint256 _day = currentDay + 1;
wsharePrice += WSHARE_PRICE_INCREMENT;
_calculateInflationAmount(_day);
wenblocksmanager.updateDailyPenalty();
}
/// @notice Queues external staking rewards to be distributed the next day.
/// @dev Called by WENBlocksManager after penalties are applied.
/// @param _wnmAmount Amount of WNM to distribute.
/// @param _wblkAmount Amount of WBLK to distribute.
/// @param _wuniAmount Amount of WUNI to distribute.
function _assignExternalStakingRewards(uint256 _wnmAmount, uint256 _wblkAmount, uint256 _wuniAmount) internal {
wnmRewards += _wnmAmount;
wblkRewards += _wblkAmount;
wuniRewards += _wuniAmount;
}
/// @notice Registers a new stakeholder by assigning them an index.
/// @dev Automatically invoked during first stake of a user.
/// @param staker Address of the user.
/// @return Index of the newly added stakeholder in the `stakeholders` array.
function _addStakeholder(address staker) internal returns (uint256) {
stakeholders.push();
uint256 userIndex = stakeholders.length - 1;
stakeholders[userIndex].user = staker;
stakes[staker] = userIndex;
return userIndex;
}
/// @notice Applies inflation calculations from the current day up to `_newday`.
/// @dev Also resets daily rewards and updates global staking metrics.
/// @param _newday Target day to process up to.
function _calculateInflationAmount(uint256 _newday) private {
for (uint256 _day = currentDay; _day < _newday; _day++) {
if (_day % 365 == 0 && _day > lastHalvingDay) {
WNT_INFLATION = WNT_INFLATION / 2;
lastHalvingDay = _day;
}
StakingInfo memory stakeinfo = stinfo[_day];
uint256 sharesToExpire = stinfo[_day].sharesEnding;
totalwshares = totalwshares > sharesToExpire ? totalwshares - sharesToExpire : 0;
stakeinfo.inflationAmount = totalwshares * PRECISION_RATE / WNT_INFLATION;
stakeinfo.wnmAmount = wnmRewards > 0 ? totalwshares * PRECISION_RATE / wnmRewards : 0;
stakeinfo.wblkAmount = wblkRewards > 0 ? totalwshares * PRECISION_RATE / wblkRewards : 0;
stakeinfo.wuniAmount = wuniRewards > 0 ? totalwshares * PRECISION_RATE / wuniRewards : 0;
stakeinfo.totalWNMstaked = totalStaked;
stakeinfo.wSharesInExistence = totalwshares;
stinfo[_day] = stakeinfo;
currentDay ++;
wnmRewards = 0;
wblkRewards = 0;
wuniRewards = 0;
emit NewDay(currentDay);
}
}
/// @notice Schedules wShares to expire on a future day.
/// @param _day The WEN day when shares will expire.
/// @param _amount Number of shares to expire.
function _allocateFutureExpiringShares(uint256 _day, uint256 _amount) private {
stinfo[_day].sharesEnding += _amount;
}
/// @notice Removes previously allocated expiring shares (used on withdrawal).
/// @param _day The expiration day.
/// @param _amount Number of shares to remove from expiry queue.
function _removeFutureExpiringShares(uint256 _day, uint256 _amount) private {
if (_day >= currentDay) {
stinfo[_day].sharesEnding = stinfo[_day].sharesEnding > _amount ? stinfo[_day].sharesEnding - _amount : 0;
}
}
/// @notice Calculates a multiplier based on staking duration.
/// @dev Linear between 7–730 days, scaling 1.0x to 2.5x.
/// @param _days Number of days the stake will last.
/// @return Multiplier scaled by 100 (e.g., 125 = 1.25x).
function _calculateDaysMultiplier(uint256 _days) internal pure returns (uint256) {
uint256 minDays = 7;
uint256 maxDays = 730;
uint256 minMultiplier = 100; // 1.0x
uint256 maxMultiplier = 250; // 2.5x
if (_days <= minDays) {
return minMultiplier;
} else if (_days >= maxDays) {
return maxMultiplier;
}
// Linear interpolation
uint256 multiplier = minMultiplier + ((_days - minDays) * (maxMultiplier - minMultiplier)) / (maxDays - minDays);
return multiplier;
}
/// @notice Determines bonus multiplier based on WBLK balance.
/// @dev Uses tiered brackets (≥1, ≥10, ≥50, ≥100 WBLK).
/// @param _amount Amount of WBLK tokens to lock.
/// @return Bonus multiplier in whole numbers (e.g., 12, 15, 20...).
function _calculateWBLKBonus(uint256 _amount) internal pure returns (uint256) {
if (_amount >= 100e18) return 25;
if (_amount >= 50e18) return 20;
if (_amount >= 10e18) return 15;
if (_amount >= 1e18) return 12;
if (_amount == 0) return 10;
return 10;
}
/// @notice Determines bonus multiplier based on XEN burnt.
/// @dev Uses tiered brackets (≥1B, ≥2B, ≥3B, ≥4B XEN).
/// @param owner The address that creates the stake.
/// @return Bonus multiplier in whole numbers.
function _calculateXENBonus(address owner) internal view returns (uint256) {
uint256 userXENburnt = xenburnt[owner];
return (userXENburnt / minXENamount) * 10 + 10;
}
/// @notice Calculates wShares based on amount, duration, WBLK, and airdrop.
/// @dev Applies both duration and WBLK bonus logic. Adds +10% if airdrop.
/// @param _amount WNM staked.
/// @param _stakingdays Number of lock days selected.
/// @param _wblkamount WBLK balance held by user.
/// @param isAirdrop Whether stake is airdrop-initiated.
/// @return Total number of wShares to assign to this stake.
function _calculatewSharesToReceive(address owner, uint256 _amount, uint256 _stakingdays, uint256 _wblkamount, bool isAirdrop) private view returns (uint256) {
uint256 stakingdaysMultiplier = _calculateDaysMultiplier(_stakingdays);
uint256 wblkbonus = _calculateWBLKBonus(_wblkamount);
uint256 xenbonus = _calculateXENBonus(owner);
uint256 wShares = _amount * wblkbonus * xenbonus * stakingdaysMultiplier / wsharePrice / 10;
uint256 wSharesWithBonus = wShares * 110 / 100; // 10% bonus for airdrop stake
if (isAirdrop) {
return wSharesWithBonus;
} else {
return wShares;
}
}
/// @notice Performs a new stake for a user.
/// @dev Applies all multipliers, bonus, burns WBLK, emits event.
/// @param owner Address of the staker.
/// @param _stakename Custom stake label.
/// @param _amount Amount of WNM to stake.
/// @param _stakingdays Duration of stake in WEN days.
/// @param isAirdrop Whether stake was created via airdrop (adds 10% wShare bonus).
function _stake(address owner, string memory _stakename, uint256 _amount, uint256 _stakingdays, uint256 _wblkamount, bool isAirdrop) internal {
require(_amount >= 1e18, "Stake amount must be at least 1 WNM.");
require(_stakingdays >= MIN_STAKE_DAYS && _stakingdays <= MAX_STAKE_DAYS, "Staking period not valid.");
uint256 index = stakes[owner];
uint256 wblkBalance = wblk.balanceOf(owner);
if (!isAirdrop) {
require(_wblkamount <= wblkBalance, "Insufficient W.BLK amount.");
}
uint256 _startDate = currentDay;
uint256 _endDate = _startDate + _stakingdays;
uint256 wShares = _calculatewSharesToReceive(owner, _amount, _stakingdays, _wblkamount, isAirdrop);
if (index == 0) {
index = _addStakeholder(owner);
}
if (!isAirdrop) {
wblk.burnFrom(owner, _wblkamount);
}
stakeholders[index].address_stakes.push(Stake(owner, _amount, _stakename, _startDate, _endDate, 0, 0, 0, 0, _wblkamount, wShares));
totalwshares += wShares;
totalStaked += _amount;
_allocateFutureExpiringShares(_endDate, wShares);
emit Staked(owner, _amount, _stakename, _startDate, _endDate, index, wblkBalance, wShares);
}
/// @notice Calculates total pending rewards across all tokens for a specific stake.
/// @dev Iterates over days the stake was active to compute rewards proportionally to wShares.
/// @param _current_stake The stake struct for which rewards are to be calculated.
/// @return _wntrewards Amount of WNT rewards earned.
/// @return _wnmrewards Amount of WNM rewards earned.
/// @return _wblkrewards Amount of WBLK rewards earned.
/// @return _wunirewards Amount of WUNI rewards earned.
function calculateStakeRewards(Stake memory _current_stake) internal view returns (uint256 _wntrewards, uint256 _wnmrewards, uint256 _wblkrewards, uint256 _wunirewards) {
uint256 _startDay = _current_stake.startDay;
uint256 _endDay = _current_stake.endDay;
if (_startDay == currentDay || _current_stake.amount == 0) {
return (0, 0, 0, 0);
}
uint256 _lastDay = _endDay > currentDay ? currentDay : _endDay;
uint256 _wShares = _current_stake.wShares;
for (uint256 _day = _startDay; _day < _lastDay; _day ++) {
StakingInfo storage info = stinfo[_day];
// 1000 * PRECISION_RATE /
if (info.inflationAmount > 0) {
_wntrewards += (_wShares * PRECISION_RATE) / info.inflationAmount;
}
if (info.wnmAmount > 0) {
_wnmrewards += (_wShares * PRECISION_RATE) / info.wnmAmount;
}
if (info.wblkAmount > 0) {
_wblkrewards += (_wShares * PRECISION_RATE) / info.wblkAmount;
}
if (info.wuniAmount > 0) {
_wunirewards += (_wShares * PRECISION_RATE) / info.wuniAmount;
}
}
return (_wntrewards, _wnmrewards, _wblkrewards, _wunirewards);
}
/// @notice Withdraws a specific stake by index.
/// @dev Calculates all rewards, early penalties, and cleans up stake state.
/// @param _amount If `0`, user opts for early withdraw (penalty). Otherwise must match original stake.
/// @param index The index of the stake to withdraw.
/// @return WNM returned, WNT rewards, WBLK rewards, WUNI rewards (all after penalty logic).
function _withdrawStake(uint256 _amount, uint256 index) internal returns (uint256, uint256, uint256, uint256) {
// Grab user_index which is the index to use to grab the Stake[])
uint256 user_index = stakes[msg.sender];
Stake memory current_stake = stakeholders[user_index].address_stakes[index];
uint256 stakeEndDay = current_stake.endDay;
require(current_stake.amount > 0, "Stake already withdrawn or invalid");
require(_amount == 0 || _amount == current_stake.amount);
require(current_stake.user == msg.sender);
(uint256 wntrewards, uint256 wnmrewards, uint256 wblkrewards, uint256 wunirewards) = calculateStakeRewards(current_stake);
uint256 sharesFee;
uint256 wblkBalance;
if (_amount != 0) {
require(currentDay >= stakeEndDay, "Stake is not mature yet.");
totalwshares -= current_stake.wShares;
current_stake.amount = 0;
totalStaked -= _amount;
wblkBalance = current_stake.wblkamount;
_removeFutureExpiringShares(current_stake.endDay, current_stake.wShares);
}
if (_amount == 0 && currentDay < stakeEndDay) {
sharesFee = current_stake.wShares * 10 / 100;
wblkBalance = 0;
} else {
sharesFee = 0;
wblkBalance = current_stake.wblkamount;
}
// If stake is empty, 0, then remove it from the array of stakes
if (current_stake.amount == 0) {
delete stakeholders[user_index].address_stakes[index];
} else {
stakeholders[user_index].address_stakes[index].claimableWNM = 0;
stakeholders[user_index].address_stakes[index].claimableWNT = 0;
stakeholders[user_index].address_stakes[index].claimableWBLK = 0;
stakeholders[user_index].address_stakes[index].claimableWUNI = 0;
stakeholders[user_index].address_stakes[index].startDay = currentDay;
stakeholders[user_index].address_stakes[index].wShares -= sharesFee;
}
return (_amount + wnmrewards, wntrewards, wblkrewards + wblkBalance, wunirewards);
}
/// @notice Retrieves all active stakes and pending rewards for a user.
/// @dev Aggregates across all user stakes and returns dynamic array.
/// @param _staker The wallet address to query.
/// @return A `StakingSummary` struct with total amount and per-stake breakdown.
function hasStake(address _staker) public view returns (StakingSummary memory) {
// totalStakeAmount is used to count total staked amount of the address
uint256 totalStakeAmount;
// Keep a summary in memory since we need to calculate this
StakingSummary memory summary = StakingSummary(0, stakeholders[stakes[_staker]].address_stakes);
// Itterate all stakes and grab amount of stakes
for (uint256 s = 0; s < summary.stakes.length; s += 1) {
if (summary.stakes[s].amount > 0) {
(uint256 wntrewards, uint256 wnmrewards, uint256 wblkrewards, uint256 wunirewards) = calculateStakeRewards(summary.stakes[s]);
summary.stakes[s].claimableWNT = wntrewards;
summary.stakes[s].claimableWNM = wnmrewards;
summary.stakes[s].claimableWBLK = wblkrewards;
summary.stakes[s].claimableWUNI = wunirewards;
totalStakeAmount = totalStakeAmount + summary.stakes[s].amount;
}
}
// Assign calculate amount to summary
summary.total_amount = totalStakeAmount;
return summary;
}
}
/// @title WNMToken - Main token for staking in the WEN ecosystem
/// @author Yonko
/// @notice Implements staking logic, reward distribution, and controlled minting
/// @dev Inherits from ERC20, Ownable, Stakeable, and ReentrancyGuard
contract WNMToken is ERC20, Ownable, Stakeable, ReentrancyGuard, ERC165, IBurnRedeemable {
/// @notice Address of the WNT reward token contract
IMintableERC20 public wntContract;
/// @notice Address of the WBLK reward token contract
IMintableERC20 public wblkContract;
/// @notice Address of the WUNI reward token contract
IMintableERC20 public wuniContract;
IBurnableToken public xenBurnable;
/// @notice Address of the WENBlocksManager contract authorized for minting and airdrops
address public manageraddress;
/// @notice Initializes the WNMToken contract with reward token addresses and mints initial supply
/// @dev Assigns WNT, WBLK, and WUNI contract addresses and mints initial tokens to deployer
/// @param _wntAddress Address of the WNT token contract
/// @param _wblkAddress Address of the WBLK token contract
/// @param _wuniAddress Address of the WUNI token contract
constructor(address _wntAddress, address _wblkAddress, address _wuniAddress, address _xenAddress) Ownable(msg.sender) ERC20("WENIUM", "WNM") {
wntContract = IMintableERC20(_wntAddress);
wblkContract = IMintableERC20(_wblkAddress);
wuniContract = IMintableERC20(_wuniAddress);
xenBurnable = IBurnableToken(_xenAddress);
_setWBLK(_wblkAddress);
_setXEN(_xenAddress);
_mint(msg.sender, 2e18);
}
/// @notice Allows a user or manager to initiate a stake
/// @dev Burns WNM from user if not called by manager; manager-triggered stakes apply AIRDROP logic
/// @param owner The recipient of the stake (user or airdrop target)
/// @param _stakename Human-readable name/label for the stake
/// @param _amount Amount of WNM to stake (in wei)
/// @param _stakingdays Number of days to lock tokens in stake
/// @param _wblkamount Amount of W.BLK to to stake (in wei)
function stake(address owner, string memory _stakename, uint256 _amount, uint256 _stakingdays, uint256 _wblkamount) external {
if (msg.sender != manageraddress) {
uint256 userBalance = balanceOf(msg.sender);
require(_amount <= userBalance, "DevToken: Cannot stake more than you own");
_stake(msg.sender, _stakename, _amount, _stakingdays, _wblkamount, false);
// Burn the amount of tokens on the sender
_burn(msg.sender, _amount);
} else if (msg.sender == manageraddress) {
_stake(owner, "AIRDROP", _amount, _stakingdays, _wblkamount, true); // If the staker is the WENBlocksManager, then apply bonus staking since that's an AIRDROP
}
}
/// @dev Restricts function access to the assigned manager address
modifier onlyManager() {
require(msg.sender == manageraddress, "Only the merkleTree contract can trigger the mint function");
_;
}
/// @notice Sets the manager contract authorized for airdrops and reward assignment
/// @dev Also updates the staking penalty integration by assigning the WENBlocksManager
/// @param _manageraddress Address of the new manager contract
function setManagerAddress(address _manageraddress) external onlyOwner {
require(_manageraddress != address(0), "WBLK address cannot be zero");
manageraddress = _manageraddress;
_setWenblocksmanager(_manageraddress);
}
/// @notice Mints WNM tokens to a specified address
/// @dev Restricted to the manager contract only
/// @param to Address receiving the newly minted WNM tokens
/// @param amount Amount of WNM tokens to mint (in wei)
function mint(address to, uint256 amount) external onlyManager {
_mint(to, amount);
}
/// @notice Assigns reward tokens for the upcoming inflation cycle
/// @dev Used by manager to inject external rewards into staking system
/// @param _wnmAmount Amount of WNM tokens to allocate
/// @param _wblkAmount Amount of WBLK tokens to allocate
/// @param _wuniAmount Amount of WUNI tokens to allocate
function assignExternalStakingRewards(uint256 _wnmAmount, uint256 _wblkAmount, uint256 _wuniAmount) external onlyManager {
_assignExternalStakingRewards(_wnmAmount, _wblkAmount, _wuniAmount);
}
/// @notice Withdraws a user's stake and distributes all earned rewards
/// @dev Mints WNM back to user and distributes WNT, WBLK, and WUNI rewards
/// @param amount Amount to withdraw (0 for early exit)
/// @param stake_index Index of the stake in the user's stake list
function withdrawStake(uint256 amount, uint256 stake_index) public nonReentrant {
(uint256 wnmamount, uint256 wntamount, uint256 wblkamount, uint256 wuniamount) = _withdrawStake(amount, stake_index);
// Return staked tokens to user
_mint(msg.sender, wnmamount);
wntContract.mint(msg.sender, wntamount);
wblkContract.mint(msg.sender, wblkamount);
wuniContract.mint(msg.sender, wuniamount);
}
/**
* @notice Callback function triggered when XEN tokens are burned via `burnXEN`.
* @dev Only callable by the configured XEN contract. Records total XEN burnt and emits a Redeemed event.
* @param user The address of the user who initiated the XEN burn.
* @param amount The amount of XEN that was burned.
*/
function onTokenBurned(address user, uint256 amount) external override {
require(msg.sender == address(xen), "Unauthorized burn source");
totalXENburnt += amount;
xenburnt[user] += amount;
emit Redeemed(user, address(xen), address(this), amount, 0);
}
/**
* @notice Burns a specified amount of XEN tokens from the caller’s wallet.
* @dev Requires at least `minXENamount` to proceed. Transfers control to the XEN contract via `burn()`.
* Emits a callback to `onTokenBurned()` once the burn is processed by the XEN contract.
* @param amount The amount of XEN to burn. Must be greater than or equal to `minXENamount`.
*/
function burnXEN(uint256 amount) external nonReentrant() {
require(amount >= minXENamount, "You must burn at least 1B XEN.");
require(xen.balanceOf(msg.sender) >= amount, "Insufficient balance.");
xenBurnable.burn(msg.sender , amount);
}
/**
* @notice Checks if this contract supports a given interface.
* @dev Supports IBurnRedeemable and standard ERC165 interface detection.
* @param interfaceId The interface identifier, as specified in ERC-165.
* @return True if the interface is supported, false otherwise.
*/
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool) {
return
interfaceId == type(IBurnRedeemable).interfaceId ||
super.supportsInterface(interfaceId);
}
}
Last updated