// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.1) (utils/Context.sol)pragmasolidity ^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.
*/abstractcontractContext{
function_msgSender() internalviewvirtualreturns (address) {
returnmsg.sender;
}
function_msgData() internalviewvirtualreturns (bytescalldata) {
returnmsg.data;
}
function_contextSuffixLength() internalviewvirtualreturns (uint256) {
return0;
}
}
Contract Source Code
File 2 of 4: IERC20.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)pragmasolidity ^0.8.20;/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/interfaceIERC20{
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/eventTransfer(addressindexedfrom, addressindexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/eventApproval(addressindexed owner, addressindexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/functiontotalSupply() externalviewreturns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/functionbalanceOf(address account) externalviewreturns (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.
*/functiontransfer(address to, uint256 value) externalreturns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/functionallowance(address owner, address spender) externalviewreturns (uint256);
/**
* @dev Sets 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.
*/functionapprove(address spender, uint256 value) externalreturns (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.
*/functiontransferFrom(addressfrom, address to, uint256 value) externalreturns (bool);
}
Contract Source Code
File 3 of 4: Ownable.sol
// SPDX-License-Identifier: MIT// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)pragmasolidity ^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.
*/abstractcontractOwnableisContext{
addressprivate _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/errorOwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/errorOwnableInvalidOwner(address owner);
eventOwnershipTransferred(addressindexed previousOwner, addressindexed 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.
*/modifieronlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/functionowner() publicviewvirtualreturns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/function_checkOwner() internalviewvirtual{
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.
*/functionrenounceOwnership() publicvirtualonlyOwner{
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/functiontransferOwnership(address newOwner) publicvirtualonlyOwner{
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) internalvirtual{
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
Contract Source Code
File 4 of 4: PresaleV2.sol
// SPDX-License-Identifier: Nonepragmasolidity ^0.8.24;import"@openzeppelin/contracts/access/Ownable.sol";
import"@openzeppelin/contracts/token/ERC20/IERC20.sol";
contractPresaleV2isOwnable{
structUserInfo {
uint256 tokensPurchased;
uint256 purchasedAmountClaimed;
uint256 tokensReferred;
uint256 referredAmountClaimed;
}
mapping(address=> UserInfo) public users;
mapping(bytes=>address) public referrers;
uint256public totalTokensSupplied;
uint256public totalTokensPurchased;
uint256public tokenRatio;
uint256publicconstant VESTING_PERIOD =14days;
uint256publicconstant CLIFF_DURATION =12hours;
uint256publicconstant INITIAL_UNLOCK_PERCENT =75;
uint256publicconstant BONUS =3; // 3% uint256privateconstant PERCENT =100;
uint256privateconstant FALSE =1;
uint256privateconstant TRUE =2;
uint256public saleEnded = FALSE;
uint256public claimEnabled = FALSE;
uint256public claimTime;
uint256public launchTime;
IERC20 public tokenAddress;
errorExceedsSupply();
errorSaleNotEnded();
errorInvalidSaleState();
errorNotEnabled();
errorAlreadyLaunched();
constructor(address _tokenAddress) Ownable(msg.sender) {
tokenAddress = IERC20(_tokenAddress);
}
receive() externalpayable{}
functionlaunch(uint256 ratio, uint256 suppliedTokens) externalonlyOwner{
if (launchTime !=0) {
revert AlreadyLaunched();
}
launchTime =block.timestamp;
tokenRatio = ratio;
totalTokensSupplied = suppliedTokens;
tokenAddress.transferFrom(msg.sender, address(this), suppliedTokens);
}
functionendSale() externalonlyOwner{
saleEnded = TRUE;
}
functionenableClaims() externalonlyOwner{
if (saleEnded != TRUE) {
revert SaleNotEnded();
}
claimEnabled = TRUE;
claimTime =block.timestamp;
// Transfers extra tokens back to the owner
tokenAddress.transfer(msg.sender, totalTokensSupplied - totalTokensPurchased);
}
functionpurchase(bytescalldata referral) externalpayable{
if (launchTime ==0|| saleEnded == TRUE) {
revert InvalidSaleState();
}
uint256 tokenAmountPreBonus =msg.value* tokenRatio; // tokens based on ETH amount aloneuint256 bonusAmount;
address referrer;
if (referral.length!=0) {
// if the user supplied referral data check the referral address
referrer = referrers[referral];
if (referrer !=address(0)) {
// calculate the bonus if valid referral code
bonusAmount = tokenAmountPreBonus * BONUS / PERCENT;
}
}
if (totalTokensPurchased + tokenAmountPreBonus + (bonusAmount *2) > totalTokensSupplied) {
revert ExceedsSupply();
}
totalTokensPurchased = totalTokensPurchased + tokenAmountPreBonus + (bonusAmount *2);
users[msg.sender].tokensPurchased = users[msg.sender].tokensPurchased + (tokenAmountPreBonus + bonusAmount);
users[referrer].tokensReferred = users[referrer].tokensReferred + bonusAmount;
}
functionclaimTokensReferred() external{
if (claimEnabled != TRUE) {
revert NotEnabled();
}
// Claimable tokens vest over time uint256 vested = vestedAmount(users[msg.sender].tokensReferred);
// Calculate claimable, update claimed amount, send user their tokensuint256 claimable = vested - users[msg.sender].referredAmountClaimed;
users[msg.sender].referredAmountClaimed = vested;
tokenAddress.transfer(msg.sender, claimable);
}
// Allows the users to claim tokens once enabledfunctionclaim() external{
if (claimEnabled != TRUE) {
revert NotEnabled();
}
// Claimable tokens vest over time uint256 vested = vestedAmount(users[msg.sender].tokensPurchased);
// Calculate claimable, update claimed amount, send user their tokensuint256 claimable = vested - users[msg.sender].purchasedAmountClaimed;
users[msg.sender].purchasedAmountClaimed = vested;
tokenAddress.transfer(msg.sender, claimable);
}
functionsetReferrers(bytes[] calldata codes, address[] calldata addresses) externalonlyOwner{
for(uint256 i; i < codes.length; ) {
referrers[codes[i]] = addresses[i];
unchecked {
++i;
}
}
}
// Withdraws all ETH from the contractfunctionwithdrawETH() externalonlyOwner{
(bool success, ) =msg.sender.call{value: address(this).balance}("");
require(success);
}
// Withdraws all of a specified token to a contract - should only be used on the presale token in case of emergencyfunctionemergencyWithdrawTokens(address token) externalonlyOwner{
IERC20(token).transfer(msg.sender, IERC20(token).balanceOf(address(this)));
}
functiontokensRemainingToPurchase() externalviewreturns (uint256){
return totalTokensSupplied - totalTokensPurchased;
}
// Calculates the amount of vested tokens from the time claiming was enabledfunctionvestedAmount(uint256 totalPurchased) publicviewreturns (uint256) {
if (claimTime ==0) {
return0;
}
uint256 elapsedTime =block.timestamp- claimTime;
uint256 initalUnlockAmount = (totalPurchased * INITIAL_UNLOCK_PERCENT) / PERCENT;
if (elapsedTime < CLIFF_DURATION) {
// Before 12 hours, only the initial 75% is availablereturn initalUnlockAmount;
} elseif (elapsedTime >= CLIFF_DURATION + VESTING_PERIOD) {
// After the vesting period, 100% is vestedreturn totalPurchased;
} else { // calculate vested amount
elapsedTime =block.timestamp- (claimTime + CLIFF_DURATION); // vesting starts from cliff durationuint256 nonInitialAmount = (totalPurchased * (PERCENT - INITIAL_UNLOCK_PERCENT)) / PERCENT; // amount that vests after initial unlockuint256 currVestedAmount = (nonInitialAmount * elapsedTime) / VESTING_PERIOD; // amount of non-initial that has vestedreturn initalUnlockAmount + currVestedAmount; // initial unlock + vested amount
}
}
}