// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Address.sol)
pragma solidity ^0.8.20;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev The ETH balance of the account is not enough to perform the operation.
*/
error AddressInsufficientBalance(address account);
/**
* @dev There's no code at `target` (it is not a contract).
*/
error AddressEmptyCode(address target);
/**
* @dev A call to an address target failed. The target may have reverted.
*/
error FailedInnerCall();
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://consensys.net/diligence/blog/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.8.20/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
if (address(this).balance < amount) {
revert AddressInsufficientBalance(address(this));
}
(bool success, ) = recipient.call{value: amount}("");
if (!success) {
revert FailedInnerCall();
}
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason or custom error, it is bubbled
* up by this function (like regular Solidity function calls). However, if
* the call reverted with no returned reason, this function reverts with a
* {FailedInnerCall} error.
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*/
function functionCallWithValue(address target, bytes memory data, uint256 value) internal returns (bytes memory) {
if (address(this).balance < value) {
revert AddressInsufficientBalance(address(this));
}
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and reverts if the target
* was not a contract or bubbling up the revert reason (falling back to {FailedInnerCall}) in case of an
* unsuccessful call.
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata
) internal view returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
// only check if target is a contract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
if (returndata.length == 0 && target.code.length == 0) {
revert AddressEmptyCode(target);
}
return returndata;
}
}
/**
* @dev Tool to verify that a low level call was successful, and reverts if it wasn't, either by bubbling the
* revert reason or with a default {FailedInnerCall} error.
*/
function verifyCallResult(bool success, bytes memory returndata) internal pure returns (bytes memory) {
if (!success) {
_revert(returndata);
} else {
return returndata;
}
}
/**
* @dev Reverts with returndata if present. Otherwise reverts with {FailedInnerCall}.
*/
function _revert(bytes memory returndata) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert FailedInnerCall();
}
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;
/* Common */
uint256 constant BASIS = 10_000;
uint256 constant SECONDS_IN_DAY = 86400;
uint256 constant SCALING_FACTOR_1e3 = 1e3;
uint256 constant SCALING_FACTOR_1e6 = 1e6;
uint256 constant SCALING_FACTOR_1e7 = 1e7;
uint256 constant SCALING_FACTOR_1e11 = 1e11;
uint256 constant SCALING_FACTOR_1e18 = 1e18;
/* TitanX staking */
uint256 constant TITANX_MAX_STAKE_PER_WALLET = 1000;
uint256 constant TITANX_MIN_STAKE_LENGTH = 28;
uint256 constant TITANX_MAX_STAKE_LENGTH = 3500;
/* TitanX Stake Longer Pays Better bonus */
uint256 constant TITANX_LPB_MAX_DAYS = 2888;
uint256 constant TITANX_LPB_PER_PERCENT = 825;
uint256 constant TITANX_BPB_MAX_TITAN = 100 * 1e9 * SCALING_FACTOR_1e18; //100 billion
uint256 constant TITANX_BPB_PER_PERCENT = 1_250_000_000_000 *
SCALING_FACTOR_1e18;
/* Addresses */
address constant TITANX_ADDRESS = 0xF19308F923582A6f7c465e5CE7a9Dc1BEC6665B1;
address constant WETH9_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address constant UNI_SWAP_ROUTER = 0xE592427A0AEce92De3Edee1F18E0157C05861564;
address constant UNI_FACTORY = 0x1F98431c8aD98523631AE4a59f267346ea31F984;
address constant UNI_NONFUNGIBLEPOSITIONMANAGER = 0xC36442b4a4522E871399CD717aBDD847Ab11FE88;
/* Uniswap Liquidity Pools (DragonX, TitanX) */
uint24 constant FEE_TIER = 10000;
int24 constant MIN_TICK = -887200;
int24 constant MAX_TICK = 887200;
uint160 constant INITIAL_SQRT_PRICE_TITANX_DRAGONX = 79228162514264337593543950336; // 1:1
/* DragonX Constants */
uint256 constant INCENTIVE_FEE = 300;
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)
pragma solidity ^0.8.20;
/**
* @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.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/Create2.sol)
pragma solidity ^0.8.20;
/**
* @dev Helper to make usage of the `CREATE2` EVM opcode easier and safer.
* `CREATE2` can be used to compute in advance the address where a smart
* contract will be deployed, which allows for interesting new mechanisms known
* as 'counterfactual interactions'.
*
* See the https://eips.ethereum.org/EIPS/eip-1014#motivation[EIP] for more
* information.
*/
library Create2 {
/**
* @dev Not enough balance for performing a CREATE2 deploy.
*/
error Create2InsufficientBalance(uint256 balance, uint256 needed);
/**
* @dev There's no code to deploy.
*/
error Create2EmptyBytecode();
/**
* @dev The deployment failed.
*/
error Create2FailedDeployment();
/**
* @dev Deploys a contract using `CREATE2`. The address where the contract
* will be deployed can be known in advance via {computeAddress}.
*
* The bytecode for a contract can be obtained from Solidity with
* `type(contractName).creationCode`.
*
* Requirements:
*
* - `bytecode` must not be empty.
* - `salt` must have not been used for `bytecode` already.
* - the factory must have a balance of at least `amount`.
* - if `amount` is non-zero, `bytecode` must have a `payable` constructor.
*/
function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) internal returns (address addr) {
if (address(this).balance < amount) {
revert Create2InsufficientBalance(address(this).balance, amount);
}
if (bytecode.length == 0) {
revert Create2EmptyBytecode();
}
/// @solidity memory-safe-assembly
assembly {
addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt)
}
if (addr == address(0)) {
revert Create2FailedDeployment();
}
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy}. Any change in the
* `bytecodeHash` or `salt` will result in a new destination address.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash) internal view returns (address) {
return computeAddress(salt, bytecodeHash, address(this));
}
/**
* @dev Returns the address where a contract will be stored if deployed via {deploy} from a contract located at
* `deployer`. If `deployer` is this contract's address, returns the same value as {computeAddress}.
*/
function computeAddress(bytes32 salt, bytes32 bytecodeHash, address deployer) internal pure returns (address addr) {
/// @solidity memory-safe-assembly
assembly {
let ptr := mload(0x40) // Get free memory pointer
// | | ↓ ptr ... ↓ ptr + 0x0B (start) ... ↓ ptr + 0x20 ... ↓ ptr + 0x40 ... |
// |-------------------|---------------------------------------------------------------------------|
// | bytecodeHash | CCCCCCCCCCCCC...CC |
// | salt | BBBBBBBBBBBBB...BB |
// | deployer | 000000...0000AAAAAAAAAAAAAAAAAAA...AA |
// | 0xFF | FF |
// |-------------------|---------------------------------------------------------------------------|
// | memory | 000000...00FFAAAAAAAAAAAAAAAAAAA...AABBBBBBBBBBBBB...BBCCCCCCCCCCCCC...CC |
// | keccak(start, 85) | ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑ |
mstore(add(ptr, 0x40), bytecodeHash)
mstore(add(ptr, 0x20), salt)
mstore(ptr, deployer) // Right-aligned with 12 preceding garbage bytes
let start := add(ptr, 0x0b) // The hashed data starts at the final garbage byte which we will set to 0xff
mstore8(start, 0xff)
addr := keccak256(start, 85)
}
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;
// OpenZeppelin
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
// Library
import "./interfaces/ITitanX.sol";
import "../DragonX.sol";
import "./Constants.sol";
/**
* @title A contract managed and deployed by DragonX to initialise the maximum amount of stakes per address
* @author The DragonX devs
* @notice This contract is instantiated by DragonX and will not be deployed as a separate entity
*/
contract DragonStake is Ownable {
using SafeERC20 for IERC20;
using SafeERC20 for ITitanX;
// -----------------------------------------
// Type declarations
// -----------------------------------------
// -----------------------------------------
// State variables
// -----------------------------------------
uint256 public openedStakes;
// -----------------------------------------
// Errors
// -----------------------------------------
/**
* @dev Error emitted when a user tries to end a stake but is not mature yet.
*/
error StakeNotMature();
// -----------------------------------------
// Events
// -----------------------------------------
// -----------------------------------------
// Modifiers
// -----------------------------------------
// -----------------------------------------
// Constructor
// -----------------------------------------
constructor() Ownable(msg.sender) {}
// -----------------------------------------
// Receive function
// -----------------------------------------
/**
* @dev Receive function to handle plain Ether transfers.
* Reverts if the sender is not the allowed address.
*/
receive() external payable {
require(msg.sender == TITANX_ADDRESS, "Sender not authorized");
}
// -----------------------------------------
// Fallback function
// -----------------------------------------
/**
* @dev Fallback function to handle non-function calls or Ether transfers if receive() doesn't exist.
* Reverts if the sender is not the allowed address.
*/
fallback() external payable {
revert("Fallback triggered");
}
// -----------------------------------------
// External functions
// -----------------------------------------
/**
* TitanX Staking Function
* @notice Stakes all available TitanX tokens held by this contract.
* @dev Initializes the TitanX contract, calculates the stakable balance, and opens a new stake.
* This function can only be called by the contract owner.
*/
function stake() external onlyOwner {
// Initialize TitanX contract reference
ITitanX titanX = ITitanX(TITANX_ADDRESS);
// Fetch the current balance of TitanX tokens in this contract
uint256 amountToStake = titanX.balanceOf(address(this));
// Initiate staking of the fetched amount for the maximum defined stake length
titanX.startStake(amountToStake, TITANX_MAX_STAKE_LENGTH);
// Increment the count of active stakes
openedStakes += 1;
}
/**
* Claim ETH Rewards from TitanX Staking
* @notice Allows the contract owner to claim accumulated ETH rewards from TitanX staking.
* @dev Retrieves the total claimable ETH amount and, if any, claims it and sends it to the owner's address.
* This function can only be called by the contract owner.
* @return claimable The total amount of ETH claimed.
*/
function claim() external onlyOwner returns (uint256 claimable) {
// Initialize TitanX contract reference
ITitanX titanX = ITitanX(TITANX_ADDRESS);
// Determine the total amount of ETH that can be claimed by this contract
claimable = titanX.getUserETHClaimableTotal(address(this));
// Proceed with claiming if there is any claimable ETH
if (claimable > 0) {
// Claim the available ETH from TitanX
titanX.claimUserAvailableETHPayouts();
// Transfer the claimed ETH to the contract owner
Address.sendValue(payable(owner()), claimable);
}
}
/**
* @dev Ends a stake after it has reached its maturity.
*
* This function interacts with the ITitanX contract to handle stake operations.
* It requires the stake ID (sId) to be valid and within the range of opened stakes.
* If the current block timestamp is greater than or equal to the stake's maturity timestamp,
* the function ends the stake and transfers the unstaked TitanX tokens to the DragonX contract.
* If the stake has not yet matured, the function will revert.
*
* @param sId The ID of the stake to be ended.
* @notice The function is callable externally and interacts with ITitanX and IERC20 contracts.
* @notice It is required that the stake ID is valid and the stake is matured.
* @notice The function will revert if the stake is not matured.
*/
function endStakeAfterMaturity(uint256 sId) external {
ITitanX titanX = ITitanX(TITANX_ADDRESS);
require(sId > 0 && sId <= openedStakes, "invalid ID");
UserStakeInfo memory stakeInfo = titanX.getUserStakeInfo(
address(this),
sId
);
// End stake if matured
if (block.timestamp >= stakeInfo.maturityTs) {
// track TitanX balance
uint256 before = titanX.balanceOf(address(this));
// End the stake
titanX.endStake(sId);
// Send total amount unstaked back to DragonX
uint256 unstaked = titanX.balanceOf(address(this)) - before;
// Transfer TitanX to DragonX
IERC20(TITANX_ADDRESS).safeTransfer(owner(), unstaked);
// Update DragonX
DragonX(payable(owner())).stakeEnded(unstaked);
} else {
revert StakeNotMature();
}
}
/**
* Send TitanX Balance to DragonX
*
* @dev This function transfers any TitanX tokens held by this contract to the owner,
* representing the DragonX account. This is a safety mechanism to handle
* rare situations where TitanX tokens are accidentally sent to this contract or are
* left over from operations like calling `TitanX#endStakeForOthers`.
*
* It's important to note that this function could lead to slight discrepancies in
* DragonX's accounting, specifically in the `totalTitanUnstaked` value - there is
* no way to distinguish between TitanX send to this contract by accident or
* users calling `TitanX#endStakeForOthers`.
*
* @notice Use this function to transfer TitanX tokens from the contract to the DragonX
* owner address in case of accidental transfers or calling `TitanX#endStakeForOthers`
*/
function sendTitanX() external {
IERC20 titanX = IERC20(TITANX_ADDRESS);
// transfer
titanX.safeTransfer(owner(), titanX.balanceOf(address(this)));
// update the vault
DragonX(payable(owner())).updateVault();
}
/**
* @dev Calculates the total amount of Ethereum claimable by the contract.
* Calls `getUserETHClaimableTotal` from the TitanX contract to retrieve the total claimable amount.
* @return claimable The total amount of Ethereum claimable by the contract.
*/
function totalEthClaimable() external view returns (uint256 claimable) {
// Initialize TitanX contract reference
ITitanX titanX = ITitanX(TITANX_ADDRESS);
claimable = titanX.getUserETHClaimableTotal(address(this));
}
/**
* @dev Determines whether any of the stakes have reached their maturity date.
* Iterates through all user stakes and checks if the current block timestamp
* is at or past the stake's maturity timestamp.
* @return A boolean indicating whether at least one stake has reached maturity.
*/
function stakeReachedMaturity() external view returns (bool, uint256) {
ITitanX titanX = ITitanX(TITANX_ADDRESS);
UserStake[] memory stakes = titanX.getUserStakes(address(this));
for (uint256 idx; idx < stakes.length; idx++) {
if (block.timestamp > stakes[idx].stakeInfo.maturityTs) {
return (true, stakes[idx].sId);
}
}
return (false, 0);
}
// -----------------------------------------
// Public functions
// -----------------------------------------
// -----------------------------------------
// Internal functions
// -----------------------------------------
// -----------------------------------------
// Private functions
// -----------------------------------------
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;
// OpenZeppelin
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/utils/Create2.sol";
import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
// Library
import "./lib/DragonStake.sol";
import "./lib/Constants.sol";
import "./lib/Types.sol";
import "./lib/interfaces/ITitanX.sol";
/**
* @title The DragonX Contranct
* @author The DragonX devs
*/
contract DragonX is ERC20, Ownable2Step, ReentrancyGuard {
using SafeERC20 for IERC20;
using SafeERC20 for ITitanX;
// -----------------------------------------
// Type declarations
// -----------------------------------------
// -----------------------------------------
// State variables
// -----------------------------------------
/**
* @notice The TitanX buy contract address.
* Set at runtime, this address allows for upgrading the buy contract version.
*/
address public titanBuyAddress;
/**
* @notice The DragonX buy and burn contract address.
* Set at runtime, this allows for upgrading the DragonX buy and burn contract.
*/
address public dragonBuyAndBurnAddress;
/**
* @notice The start time of the mint phase, expressed in UTC seconds.
* Indicates when the minting phase for tokens begins.
*/
uint256 public mintPhaseBegin;
/**
* @notice The end time of the mint phase, expressed in UTC seconds.
* Indicates when the minting phase for tokens ends.
*/
uint256 public mintPhaseEnd;
/**
* @notice mint ratios from launch for 84 days (12 weeks)
*/
uint256 public constant mintRatioWeekOne = BASIS;
uint256 public constant mintRatioWeekTwo = BASIS;
uint256 public constant mintRatioWeekThree = 9500;
uint256 public constant mintRatioWeekFour = 9000;
uint256 public constant mintRatioWeekFive = 8500;
uint256 public constant mintRatioWeekSix = 8000;
uint256 public constant mintRatioWeekSeven = 7500;
uint256 public constant mintRatioWeekEight = 7000;
uint256 public constant mintRatioWeekNine = 6500;
uint256 public constant mintRatioWeekTen = 6000;
uint256 public constant mintRatioWeekEleven = 5500;
uint256 public constant mintRatioWeekTwelve = 5000;
/**
* @notice The time when it's possible to open a new TitanX stake after the cooldown.
* This cooldown period controls the frequency of new stakes being initiated.
*/
uint256 public nextStakeTs;
/**
* @notice The number of DragonStake contracts that have been deployed.
* Tracks how many DragonStake contracts exist within the system.
*/
uint256 public numDragonStakeContracts;
/**
* @notice The address of the currently active DragonStake contract instance.
* This contract is used for initiating new TitanX stakes.
*/
address public activeDragonStakeContract;
/**
* @notice A mapping from an ID to a deployed instance of the DragonStake contract.
* The index starts at zero. Use a loop to iterate through instances, e.g., for(uint256 idx = 0; idx < numDragonStakeContracts; idx++).
*/
mapping(uint256 => address) public dragonStakeContracts;
/**
* @notice The amount of TitanX currently held by this contract and not used in stakes.
* Represents the reserve of TitanX tokens that are available but not currently staked.
*/
uint256 public vault;
/**
* @notice The total amount of Titan staked by DragonX
*/
uint256 public totalTitanStaked;
/**
* @notice The total amount of Titan unstaked by DragonX
*/
uint256 public totalTitanUnstaked;
/**
* @notice The total amount of ETH claimed by DragonX
*/
uint256 public totalEthClaimed;
/**
* Indicates that the initial liquidity has been minted
*/
InitialLiquidityMinted public initalLiquidityMinted;
/**
* @dev Mapping of amounts allocated to the genesis address held by this contract.
* - address(0): Represents the ETH allocated.
* - address(TitanX): Represents the TitanX tokens allocated.
* - address(this): Represents the DragonX tokens allocated.
*/
mapping(address => uint256) private _genesisVault;
/**
* @dev Mapping of address to bool indicating if an address is allowed to send ETH
* to DragonX limiting EOA addresses from accidently sending ETH to DragonX
*/
mapping(address => bool) private _receiveEthAllowlist;
/**
* @dev Mapping of address to bool indicating if an address is a DragonStake instance
*/
mapping(address => bool) private _dragonStakeAllowlist;
// -----------------------------------------
// Events
// -----------------------------------------
/**
* @dev Event emitted when a new Dragon stake instance is created.
* @param stakeContractId Unique identifier of the stake contract.
* @param stakeContractAddress Address of the newly created stake contract.
*/
event DragonStakeInstanceCreated(
uint256 indexed stakeContractId,
address indexed stakeContractAddress
);
/**
* @notice Emitted when staking rewards are claimed.
* @param caller The address of the caller who initiated the transaction.
* @param totalClaimed The total amount of ETH claimed.
* @param titanBuy Amount transfered to TitanBuy.
* @param dragonBuyAndBurn Amount transfered to DragonBuyAndBurn
* @param genesis Amount accounted to genesis
* @param incentiveFee Incentive see send to caller
* (this might include the incentice for calling triggerPayouts on TitanX)
*/
event Claimed(
address indexed caller,
uint256 indexed totalClaimed,
uint256 titanBuy,
uint256 dragonBuyAndBurn,
uint256 genesis,
uint256 incentiveFee
);
/**
* @notice Emitted when a new TitanX stake is opened by Dragonx
* @param dragonStakeAddress The DragonStake instance used for this stake
* @param amount The amount staked
*/
event TitanStakeStarted(address indexed dragonStakeAddress, uint256 amount);
/**
* @notice Emitted when TitanX stakes are ended by Dragonx
* @param dragonStakeAddress The DragonStake instance used for this action
* @param amount The amount unstaked
*/
event TitanStakesEnded(address indexed dragonStakeAddress, uint256 amount);
// -----------------------------------------
// Errors
// -----------------------------------------
/**
* @dev Error emitted when a user tries to mint but the minting phase has not started.
* This prevents actions related to minting before the official commencement of the minting period.
*/
error MintingNotYetActive();
/**
* @dev Error when a user tries to mint but the minting phase has ended.
* This ensures minting operations are restricted to the designated minting timeframe.
*/
error MintingPeriodOver();
/**
* @dev Emitted when a user tries to mint but the TitanX allowance for this contract is too low.
* Indicates that the contract does not have enough TitanX tokens allocated to it for the minting operation.
*/
error InsufficientTitanXAllowance();
/**
* @dev Emitted when a user tries to mint without having enough TitanX tokens.
* This ensures that users have a sufficient balance of TitanX tokens to perform the minting operation.
*/
error InsufficientTitanXBalance();
/**
* @dev Error emitted when the stake function is currently in the cooldown period and cannot be called.
* This enforces a waiting period before the stake function can be executed again.
*/
error CooldownPeriodActive();
/**
* @dev Emitted when no additional stakes can be opened.
* This is triggered when the maximum limit of open stakes is reached.
*/
error NoAdditionalStakesAllowed();
/**
* @dev Error emitted when there is no ETH claimable by the function caller.
* This ensures that the claim operation is only performed when there is ETH available to be claimed.
*/
error NoEthClaimable();
/**
* @dev Error emitted when there are no tokens available to stake.
* This ensures that the staking operation is only executed when there are tokens to be staked.
*/
error NoTokensToStake();
/**
* @dev Error emitted when there is no need for creating a new Dragon stake instance.
* This occurs when attempting to create a redundant Dragon stake instance.
*/
error NoNeedForNewDragonStakeInstance();
/**
* @dev Error emitted when an invalid address is given to a function.
* This occurs when the genesis address manages an address and passes address(0) by accident.
*/
error InvalidAddress();
/**
* @dev Error emitted when a user attempts to mint but the initial liquidity has net yet been mined
*/
error LiquidityNotMintedYet();
/**
* @dev Thrown when the function caller is not authorized or expected.
*/
error InvalidCaller();
// -----------------------------------------
// Modifiers
// -----------------------------------------
/**
* @dev Modifier to restrict function access to allowed DragonStake contracts.
*
* This modifier ensures that the function can only be called by addresses that are
* present in the `_dragonStakeAllowlist`. If the calling address is not on the allowlist,
* the transaction will revert with the message "not allowed".
* @notice Use this modifier to restrict function access to specific addresses only.
*/
modifier onlyDragonStake() {
require(_dragonStakeAllowlist[_msgSender()], "not allowed");
_;
}
// -----------------------------------------
// Constructor
// -----------------------------------------
/**
* @notice Constructor for the DragonX ERC20 Token Contract.
* @dev Initializes the contract, sets up the minting phase, and deploys the first DragonStake instance.
* - Inherits from ERC20 and sets token name to "DragonX" and symbol to "DRAGONX".
* - Calculates and sets the start and end times for the minting phase based on current time.
* - Sets the time for the next restake opportunity.
* - Deploys the first DragonStake contract instance.
* - Transfers ownership to contract deployer.
* - Set the initial TitanBuy and DragonBuyAndBurn contract addresses.
* @param titanBuyAddress_ The address of the TitanBuy contract instance.
* @param dragonBuyAndBurnAdddress_ The address of the DragonBuyAndBurn contract instance.
*/
constructor(
address titanBuyAddress_,
address dragonBuyAndBurnAdddress_
) ERC20("DragonX", "DRAGONX") Ownable(msg.sender) {
if (titanBuyAddress_ == address(0)) {
revert InvalidAddress();
}
if (dragonBuyAndBurnAdddress_ == address(0)) {
revert InvalidAddress();
}
// Deploy stake contract instance setting DragonX as its owner
_deployDragonStakeInstance();
// Set contract addresses
titanBuyAddress = titanBuyAddress_;
dragonBuyAndBurnAddress = dragonBuyAndBurnAdddress_;
// set other states
initalLiquidityMinted = InitialLiquidityMinted.No;
// Allow TitanX to send ETH to DragonX (incentive fee)
_receiveEthAllowlist[TITANX_ADDRESS] = true;
}
// -----------------------------------------
// Receive function
// -----------------------------------------
/**
* @dev Receive function to handle plain Ether transfers.
* Reverts if the sender is not one of the DragonStake contracts.
*/
receive() external payable {
require(_receiveEthAllowlist[msg.sender], "Sender not authorized");
}
// -----------------------------------------
// Fallback function
// -----------------------------------------
/**
* @dev Fallback function to handle non-function calls or Ether transfers if receive() doesn't exist.
* Always revert
*/
fallback() external {
revert("Fallback triggered");
}
// -----------------------------------------
// External functions
// -----------------------------------------
/**
* This function enables the minting of DragonX tokens in exchange for TitanX.
* Users can transfer TitanX to the DragonX contract to mint an equivalent amount of DragonX tokens.
* The minting process is available only during a specified time frame.
* When minting, 8% of the total minted DragonX supply and 8% of the TitanX used for minting
* are allocated to the genesis address. The remaining TitanX is retained within the contract.
* Minting starts once the initial liquidity has been minted (indicating all other contracts)
* have been deployed and initialized successfully by the genesis address.
* @param amount The amount of DragonX tokens to be minted.
*/
function mint(uint256 amount) external {
// Cache state variables
uint256 mintPhaseBegin_ = mintPhaseBegin;
// To avoid being frontrun, minting creating DragonX tokens will only
// be able once the inital liqudiity ahs been created
if (initalLiquidityMinted != InitialLiquidityMinted.Yes) {
revert LiquidityNotMintedYet();
}
// Check if the minting phase is currently active
if (block.timestamp < mintPhaseBegin_) {
revert MintingNotYetActive();
}
if (block.timestamp > mintPhaseEnd) {
revert MintingPeriodOver();
}
ITitanX titanX = ITitanX(TITANX_ADDRESS);
// Ensure the user has sufficient TitanX and has granted enough allowance
if (titanX.allowance(_msgSender(), address(this)) < amount) {
revert InsufficientTitanXAllowance();
}
if (titanX.balanceOf(_msgSender()) < amount) {
revert InsufficientTitanXBalance();
}
// Transfer TitanX from the user to this contract
titanX.safeTransferFrom(_msgSender(), address(this), amount);
uint256 ratio;
if (block.timestamp < mintPhaseBegin_ + 7 days) {
// week 1
ratio = mintRatioWeekOne;
} else if (block.timestamp < mintPhaseBegin_ + 14 days) {
// week 2
ratio = mintRatioWeekTwo;
} else if (block.timestamp < mintPhaseBegin_ + 21 days) {
// week 3
ratio = mintRatioWeekThree;
} else if (block.timestamp < mintPhaseBegin_ + 28 days) {
// week 4
ratio = mintRatioWeekFour;
} else if (block.timestamp < mintPhaseBegin_ + 35 days) {
// week 5
ratio = mintRatioWeekFive;
} else if (block.timestamp < mintPhaseBegin_ + 42 days) {
// week 6
ratio = mintRatioWeekSix;
} else if (block.timestamp < mintPhaseBegin_ + 49 days) {
// week 7
ratio = mintRatioWeekSeven;
} else if (block.timestamp < mintPhaseBegin_ + 56 days) {
// week 8
ratio = mintRatioWeekEight;
} else if (block.timestamp < mintPhaseBegin_ + 63 days) {
// weeek 9
ratio = mintRatioWeekNine;
} else if (block.timestamp < mintPhaseBegin_ + 70 days) {
// week 10
ratio = mintRatioWeekTen;
} else if (block.timestamp < mintPhaseBegin_ + 77 days) {
// week 11
ratio = mintRatioWeekEleven;
} else {
// week 12
ratio = mintRatioWeekTwelve;
}
// calculate the amount to mint
uint256 mintAmount = (amount * ratio) / BASIS;
// Mint an equivalent amount of DragonX tokens
_mint(_msgSender(), mintAmount);
// Calculate and mint the genesis 8% share (of total supply minted)
uint256 dragonGenesisShare = (mintAmount * 800) / BASIS;
_mint(address(this), dragonGenesisShare);
// Allocate 8% of DragonX to the genesis vault
_genesisVault[address(this)] += dragonGenesisShare;
// Allocate 8% of total TitanX send to DragonX to genesis vault
uint256 titanGenesisShare = (amount * 800) / BASIS;
_genesisVault[address(titanX)] += titanGenesisShare;
// Retain the remaining TitanX within the contract's vault
vault += amount - titanGenesisShare;
}
/**
* This function allows users to open a new TitanX stake through the DragonX contract.
* Each stake runs for the maximum duration, and upon completion, the TitanX is burned.
*
* A stake can be opened when either of the following conditions is met:
* 1. The vault has sufficient TitanX tokens to achieve the maximum 'bigger pays better' bonus.
* 2. If the vault doesn't have enough tokens, the function can be invoked after a cooldown
* period of 1 week. This delay allows the accumulation of sufficient TitanX to gain
* the 'bigger pays better' bonus.
*/
function stake() external {
DragonStake dragonStake = DragonStake(
payable(activeDragonStakeContract)
);
if (dragonStake.openedStakes() >= TITANX_MAX_STAKE_PER_WALLET) {
revert NoAdditionalStakesAllowed();
}
updateVault();
// Cache state variables
uint256 vault_ = vault;
if (vault_ == 0) {
revert NoTokensToStake();
}
if (vault_ >= TITANX_BPB_MAX_TITAN) {
// Start a stake using the currently active DragonStake instance
_startStake();
// Schedule the next possible stake after a 7-day cooldown period
nextStakeTs = block.timestamp + 7 days;
} else {
// If the vault lacks sufficient TitanX, a stake can be opened only
// after a cooldown period of 7 days to allow for token accumulation.
if (block.timestamp < nextStakeTs) {
revert CooldownPeriodActive();
}
// Start a new stake using the currently active DragonStake instance
_startStake();
// Schedule the next possible stake after a 7-day cooldown period.
nextStakeTs = block.timestamp + 7 days;
}
}
/**
* Claim Function for ETH Rewards
* This function claims ETH rewards based on TitanX stakes and allocates them according to predefined shares.
* @dev The function performs the following operations:
* 1. Retrieves the claimable ETH amount from TitanX stakes.
* 2. Validates if there is any ETH to claim, and reverts if none is available.
* 3. Claims the available ETH payouts.
* 4. Calculates and distributes the ETH according to predefined shares:
* - 8% is allocated as a genesis share.
* - 3% is sent as a tip to the caller of the function.
* - 44.5% is used for buying and burning DragonX tokens.
* - The remaining 44.5% is used for buying and burning TitanX tokens.
* 5. Updates the respective vaults with their allocated shares.
* 6. Sends the tip to the caller of the function.
*/
function claim() external nonReentrant returns (uint256 claimedAmount) {
//prevent contract accounts (bots) from calling this function
if (msg.sender != tx.origin) {
revert InvalidCaller();
}
// Trigger payouts on TitanX
// This potentially sends an incentive fee to DragonX
// The incentive fee is transparently forwarded to the caller
uint256 ethBalanceBefore = address(this).balance;
ITitanX(TITANX_ADDRESS).triggerPayouts();
uint256 triggerPayoutsIncentiveFee = address(this).balance -
ethBalanceBefore;
// Retrieve the total claimable ETH amount.
for (uint256 idx; idx < numDragonStakeContracts; idx++) {
DragonStake dragonStake = DragonStake(
payable(dragonStakeContracts[idx])
);
claimedAmount += dragonStake.claim();
}
// Check if there is any claimable ETH, revert if none.
if (claimedAmount == 0) {
revert NoEthClaimable();
}
// Calculate the genesis share (8%).
uint256 genesisShare = (claimedAmount * 800) / BASIS;
// Calculate the tip for the caller (3%).
uint256 incentiveFee = (claimedAmount * INCENTIVE_FEE) / BASIS;
// Calculate the Buy and Burn share for DragonX (44.5%).
uint256 buyAndBurnDragonX = (claimedAmount * 4450) / BASIS;
// Calculate the Buy and Burn share for TitanX (remainder, ~44.5%).
uint256 buyTitanX = claimedAmount -
genesisShare -
buyAndBurnDragonX -
incentiveFee;
// Update the genesis vault with the genesis share.
_genesisVault[address(0)] += genesisShare;
// Send to the Buy and Burn contract for DragonX.
Address.sendValue(payable(dragonBuyAndBurnAddress), buyAndBurnDragonX);
// Send to the buy contract for TitanX.
Address.sendValue(payable(titanBuyAddress), buyTitanX);
// Send the tip to the function caller.
address sender = _msgSender();
Address.sendValue(
payable(sender),
incentiveFee + triggerPayoutsIncentiveFee
);
// update state
totalEthClaimed += claimedAmount;
// Emit event
emit Claimed(
sender,
claimedAmount,
buyTitanX,
buyAndBurnDragonX,
genesisShare,
incentiveFee + triggerPayoutsIncentiveFee
);
}
/**
* @notice Factory function to deploy a new DragonStake contract instance.
* @dev This function deploys a new DragonStake instance if the number of open stakes in the current
* active instance exceeds the maximum allowed per wallet.
* It reverts with NoNeedForNewDragonStakeInstance if the condition is not met.
* Only callable externally.
*/
function deployNewDragonStakeInstance() external {
DragonStake dragonStake = DragonStake(
payable(activeDragonStakeContract)
);
// Check if the maximum number of stakes per wallet has been reached
if (dragonStake.openedStakes() < TITANX_MAX_STAKE_PER_WALLET) {
revert NoNeedForNewDragonStakeInstance();
}
// Deploy a new DragonStake instance
_deployDragonStakeInstance();
}
/**
* @notice Mints the initial liquidity for the DragonX token.
* @dev This function mints a specified amount of tokens and sets up the minting phases.
* It can only be called once by the authorized address.
* @param amount The amount of DragonX tokens to be minted for initial liquidity.
*/
function mintInitialLiquidity(uint256 amount) external {
// Cache state variables
address dragonBuyAndBurnAddress_ = dragonBuyAndBurnAddress;
// Verify that the caller is authorized to mint initial liquidity
require(msg.sender == dragonBuyAndBurnAddress_, "not authorized");
// Ensure that initial liquidity hasn't been minted before
require(
initalLiquidityMinted == InitialLiquidityMinted.No,
"already minted"
);
// Mint the specified amount of DragonX tokens to the authorized address
_mint(dragonBuyAndBurnAddress_, amount);
// Update the state to reflect that initial liquidity has been minted
initalLiquidityMinted = InitialLiquidityMinted.Yes;
// Set up the minting phase timings
uint256 currentTimestamp = block.timestamp;
uint256 secondsUntilMidnight = 86400 - (currentTimestamp % 86400);
// The mint phase is open for 84 days (12 weeks) and begins at midnight
// once contracts are fully set up
uint256 mintPhaseBegin_ = currentTimestamp + secondsUntilMidnight;
// Update storage
mintPhaseBegin = mintPhaseBegin_;
// Set mint phase end
mintPhaseEnd = mintPhaseBegin_ + 84 days;
// Allow the first stake after 7 days of mint-phase begin
nextStakeTs = mintPhaseBegin_ + 7 days;
}
/**
* Token Burn Function
* @notice Allows a token holder to burn all of their tokens.
* @dev Burns the entire token balance of the caller. This function calls `_burn`
* with the caller's address and their full token balance.
* This function can be called by any token holder wishing to burn their tokens.
* Tokens burned are permanently removed from the circulation.
* @custom:warning WARNING: This function will irreversibly burn all tokens in the caller's account.
* Ensure you understand the consequences before calling.
*/
function burn() external {
address sender = _msgSender();
_burn(sender, balanceOf(sender));
}
/**
* @notice Calculates the total number of stakes opened across all DragonStake contract instances.
* @dev This function iterates over all the DragonStake contract instances recorded in the contract:
* 1. For each DragonStake contract, it gets a reference to the contract instance.
* 2. It then calls the `openedStakes` function on each instance to get the number of opened stakes.
* 3. These values are summed up to calculate the total number of stakes opened across all instances.
* @return totalStakes The total number of stakes opened across all DragonStake contract instances.
*/
function totalStakesOpened() external view returns (uint256 totalStakes) {
// Iterate over all DragonStake contract instances
for (uint256 idx; idx < numDragonStakeContracts; idx++) {
// Get a reference to each DragonStake contract
DragonStake dragonStake = DragonStake(
payable(dragonStakeContracts[idx])
);
// Add the stakes opened by this DragonStake instance
totalStakes += dragonStake.openedStakes();
}
}
/**
* @dev Calculate the incentive fee a user will receive for calling the claim function.
* This function computes the fee based on the total amount of Ethereum claimable
* and a predefined incentive fee rate.
*
* @notice Used to determine the fee awarded for claiming Ethereum.
*
* @return fee The calculated incentive fee, represented as a uint256.
* This value is calculated by taking the product of `totalEthClaimable()` and
* `INCENTIVE_FEE`, then dividing by `BASIS` to normalize the fee calculation.
*/
function incentiveFeeForClaim() external view returns (uint256 fee) {
fee = (totalEthClaimable() * INCENTIVE_FEE) / BASIS;
}
/**
* @dev Checks all DragonStake contract instances to determine if any stake has reached maturity.
* Iterates through each DragonStake contract instance and checks for stakes that have reached maturity.
* If a stake has reached maturity in a particular instance, it returns true along with the instance's address and the ID.
* If no stakes have reached maturity in any instance, it returns false and a zero address and zero for the ID.
* @return hasStakesToEnd A boolean indicating if there is at least one stake that has reached maturity.
* @return instanceAddress The address of the DragonStake contract instance that has a stake which reached maturity.
* @return sId The ID of the stake which reached maturity
* Returns zero address if no such instance is found.
* @notice This function is used to identify if and where stakes have reached maturity across multiple contract instances.
*/
function stakeReachedMaturity()
external
view
returns (bool hasStakesToEnd, address instanceAddress, uint256 sId)
{
// Iterate over all DragonStake contract instances
for (uint256 idx; idx < numDragonStakeContracts; idx++) {
address instance = dragonStakeContracts[idx];
// Get a reference to each DragonStake contract
DragonStake dragonStake = DragonStake(payable(instance));
(bool reachedMaturity, uint256 id) = dragonStake
.stakeReachedMaturity();
// Exit if this instance contains a stake that reached maturity
if (reachedMaturity) {
return (true, instance, id);
}
}
return (false, address(0), 0);
}
/**
* @dev Sets the address used for buying and burning DRAGONX tokens.
* @notice This function can only be called by the contract owner.
* @param dragonBuyAndBurn The address to be set for the DRAGONX buy and burn operation.
* If this address is the zero address, the transaction is reverted.
* @custom:throws InvalidAddress if the provided address is the zero address.
*/
function setDragonBuyAndBurnAddress(
address dragonBuyAndBurn
) external onlyOwner {
if (dragonBuyAndBurn == address(0)) {
revert InvalidAddress();
}
dragonBuyAndBurnAddress = dragonBuyAndBurn;
}
/**
* @dev Sets the address used for buying TITANX tokens.
* @notice This function can only be called by the contract owner.
* @param titanBuy The address to be set for the TITANX buy operation.
* If this address is the zero address, the transaction is reverted.
* @custom:throws InvalidAddress if the provided address is the zero address.
*/
function setTitanBuyAddress(address titanBuy) external onlyOwner {
if (titanBuy == address(0)) {
revert InvalidAddress();
}
titanBuyAddress = titanBuy;
}
/**
* @notice Transfers the accumulated balance of a specified asset from the Genesis Vault to the owner.
* @dev This function allows the contract owner to claim assets accumulated in the Genesis Vault. It supports both Ether and ERC20 tokens.
* The function performs the following operations:
* 1. Retrieves the balance of the specified asset from the `_genesisVault`.
* 2. Sets the balance of the asset in the vault to zero, effectively resetting it.
* 3. Checks that the retrieved balance is greater than zero, and reverts if it's not.
* 4. If the asset is Ether (denoted by `asset` being the zero address), it transfers the Ether to the owner using `Address.sendValue`.
* 5. If the asset is an ERC20 token, it transfers the token amount to the owner using `safeTransfer` from the ERC20 token's contract.
* @param asset The address of the asset to be claimed. A zero address indicates Ether, and a non-zero address indicates an ERC20 token.
*/
function claimGenesis(address asset) external onlyOwner {
uint256 balance = _genesisVault[asset];
_genesisVault[asset] = 0;
require(balance > 0, "no balance");
if (asset == address(0)) {
Address.sendValue(payable(owner()), balance);
} else {
IERC20 erc20 = IERC20(asset);
erc20.safeTransfer(owner(), balance);
}
}
/**
* @dev Updates the state when a TitanX stake has ended and the tokens are unstaked.
*
* This function should be called after unstaking TitanX tokens. It updates the vault
* and the total amount of TitanX tokens that have been unstaked. This function can only
* be called by an address that is allowed to end stakes (enforced by the `onlyDragonStake` modifier).
*
* @param amountUnstaked The amount of TitanX tokens that have been unstaked.
* @notice This function is callable externally but restricted to allowed addresses (DragonStake contracts).
* @notice It emits the `TitanStakesEnded` event after updating the total unstaked amount.
*/
function stakeEnded(uint256 amountUnstaked) external onlyDragonStake {
// Update vault (TitanX is transfered to DragonX)
updateVault();
// Update state
totalTitanUnstaked += amountUnstaked;
// Emit event
emit TitanStakesEnded(_msgSender(), amountUnstaked);
}
// -----------------------------------------
// Public functions
// -----------------------------------------
/**
* @notice Updates the vault balance based on the current TITANX token balance.
* @dev This function calculates the vault balance by subtracting the initial
* balance of TITANX tokens stored in `_genesisVault` from the current balance of
* TITANX tokens held by this contract.
* Steps involved in the function:
* 1. Create an instance of the IERC20 interface for the TITANX token.
* 2. Fetch the current TITANX token balance of this contract.
* 3. Subtract the initial TITANX token balance (recorded in `_genesisVault`)
* from the current balance.
* 4. Update the `vault` variable with the resulting value.
* The `vault` variable represents the net amount of TITANX tokens that have
* been accumulated in this contract since its inception (excluding the initial amount).
* This function should be called to reflect the latest state of the vault balance.
*/
function updateVault() public {
IERC20 titanX = IERC20(TITANX_ADDRESS);
uint256 balance = titanX.balanceOf(address(this));
vault = balance - _genesisVault[address(titanX)];
}
/**
* @notice Calculates the total amount of ETH claimable from all DragonStake contract instances.
* @dev Iterates through all deployed DragonStake contract instances and sums up the ETH claimable from each.
* This function is read-only and can be called externally.
* @return claimable The total amount of ETH claimable across all DragonStake contract instances.
*/
function totalEthClaimable() public view returns (uint256 claimable) {
// Iterate over all DragonStake contract instances
for (uint256 idx; idx < numDragonStakeContracts; idx++) {
// Get a reference to each DragonStake contract
DragonStake dragonStake = DragonStake(
payable(dragonStakeContracts[idx])
);
// Add the claimable ETH from each DragonStake to the total
claimable += dragonStake.totalEthClaimable();
}
}
// -----------------------------------------
// Internal functions
// -----------------------------------------
// -----------------------------------------
// Private functions
// -----------------------------------------
/**
* @dev Private function to deploy a DragonStake contract instance.
* It deploys a new DragonStake contract using create2 for deterministic addresses,
* and updates the activeDragonStakeContract and dragonStakeContracts mapping.
* An event DragonStakeInstanceCreated is emitted after successful deployment.
* This function is called by the deployNewDragonStakeInstance function.
*/
function _deployDragonStakeInstance() private {
// Deploy an instance of dragon staking contract
bytes memory bytecode = type(DragonStake).creationCode;
uint256 stakeContractId = numDragonStakeContracts;
// Create a unique salt for deployment
bytes32 salt = keccak256(
abi.encodePacked(address(this), stakeContractId)
);
// Deploy a new DragonStake contract instance
address newDragonStakeContract = Create2.deploy(0, salt, bytecode);
// Set new contract as active
activeDragonStakeContract = newDragonStakeContract;
// Update storage
dragonStakeContracts[stakeContractId] = newDragonStakeContract;
// Allow the DragonStake instance to send ETH to DragonX
_receiveEthAllowlist[newDragonStakeContract] = true;
// For functions limited to DragonStake
_dragonStakeAllowlist[newDragonStakeContract] = true;
// Emit an event to track the creation of a new stake contract
emit DragonStakeInstanceCreated(
stakeContractId,
newDragonStakeContract
);
// Increment the counter for DragonStake contracts
numDragonStakeContracts += 1;
}
/**
* @dev Private function to start a new stake using the currently active DragonStake instance.
* It transfers all TitanX tokens held by this contract to the active DragonStake instance
* and then initiates a new stake with the total amount transferred.
* This function is meant to be called internally by other contract functions.
*/
function _startStake() private {
// Cache state variables
address activeDragonStakeContract_ = activeDragonStakeContract;
// Initialize TitanX contract reference
ITitanX titanX = ITitanX(TITANX_ADDRESS);
DragonStake dragonStake = DragonStake(
payable(activeDragonStakeContract_)
);
uint256 amountToStake = vault;
vault = 0;
// Transfer TitanX tokens to the active DragonStake contract
titanX.safeTransfer(activeDragonStakeContract_, amountToStake);
// Open a new stake with the total amount transferred
dragonStake.stake();
// Update states
totalTitanStaked += amountToStake;
// Emit event
emit TitanStakeStarted(activeDragonStakeContract_, amountToStake);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.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}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* 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.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
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 default value returned by this function, unless
* it's 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}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` 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.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @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 `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` 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.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
_totalSupply += value;
} else {
uint256 fromBalance = _balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
_balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
_totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
_balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` 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.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
* ```
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
_allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed 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.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (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.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens 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.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;
// OpenZeppelin
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
// Enum for stake status
enum StakeStatus {
ACTIVE,
ENDED,
BURNED
}
// Struct for user stake information
struct UserStakeInfo {
uint152 titanAmount;
uint128 shares;
uint16 numOfDays;
uint48 stakeStartTs;
uint48 maturityTs;
StakeStatus status;
}
// Struct for user stake
struct UserStake {
uint256 sId;
uint256 globalStakeId;
UserStakeInfo stakeInfo;
}
// Interface for the contract
interface IStakeInfo {
/**
* @notice Get all stake info of a given user address.
* @param user The address of the user to query stake information for.
* @return An array of UserStake structs containing all stake info for the given address.
*/
function getUserStakes(
address user
) external view returns (UserStake[] memory);
/** @notice get stake info with stake id
* @return stakeInfo stake info
*/
function getUserStakeInfo(
address user,
uint256 id
) external view returns (UserStakeInfo memory);
}
/**
* @title The TitanX interface used by DragonX to manages stakes
* @author The DragonX devs
*/
interface ITitanX is IERC20, IStakeInfo {
/**
* @notice Start a new stake
* @param amount The amount of TitanX tokens to stake
* @param numOfDays The length of the stake in days
*/
function startStake(uint256 amount, uint256 numOfDays) external;
/**
* @notice Claims available ETH payouts for a user based on their shares in various cycles.
* @dev This function calculates the total reward from different cycles and transfers it to the caller.
*/
function claimUserAvailableETHPayouts() external;
/**
* @notice Calculates the total ETH claimable by a user for all cycles.
* @dev This function sums up the rewards from various cycles based on user shares.
* @param user The address of the user for whom to calculate the claimable ETH.
* @return reward The total ETH reward claimable by the user.
*/
function getUserETHClaimableTotal(
address user
) external view returns (uint256 reward);
/**
* @notice Allows anyone to sync dailyUpdate manually.
* @dev Function to be called for manually triggering the daily update process.
* This function is public and can be called by any external entity.
*/
function manualDailyUpdate() external;
/**
* @notice Trigger cycle payouts for days 8, 28, 90, 369, 888, including the burn reward cycle 28.
* Payouts can be triggered on or after the maturity day of each cycle (e.g., Cycle8 on day 8).
*/
function triggerPayouts() external;
/**
* @notice Create a new mint
* @param mintPower The power of the mint, ranging from 1 to 100.
* @param numOfDays The duration of the mint, ranging from 1 to 280 days.
*/
function startMint(uint256 mintPower, uint256 numOfDays) external payable;
/**
* @notice Returns current mint cost
* @return currentMintCost The current cost of minting.
*/
function getCurrentMintCost() external view returns (uint256);
/** @notice end a stake
* @param id stake id
*/
function endStake(uint256 id) external;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable2Step.sol)
pragma solidity ^0.8.20;
import {Ownable} from "./Ownable.sol";
/**
* @dev Contract module which provides access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is specified at deployment time in the constructor for `Ownable`. This
* can later be changed with {transferOwnership} and {acceptOwnership}.
*
* This module is used through inheritance. It will make available all functions
* from parent (Ownable).
*/
abstract contract Ownable2Step is Ownable {
address private _pendingOwner;
event OwnershipTransferStarted(address indexed previousOwner, address indexed newOwner);
/**
* @dev Returns the address of the pending owner.
*/
function pendingOwner() public view virtual returns (address) {
return _pendingOwner;
}
/**
* @dev Starts the ownership transfer of the contract to a new account. Replaces the pending transfer if there is one.
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual override onlyOwner {
_pendingOwner = newOwner;
emit OwnershipTransferStarted(owner(), newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`) and deletes any pending owner.
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual override {
delete _pendingOwner;
super._transferOwnership(newOwner);
}
/**
* @dev The new owner accepts the ownership transfer.
*/
function acceptOwnership() public virtual {
address sender = _msgSender();
if (pendingOwner() != sender) {
revert OwnableUnauthorizedAccount(sender);
}
_transferOwnership(sender);
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuard {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
uint256 private _status;
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
constructor() {
_status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
// On the first call to nonReentrant, _status will be NOT_ENTERED
if (_status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
_status = ENTERED;
}
function _nonReentrantAfter() private {
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
_status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
return _status == ENTERED;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "../IERC20.sol";
import {IERC20Permit} from "../extensions/IERC20Permit.sol";
import {Address} from "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
/**
* @dev An operation with an ERC20 token failed.
*/
error SafeERC20FailedOperation(address token);
/**
* @dev Indicates a failed `decreaseAllowance` request.
*/
error SafeERC20FailedDecreaseAllowance(address spender, uint256 currentAllowance, uint256 requestedDecrease);
/**
* @dev Transfer `value` amount of `token` from the calling contract to `to`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeTransfer(IERC20 token, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transfer, (to, value)));
}
/**
* @dev Transfer `value` amount of `token` from `from` to `to`, spending the approval given by `from` to the
* calling contract. If `token` returns no value, non-reverting calls are assumed to be successful.
*/
function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
_callOptionalReturn(token, abi.encodeCall(token.transferFrom, (from, to, value)));
}
/**
* @dev Increase the calling contract's allowance toward `spender` by `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful.
*/
function safeIncreaseAllowance(IERC20 token, address spender, uint256 value) internal {
uint256 oldAllowance = token.allowance(address(this), spender);
forceApprove(token, spender, oldAllowance + value);
}
/**
* @dev Decrease the calling contract's allowance toward `spender` by `requestedDecrease`. If `token` returns no
* value, non-reverting calls are assumed to be successful.
*/
function safeDecreaseAllowance(IERC20 token, address spender, uint256 requestedDecrease) internal {
unchecked {
uint256 currentAllowance = token.allowance(address(this), spender);
if (currentAllowance < requestedDecrease) {
revert SafeERC20FailedDecreaseAllowance(spender, currentAllowance, requestedDecrease);
}
forceApprove(token, spender, currentAllowance - requestedDecrease);
}
}
/**
* @dev Set the calling contract's allowance toward `spender` to `value`. If `token` returns no value,
* non-reverting calls are assumed to be successful. Meant to be used with tokens that require the approval
* to be set to zero before setting it to a non-zero value, such as USDT.
*/
function forceApprove(IERC20 token, address spender, uint256 value) internal {
bytes memory approvalCall = abi.encodeCall(token.approve, (spender, value));
if (!_callOptionalReturnBool(token, approvalCall)) {
_callOptionalReturn(token, abi.encodeCall(token.approve, (spender, 0)));
_callOptionalReturn(token, approvalCall);
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data);
if (returndata.length != 0 && !abi.decode(returndata, (bool))) {
revert SafeERC20FailedOperation(address(token));
}
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*
* This is a variant of {_callOptionalReturn} that silents catches all reverts and returns a bool instead.
*/
function _callOptionalReturnBool(IERC20 token, bytes memory data) private returns (bool) {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We cannot use {Address-functionCall} here since this should return false
// and not revert is the subcall reverts.
(bool success, bytes memory returndata) = address(token).call(data);
return success && (returndata.length == 0 || abi.decode(returndata, (bool))) && address(token).code.length > 0;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;
/**
* A simple enum to indicate of the initial liquidity for DragonX / TitanX pool has been minted
*/
enum InitialLiquidityMinted {
No,
Yes
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (interfaces/draft-IERC6093.sol)
pragma solidity ^0.8.20;
/**
* @dev Standard ERC20 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC20 tokens.
*/
interface IERC20Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC20InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC20InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `spender`’s `allowance`. Used in transfers.
* @param spender Address that may be allowed to operate on tokens without being their owner.
* @param allowance Amount of tokens a `spender` is allowed to operate with.
* @param needed Minimum amount required to perform a transfer.
*/
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC20InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `spender` to be approved. Used in approvals.
* @param spender Address that may be allowed to operate on tokens without being their owner.
*/
error ERC20InvalidSpender(address spender);
}
/**
* @dev Standard ERC721 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC721 tokens.
*/
interface IERC721Errors {
/**
* @dev Indicates that an address can't be an owner. For example, `address(0)` is a forbidden owner in EIP-20.
* Used in balance queries.
* @param owner Address of the current owner of a token.
*/
error ERC721InvalidOwner(address owner);
/**
* @dev Indicates a `tokenId` whose `owner` is the zero address.
* @param tokenId Identifier number of a token.
*/
error ERC721NonexistentToken(uint256 tokenId);
/**
* @dev Indicates an error related to the ownership over a particular token. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param tokenId Identifier number of a token.
* @param owner Address of the current owner of a token.
*/
error ERC721IncorrectOwner(address sender, uint256 tokenId, address owner);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC721InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC721InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param tokenId Identifier number of a token.
*/
error ERC721InsufficientApproval(address operator, uint256 tokenId);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC721InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC721InvalidOperator(address operator);
}
/**
* @dev Standard ERC1155 Errors
* Interface of the https://eips.ethereum.org/EIPS/eip-6093[ERC-6093] custom errors for ERC1155 tokens.
*/
interface IERC1155Errors {
/**
* @dev Indicates an error related to the current `balance` of a `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
* @param balance Current balance for the interacting account.
* @param needed Minimum amount required to perform a transfer.
* @param tokenId Identifier number of a token.
*/
error ERC1155InsufficientBalance(address sender, uint256 balance, uint256 needed, uint256 tokenId);
/**
* @dev Indicates a failure with the token `sender`. Used in transfers.
* @param sender Address whose tokens are being transferred.
*/
error ERC1155InvalidSender(address sender);
/**
* @dev Indicates a failure with the token `receiver`. Used in transfers.
* @param receiver Address to which tokens are being transferred.
*/
error ERC1155InvalidReceiver(address receiver);
/**
* @dev Indicates a failure with the `operator`’s approval. Used in transfers.
* @param operator Address that may be allowed to operate on tokens without being their owner.
* @param owner Address of the current owner of a token.
*/
error ERC1155MissingApprovalForAll(address operator, address owner);
/**
* @dev Indicates a failure with the `approver` of a token to be approved. Used in approvals.
* @param approver Address initiating an approval operation.
*/
error ERC1155InvalidApprover(address approver);
/**
* @dev Indicates a failure with the `operator` to be approved. Used in approvals.
* @param operator Address that may be allowed to operate on tokens without being their owner.
*/
error ERC1155InvalidOperator(address operator);
/**
* @dev Indicates an array length mismatch between ids and values in a safeBatchTransferFrom operation.
* Used in batch transfers.
* @param idsLength Length of the array of token identifiers
* @param valuesLength Length of the array of token amounts
*/
error ERC1155InvalidArrayLength(uint256 idsLength, uint256 valuesLength);
}
{
"compilationTarget": {
"contracts/DragonX.sol": "DragonX"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "none"
},
"optimizer": {
"enabled": true,
"runs": 9999
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"titanBuyAddress_","type":"address"},{"internalType":"address","name":"dragonBuyAndBurnAdddress_","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"target","type":"address"}],"name":"AddressEmptyCode","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"AddressInsufficientBalance","type":"error"},{"inputs":[],"name":"CooldownPeriodActive","type":"error"},{"inputs":[],"name":"Create2EmptyBytecode","type":"error"},{"inputs":[],"name":"Create2FailedDeployment","type":"error"},{"inputs":[{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"Create2InsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"allowance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientAllowance","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientBalance","type":"error"},{"inputs":[{"internalType":"address","name":"approver","type":"address"}],"name":"ERC20InvalidApprover","type":"error"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"}],"name":"ERC20InvalidReceiver","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"}],"name":"ERC20InvalidSender","type":"error"},{"inputs":[{"internalType":"address","name":"spender","type":"address"}],"name":"ERC20InvalidSpender","type":"error"},{"inputs":[],"name":"FailedInnerCall","type":"error"},{"inputs":[],"name":"InsufficientTitanXAllowance","type":"error"},{"inputs":[],"name":"InsufficientTitanXBalance","type":"error"},{"inputs":[],"name":"InvalidAddress","type":"error"},{"inputs":[],"name":"InvalidCaller","type":"error"},{"inputs":[],"name":"LiquidityNotMintedYet","type":"error"},{"inputs":[],"name":"MintingNotYetActive","type":"error"},{"inputs":[],"name":"MintingPeriodOver","type":"error"},{"inputs":[],"name":"NoAdditionalStakesAllowed","type":"error"},{"inputs":[],"name":"NoEthClaimable","type":"error"},{"inputs":[],"name":"NoNeedForNewDragonStakeInstance","type":"error"},{"inputs":[],"name":"NoTokensToStake","type":"error"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"inputs":[],"name":"ReentrancyGuardReentrantCall","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"SafeERC20FailedOperation","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"caller","type":"address"},{"indexed":true,"internalType":"uint256","name":"totalClaimed","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"titanBuy","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"dragonBuyAndBurn","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"genesis","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"incentiveFee","type":"uint256"}],"name":"Claimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"stakeContractId","type":"uint256"},{"indexed":true,"internalType":"address","name":"stakeContractAddress","type":"address"}],"name":"DragonStakeInstanceCreated","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferStarted","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"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"dragonStakeAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TitanStakeStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"dragonStakeAddress","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TitanStakesEnded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"stateMutability":"nonpayable","type":"fallback"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"activeDragonStakeContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"claim","outputs":[{"internalType":"uint256","name":"claimedAmount","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"asset","type":"address"}],"name":"claimGenesis","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deployNewDragonStakeInstance","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"dragonBuyAndBurnAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"dragonStakeContracts","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"incentiveFeeForClaim","outputs":[{"internalType":"uint256","name":"fee","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"initalLiquidityMinted","outputs":[{"internalType":"enum InitialLiquidityMinted","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mintInitialLiquidity","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"mintPhaseBegin","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mintPhaseEnd","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mintRatioWeekEight","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mintRatioWeekEleven","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mintRatioWeekFive","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mintRatioWeekFour","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mintRatioWeekNine","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mintRatioWeekOne","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mintRatioWeekSeven","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mintRatioWeekSix","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mintRatioWeekTen","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mintRatioWeekThree","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mintRatioWeekTwelve","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"mintRatioWeekTwo","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"nextStakeTs","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"numDragonStakeContracts","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pendingOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"dragonBuyAndBurn","type":"address"}],"name":"setDragonBuyAndBurnAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"titanBuy","type":"address"}],"name":"setTitanBuyAddress","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stake","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountUnstaked","type":"uint256"}],"name":"stakeEnded","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"stakeReachedMaturity","outputs":[{"internalType":"bool","name":"hasStakesToEnd","type":"bool"},{"internalType":"address","name":"instanceAddress","type":"address"},{"internalType":"uint256","name":"sId","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"titanBuyAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalEthClaimable","outputs":[{"internalType":"uint256","name":"claimable","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalEthClaimed","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalStakesOpened","outputs":[{"internalType":"uint256","name":"totalStakes","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalTitanStaked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalTitanUnstaked","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"updateVault","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"vault","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]