// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)pragmasolidity ^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.
*/abstractcontractContext{
function_msgSender() internalviewvirtualreturns (address) {
returnmsg.sender;
}
function_msgData() internalviewvirtualreturns (bytescalldata) {
returnmsg.data;
}
}
Contract Source Code
File 3 of 12: ERC20.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.7.0) (token/ERC20/ERC20.sol)pragmasolidity ^0.8.0;import"./IERC20.sol";
import"./extensions/IERC20Metadata.sol";
import"../../utils/Context.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/contractERC20isContext, IERC20, IERC20Metadata{
mapping(address=>uint256) private _balances;
mapping(address=>mapping(address=>uint256)) private _allowances;
uint256private _totalSupply;
stringprivate _name;
stringprivate _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* The default value of {decimals} is 18. To select a different value for
* {decimals} you should overload it.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/constructor(stringmemory name_, stringmemory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/functionname() publicviewvirtualoverridereturns (stringmemory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/functionsymbol() publicviewvirtualoverridereturns (stringmemory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the value {ERC20} uses, unless this function is
* overridden;
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/functiondecimals() publicviewvirtualoverridereturns (uint8) {
return18;
}
/**
* @dev See {IERC20-totalSupply}.
*/functiontotalSupply() publicviewvirtualoverridereturns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/functionbalanceOf(address account) publicviewvirtualoverridereturns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/functiontransfer(address to, uint256 amount) publicvirtualoverridereturns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
returntrue;
}
/**
* @dev See {IERC20-allowance}.
*/functionallowance(address owner, address spender) publicviewvirtualoverridereturns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/functionapprove(address spender, uint256 amount) publicvirtualoverridereturns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
returntrue;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
* - the caller must have allowance for ``from``'s tokens of at least
* `amount`.
*/functiontransferFrom(addressfrom,
address to,
uint256 amount
) publicvirtualoverridereturns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
returntrue;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/functionincreaseAllowance(address spender, uint256 addedValue) publicvirtualreturns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
returntrue;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/functiondecreaseAllowance(address spender, uint256 subtractedValue) publicvirtualreturns (bool) {
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
returntrue;
}
/**
* @dev Moves `amount` of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
*/function_transfer(addressfrom,
address to,
uint256 amount
) internalvirtual{
require(from!=address(0), "ERC20: transfer from the zero address");
require(to !=address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
}
_balances[to] += amount;
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/function_mint(address account, uint256 amount) internalvirtual{
require(account !=address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
_balances[account] += amount;
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/function_burn(address account, uint256 amount) internalvirtual{
require(account !=address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
}
_totalSupply -= amount;
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/function_approve(address owner,
address spender,
uint256 amount
) internalvirtual{
require(owner !=address(0), "ERC20: approve from the zero address");
require(spender !=address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `amount`.
*
* Does not update the allowance amount in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Might emit an {Approval} event.
*/function_spendAllowance(address owner,
address spender,
uint256 amount
) internalvirtual{
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance !=type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/function_beforeTokenTransfer(addressfrom,
address to,
uint256 amount
) internalvirtual{}
/**
* @dev Hook that is called after any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/function_afterTokenTransfer(addressfrom,
address to,
uint256 amount
) internalvirtual{}
}
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (utils/introspection/IERC165.sol)pragmasolidity ^0.8.0;/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/interfaceIERC165{
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/functionsupportsInterface(bytes4 interfaceId) externalviewreturns (bool);
}
Contract Source Code
File 7 of 12: IERC20.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)pragmasolidity ^0.8.0;/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/interfaceIERC20{
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/eventTransfer(addressindexedfrom, addressindexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/eventApproval(addressindexed owner, addressindexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/functiontotalSupply() externalviewreturns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/functionbalanceOf(address account) externalviewreturns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/functiontransfer(address to, uint256 amount) externalreturns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/functionallowance(address owner, address spender) externalviewreturns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/functionapprove(address spender, uint256 amount) externalreturns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/functiontransferFrom(addressfrom,
address to,
uint256 amount
) externalreturns (bool);
}
Contract Source Code
File 8 of 12: IERC20Metadata.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)pragmasolidity ^0.8.0;import"../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/interfaceIERC20MetadataisIERC20{
/**
* @dev Returns the name of the token.
*/functionname() externalviewreturns (stringmemory);
/**
* @dev Returns the symbol of the token.
*/functionsymbol() externalviewreturns (stringmemory);
/**
* @dev Returns the decimals places of the token.
*/functiondecimals() externalviewreturns (uint8);
}
// SPDX-License-Identifier: MITpragmasolidity ^0.8.10;import"abdk-libraries-solidity/ABDKMath64x64.sol";
libraryMath{
functionmin(uint256 a, uint256 b) externalpurereturns (uint256) {
if (a > b) return b;
return a;
}
functionmax(uint256 a, uint256 b) externalpurereturns (uint256) {
if (a > b) return a;
return b;
}
functionlogX64(uint256 x) externalpurereturns (int128) {
return ABDKMath64x64.log_2(ABDKMath64x64.fromUInt(x));
}
}
Contract Source Code
File 12 of 12: XENCrypto.sol
// SPDX-License-Identifier: MITpragmasolidity ^0.8.10;import"./Math.sol";
import"@openzeppelin/contracts/token/ERC20/ERC20.sol";
import"@openzeppelin/contracts/interfaces/IERC165.sol";
import"abdk-libraries-solidity/ABDKMath64x64.sol";
import"./interfaces/IStakingToken.sol";
import"./interfaces/IRankedMintingToken.sol";
import"./interfaces/IBurnableToken.sol";
import"./interfaces/IBurnRedeemable.sol";
contractXENCryptoisContext, IRankedMintingToken, IStakingToken, IBurnableToken, ERC20("XEN Crypto", "XEN") {
usingMathforuint256;
usingABDKMath64x64forint128;
usingABDKMath64x64foruint256;
// INTERNAL TYPE TO DESCRIBE A XEN MINT INFOstructMintInfo {
address user;
uint256 term;
uint256 maturityTs;
uint256 rank;
uint256 amplifier;
uint256 eaaRate;
}
// INTERNAL TYPE TO DESCRIBE A XEN STAKEstructStakeInfo {
uint256 term;
uint256 maturityTs;
uint256 amount;
uint256 apy;
}
// PUBLIC CONSTANTSuint256publicconstant SECONDS_IN_DAY =3_600*24;
uint256publicconstant DAYS_IN_YEAR =365;
uint256publicconstant GENESIS_RANK =1;
uint256publicconstant MIN_TERM =1* SECONDS_IN_DAY -1;
uint256publicconstant MAX_TERM_START =100* SECONDS_IN_DAY;
uint256publicconstant MAX_TERM_END =1_000* SECONDS_IN_DAY;
uint256publicconstant TERM_AMPLIFIER =15;
uint256publicconstant TERM_AMPLIFIER_THRESHOLD =5_000;
uint256publicconstant REWARD_AMPLIFIER_START =3_000;
uint256publicconstant REWARD_AMPLIFIER_END =1;
uint256publicconstant EAA_PM_START =100;
uint256publicconstant EAA_PM_STEP =1;
uint256publicconstant EAA_RANK_STEP =100_000;
uint256publicconstant WITHDRAWAL_WINDOW_DAYS =7;
uint256publicconstant MAX_PENALTY_PCT =99;
uint256publicconstant XEN_MIN_STAKE =0;
uint256publicconstant XEN_MIN_BURN =0;
uint256publicconstant XEN_APY_START =20;
uint256publicconstant XEN_APY_DAYS_STEP =90;
uint256publicconstant XEN_APY_END =2;
stringpublicconstant AUTHORS ="@MrJackLevin @lbelyaev faircrypto.org";
// PUBLIC STATE, READABLE VIA NAMESAKE GETTERSuint256publicimmutable genesisTs;
uint256public globalRank = GENESIS_RANK;
uint256public activeMinters;
uint256public activeStakes;
uint256public totalXenStaked;
// user address => XEN mint infomapping(address=> MintInfo) public userMints;
// user address => XEN stake infomapping(address=> StakeInfo) public userStakes;
// user address => XEN burn amountmapping(address=>uint256) public userBurns;
// CONSTRUCTORconstructor() {
genesisTs =block.timestamp;
}
// PRIVATE METHODS/**
* @dev calculates current MaxTerm based on Global Rank
* (if Global Rank crosses over TERM_AMPLIFIER_THRESHOLD)
*/function_calculateMaxTerm() privateviewreturns (uint256) {
if (globalRank > TERM_AMPLIFIER_THRESHOLD) {
uint256 delta = globalRank.fromUInt().log_2().mul(TERM_AMPLIFIER.fromUInt()).toUInt();
uint256 newMax = MAX_TERM_START + delta * SECONDS_IN_DAY;
return Math.min(newMax, MAX_TERM_END);
}
return MAX_TERM_START;
}
/**
* @dev calculates Withdrawal Penalty depending on lateness
*/function_penalty(uint256 secsLate) privatepurereturns (uint256) {
// =MIN(2^(daysLate+3)/window-1,99)uint256 daysLate = secsLate / SECONDS_IN_DAY;
if (daysLate > WITHDRAWAL_WINDOW_DAYS -1) return MAX_PENALTY_PCT;
uint256 penalty = (uint256(1) << (daysLate +3)) / WITHDRAWAL_WINDOW_DAYS -1;
return Math.min(penalty, MAX_PENALTY_PCT);
}
/**
* @dev calculates net Mint Reward (adjusted for Penalty)
*/function_calculateMintReward(uint256 cRank,
uint256 term,
uint256 maturityTs,
uint256 amplifier,
uint256 eeaRate
) privateviewreturns (uint256) {
uint256 secsLate =block.timestamp- maturityTs;
uint256 penalty = _penalty(secsLate);
uint256 rankDelta = Math.max(globalRank - cRank, 2);
uint256 EAA = (1_000+ eeaRate);
uint256 reward = getGrossReward(rankDelta, amplifier, term, EAA);
return (reward * (100- penalty)) /100;
}
/**
* @dev cleans up User Mint storage (gets some Gas credit;))
*/function_cleanUpUserMint() private{
delete userMints[_msgSender()];
activeMinters--;
}
/**
* @dev calculates XEN Stake Reward
*/function_calculateStakeReward(uint256 amount,
uint256 term,
uint256 maturityTs,
uint256 apy
) privateviewreturns (uint256) {
if (block.timestamp> maturityTs) {
uint256 rate = (apy * term *1_000_000) / DAYS_IN_YEAR;
return (amount * rate) /100_000_000;
}
return0;
}
/**
* @dev calculates Reward Amplifier
*/function_calculateRewardAmplifier() privateviewreturns (uint256) {
uint256 amplifierDecrease = (block.timestamp- genesisTs) / SECONDS_IN_DAY;
if (amplifierDecrease < REWARD_AMPLIFIER_START) {
return Math.max(REWARD_AMPLIFIER_START - amplifierDecrease, REWARD_AMPLIFIER_END);
} else {
return REWARD_AMPLIFIER_END;
}
}
/**
* @dev calculates Early Adopter Amplifier Rate (in 1/000ths)
* actual EAA is (1_000 + EAAR) / 1_000
*/function_calculateEAARate() privateviewreturns (uint256) {
uint256 decrease = (EAA_PM_STEP * globalRank) / EAA_RANK_STEP;
if (decrease > EAA_PM_START) return0;
return EAA_PM_START - decrease;
}
/**
* @dev calculates APY (in %)
*/function_calculateAPY() privateviewreturns (uint256) {
uint256 decrease = (block.timestamp- genesisTs) / (SECONDS_IN_DAY * XEN_APY_DAYS_STEP);
if (XEN_APY_START - XEN_APY_END < decrease) return XEN_APY_END;
return XEN_APY_START - decrease;
}
/**
* @dev creates User Stake
*/function_createStake(uint256 amount, uint256 term) private{
userStakes[_msgSender()] = StakeInfo({
term: term,
maturityTs: block.timestamp+ term * SECONDS_IN_DAY,
amount: amount,
apy: _calculateAPY()
});
activeStakes++;
totalXenStaked += amount;
}
// PUBLIC CONVENIENCE GETTERS/**
* @dev calculates gross Mint Reward
*/functiongetGrossReward(uint256 rankDelta,
uint256 amplifier,
uint256 term,
uint256 eaa
) publicpurereturns (uint256) {
int128 log128 = rankDelta.fromUInt().log_2();
int128 reward128 = log128.mul(amplifier.fromUInt()).mul(term.fromUInt()).mul(eaa.fromUInt());
return reward128.div(uint256(1_000).fromUInt()).toUInt();
}
/**
* @dev returns User Mint object associated with User account address
*/functiongetUserMint() externalviewreturns (MintInfo memory) {
return userMints[_msgSender()];
}
/**
* @dev returns XEN Stake object associated with User account address
*/functiongetUserStake() externalviewreturns (StakeInfo memory) {
return userStakes[_msgSender()];
}
/**
* @dev returns current AMP
*/functiongetCurrentAMP() externalviewreturns (uint256) {
return _calculateRewardAmplifier();
}
/**
* @dev returns current EAA Rate
*/functiongetCurrentEAAR() externalviewreturns (uint256) {
return _calculateEAARate();
}
/**
* @dev returns current APY
*/functiongetCurrentAPY() externalviewreturns (uint256) {
return _calculateAPY();
}
/**
* @dev returns current MaxTerm
*/functiongetCurrentMaxTerm() externalviewreturns (uint256) {
return _calculateMaxTerm();
}
// PUBLIC STATE-CHANGING METHODS/**
* @dev accepts User cRank claim provided all checks pass (incl. no current claim exists)
*/functionclaimRank(uint256 term) external{
uint256 termSec = term * SECONDS_IN_DAY;
require(termSec > MIN_TERM, "CRank: Term less than min");
require(termSec < _calculateMaxTerm() +1, "CRank: Term more than current max term");
require(userMints[_msgSender()].rank ==0, "CRank: Mint already in progress");
// create and store new MintInfo
MintInfo memory mintInfo = MintInfo({
user: _msgSender(),
term: term,
maturityTs: block.timestamp+ termSec,
rank: globalRank,
amplifier: _calculateRewardAmplifier(),
eaaRate: _calculateEAARate()
});
userMints[_msgSender()] = mintInfo;
activeMinters++;
emit RankClaimed(_msgSender(), term, globalRank++);
}
/**
* @dev ends minting upon maturity (and within permitted Withdrawal Time Window), gets minted XEN
*/functionclaimMintReward() external{
MintInfo memory mintInfo = userMints[_msgSender()];
require(mintInfo.rank >0, "CRank: No mint exists");
require(block.timestamp> mintInfo.maturityTs, "CRank: Mint maturity not reached");
// calculate reward and mint tokensuint256 rewardAmount = _calculateMintReward(
mintInfo.rank,
mintInfo.term,
mintInfo.maturityTs,
mintInfo.amplifier,
mintInfo.eaaRate
) *1ether;
_mint(_msgSender(), rewardAmount);
_cleanUpUserMint();
emit MintClaimed(_msgSender(), rewardAmount);
}
/**
* @dev ends minting upon maturity (and within permitted Withdrawal time Window)
* mints XEN coins and splits them between User and designated other address
*/functionclaimMintRewardAndShare(address other, uint256 pct) external{
MintInfo memory mintInfo = userMints[_msgSender()];
require(other !=address(0), "CRank: Cannot share with zero address");
require(pct >0, "CRank: Cannot share zero percent");
require(pct <101, "CRank: Cannot share 100+ percent");
require(mintInfo.rank >0, "CRank: No mint exists");
require(block.timestamp> mintInfo.maturityTs, "CRank: Mint maturity not reached");
// calculate rewarduint256 rewardAmount = _calculateMintReward(
mintInfo.rank,
mintInfo.term,
mintInfo.maturityTs,
mintInfo.amplifier,
mintInfo.eaaRate
) *1ether;
uint256 sharedReward = (rewardAmount * pct) /100;
uint256 ownReward = rewardAmount - sharedReward;
// mint reward tokens
_mint(_msgSender(), ownReward);
_mint(other, sharedReward);
_cleanUpUserMint();
emit MintClaimed(_msgSender(), rewardAmount);
}
/**
* @dev ends minting upon maturity (and within permitted Withdrawal time Window)
* mints XEN coins and stakes 'pct' of it for 'term'
*/functionclaimMintRewardAndStake(uint256 pct, uint256 term) external{
MintInfo memory mintInfo = userMints[_msgSender()];
// require(pct > 0, "CRank: Cannot share zero percent");require(pct <101, "CRank: Cannot share >100 percent");
require(mintInfo.rank >0, "CRank: No mint exists");
require(block.timestamp> mintInfo.maturityTs, "CRank: Mint maturity not reached");
// calculate rewarduint256 rewardAmount = _calculateMintReward(
mintInfo.rank,
mintInfo.term,
mintInfo.maturityTs,
mintInfo.amplifier,
mintInfo.eaaRate
) *1ether;
uint256 stakedReward = (rewardAmount * pct) /100;
uint256 ownReward = rewardAmount - stakedReward;
// mint reward tokens part
_mint(_msgSender(), ownReward);
_cleanUpUserMint();
emit MintClaimed(_msgSender(), rewardAmount);
// nothing to burn since we haven't minted this part yet// stake extra tokens partrequire(stakedReward > XEN_MIN_STAKE, "XEN: Below min stake");
require(term * SECONDS_IN_DAY > MIN_TERM, "XEN: Below min stake term");
require(term * SECONDS_IN_DAY < MAX_TERM_END +1, "XEN: Above max stake term");
require(userStakes[_msgSender()].amount ==0, "XEN: stake exists");
_createStake(stakedReward, term);
emit Staked(_msgSender(), stakedReward, term);
}
/**
* @dev initiates XEN Stake in amount for a term (days)
*/functionstake(uint256 amount, uint256 term) external{
require(balanceOf(_msgSender()) >= amount, "XEN: not enough balance");
require(amount > XEN_MIN_STAKE, "XEN: Below min stake");
require(term * SECONDS_IN_DAY > MIN_TERM, "XEN: Below min stake term");
require(term * SECONDS_IN_DAY < MAX_TERM_END +1, "XEN: Above max stake term");
require(userStakes[_msgSender()].amount ==0, "XEN: stake exists");
// burn staked XEN
_burn(_msgSender(), amount);
// create XEN Stake
_createStake(amount, term);
emit Staked(_msgSender(), amount, term);
}
/**
* @dev ends XEN Stake and gets reward if the Stake is mature
*/functionwithdraw() external{
StakeInfo memory userStake = userStakes[_msgSender()];
require(userStake.amount >0, "XEN: no stake exists");
uint256 xenReward = _calculateStakeReward(
userStake.amount,
userStake.term,
userStake.maturityTs,
userStake.apy
);
activeStakes--;
totalXenStaked -= userStake.amount;
// mint staked XEN (+ reward)
_mint(_msgSender(), userStake.amount + xenReward);
emit Withdrawn(_msgSender(), userStake.amount, xenReward);
delete userStakes[_msgSender()];
}
/**
* @dev burns XEN tokens and creates Proof-Of-Burn record to be used by connected DeFi services
*/functionburn(address user, uint256 amount) public{
require(amount > XEN_MIN_BURN, "Burn: Below min limit");
require(
IERC165(_msgSender()).supportsInterface(type(IBurnRedeemable).interfaceId),
"Burn: not a supported contract"
);
_spendAllowance(user, _msgSender(), amount);
_burn(user, amount);
userBurns[user] += amount;
IBurnRedeemable(_msgSender()).onTokenBurned(user, amount);
}
}