// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
/**
* @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;
}
}
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);
}
}
interface IOpenNFT {
struct UserNFTs {
uint256 tokenId;
uint256 strength;
}
function getUserNFTs(
address _address
) external view returns (UserNFTs[] memory);
}
interface IERC20 {
function transfer(
address recipient,
uint256 amount
) external returns (bool);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
function balanceOf(address account) external view returns (uint256);
}
interface IUniswapV2Router02 {
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external returns (uint[] memory amounts);
function swapExactETHForTokens(
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external payable returns (uint[] memory amounts);
function getAmountsOut(
uint amountIn,
address[] calldata path
) external view returns (uint[] memory amounts);
function getAmountsIn(
uint amountOut,
address[] calldata path
) external view returns (uint[] memory amounts);
}
contract TakoSwapPrivateSaleETH is Ownable {
uint256 public MAX_BUY = 100_000 ether;
uint256 public MIN_BUY_PER_TX = 100 ether;
uint256 public MAX_BUY_PER_ADDRESS = 5_000 ether;
uint256 public totalContributed;
uint256 public startTime;
IOpenNFT public openNFT;
IERC20 public OGPU;
IUniswapV2Router02 public uniswapRouter;
address public OGPUAddress;
address public openNFTAddress;
address public uniswapRouterAddress;
address public WETH;
bool public isSaleOpen = false;
bool public reentrancyLock = false;
mapping(address => uint256) public contributionAmount;
address[] public contributors;
enum NFTType {
Diamond,
Common,
None
}
// Events
event Contributed(address indexed buyer, uint256 ogpuAmount);
event SaleStatusChanged(bool isOpen);
event SalePublicStatusChanged(bool isPublic);
event AddressesSet(address ogpu, address nft, address router, address weth);
event FundsWithdrawn(address indexed to, uint256 amount);
event TokensWithdrawn(address indexed token, address indexed to, uint256 amount);
event LimitsUpdated(uint256 maxBuy, uint256 minBuyPerTx, uint256 maxBuyPerAddress);
constructor(
address initialOwner,
address _ogpuAddress,
address _openNFTAddress,
address _uniswapRouterAddress,
address _weth
) Ownable(initialOwner) {
require(_ogpuAddress != address(0), "OGPU address cannot be zero");
require(_openNFTAddress != address(0), "NFT address cannot be zero");
require(_uniswapRouterAddress != address(0), "Router address cannot be zero");
require(_weth != address(0), "WETH address cannot be zero");
OGPUAddress = _ogpuAddress;
openNFTAddress = _openNFTAddress;
uniswapRouterAddress = _uniswapRouterAddress;
WETH = _weth;
OGPU = IERC20(_ogpuAddress);
openNFT = IOpenNFT(_openNFTAddress);
uniswapRouter = IUniswapV2Router02(_uniswapRouterAddress);
emit AddressesSet(_ogpuAddress, _openNFTAddress, _uniswapRouterAddress, _weth);
}
receive() external payable {}
modifier stopped() {
require(isSaleOpen, "TakoSwapPrivateSale: sale is stopped");
_;
}
modifier nonReentrant() {
require(!reentrancyLock, "TakoSwapPrivateSale: reentrant call");
reentrancyLock = true;
_;
reentrancyLock = false;
}
function getBestNFT(address _address) public view returns (NFTType) {
if (_address == address(0)) return NFTType.None;
IOpenNFT.UserNFTs[] memory userNFTs = openNFT.getUserNFTs(_address);
if (userNFTs.length == 0) return NFTType.None;
bool isCommon = false;
for (uint256 i = 0; i < userNFTs.length; i++) {
if (userNFTs[i].strength > 10) {
return NFTType.Diamond;
}
else if (userNFTs[i].strength > 0) {
isCommon = true;
}
}
if (isCommon) return NFTType.Common;
return NFTType.None;
}
function getContributors() external view returns (address[] memory) {
return contributors;
}
function getContributorAmount(
address _address
) external view returns (uint256) {
return contributionAmount[_address];
}
function contributionChecks(
address buyerAddress,
uint256 amount
) internal view {
require(buyerAddress != address(0), "Invalid buyer address");
require(
amount >= MIN_BUY_PER_TX,
"TakoSwapPrivateSale: amount must be greater than minimum"
);
require(
contributionAmount[buyerAddress] + amount <= MAX_BUY_PER_ADDRESS,
"TakoSwapPrivateSale: max buy per address exceeded"
);
require(
totalContributed + amount <= MAX_BUY,
"TakoSwapPrivateSale: presale is filled"
);
require(
startTime > 0,
"TakoSwapPrivateSale: presale is not started yet"
);
NFTType nftType = getBestNFT(buyerAddress);
if (nftType == NFTType.Diamond && block.timestamp < startTime + 5 minutes) {
require(
totalContributed + amount <= MAX_BUY / 2,
"TakoSwapPrivateSale: presale is filled for diamond holders"
);
} else if (nftType == NFTType.Common) {
// Common holders get access after 5 minutes
require(
block.timestamp > startTime + 5 minutes,
"TakoSwapPrivateSale: presale is not started yet for common holders"
);
} else {
require(
block.timestamp > startTime + 35 minutes,
"TakoSwapPrivateSale: Sale not yet public. Buy OpenNFT for Whitelist Slot"
);
}
}
function buyOGPU(
uint256 amount,
address _buyerAddress
) internal returns (uint256) {
address[] memory path = new address[](2);
path[0] = address(WETH);
path[1] = address(OGPU);
uint256 amountOutMin = uniswapRouter.getAmountsOut(
amount,
path
)[1];
// 7% slippage is acceptable
uint256 slippage = (amountOutMin * 90) / 100;
uint256 balanceBefore = OGPU.balanceOf(address(this));
// swap
require(
uniswapRouter.swapExactETHForTokens{value: amount}(
slippage,
path,
address(this),
block.timestamp
)[1] > 0,
"TakoSwapPrivateSale: swap failed"
);
uint256 balanceAfter = OGPU.balanceOf(address(this));
require(
balanceAfter - balanceBefore > 0,
"TakoSwapPrivateSale: swap failed"
);
if (balanceAfter - balanceBefore > MAX_BUY_PER_ADDRESS) {
uint256 excess = balanceAfter - balanceBefore - (MAX_BUY_PER_ADDRESS - contributionAmount[_buyerAddress]);
require(
OGPU.transfer(_msgSender(), excess),
"TakoSwapPrivateSale: transfer failed"
);
return MAX_BUY_PER_ADDRESS - contributionAmount[_buyerAddress];
}
return balanceAfter - balanceBefore;
}
function contribute(uint256 amount) external payable nonReentrant stopped {
require(msg.value == amount, "TakoSwapPrivateSale: insufficient funds");
require(amount > 0, "Amount must be greater than 0");
address buyerAddress = _msgSender();
uint256 ogpuAmount = buyOGPU(amount, buyerAddress);
contributionChecks(buyerAddress, ogpuAmount);
if (contributionAmount[buyerAddress] == 0) {
contributors.push(buyerAddress);
}
contributionAmount[buyerAddress] += ogpuAmount;
totalContributed += ogpuAmount;
emit Contributed(buyerAddress, ogpuAmount);
}
function contributeOGPU(
uint256 amount
) external nonReentrant stopped {
require(amount > 0, "Amount must be greater than 0");
address buyerAddress = _msgSender();
contributionChecks(buyerAddress, amount);
require(
OGPU.transferFrom(buyerAddress, address(this), amount),
"TakoSwapPrivateSale: transfer failed"
);
if (contributionAmount[buyerAddress] == 0) {
contributors.push(buyerAddress);
}
contributionAmount[buyerAddress] += amount;
totalContributed += amount;
emit Contributed(buyerAddress, amount);
}
function stopSale() external onlyOwner {
startTime = 0;
isSaleOpen = false;
emit SaleStatusChanged(false);
}
function startSale() external onlyOwner {
startTime = block.timestamp;
isSaleOpen = true;
emit SaleStatusChanged(true);
}
function updateLimits(
uint256 _maxBuy,
uint256 _minBuyPerTx,
uint256 _maxBuyPerAddress
) external onlyOwner {
require(_minBuyPerTx <= _maxBuyPerAddress, "Min buy cannot exceed max buy per address");
require(_maxBuyPerAddress <= _maxBuy, "Max buy per address cannot exceed max buy");
MAX_BUY = _maxBuy;
MIN_BUY_PER_TX = _minBuyPerTx;
MAX_BUY_PER_ADDRESS = _maxBuyPerAddress;
emit LimitsUpdated(_maxBuy, _minBuyPerTx, _maxBuyPerAddress);
}
function withdraw() external onlyOwner nonReentrant {
uint256 balance = address(this).balance;
require(balance > 0, "No ETH to withdraw");
payable(owner()).transfer(balance);
emit FundsWithdrawn(owner(), balance);
}
function withdrawOGPU() external onlyOwner nonReentrant {
uint256 balance = OGPU.balanceOf(address(this));
require(balance > 0, "No OGPU to withdraw");
require(
OGPU.transfer(owner(), balance),
"TakoSwapPrivateSale: transfer failed"
);
emit TokensWithdrawn(address(OGPU), owner(), balance);
}
function withdrawTokens(
address tokenAddress
) external onlyOwner nonReentrant {
require(tokenAddress != address(0), "Invalid token address");
IERC20 token = IERC20(tokenAddress);
uint256 balance = token.balanceOf(address(this));
require(balance > 0, "No tokens to withdraw");
require(
token.transfer(owner(), balance),
"TakoSwapPrivateSale: transfer failed"
);
emit TokensWithdrawn(tokenAddress, owner(), balance);
}
}
{
"compilationTarget": {
"contracts/privateSaleETH.sol": "TakoSwapPrivateSaleETH"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"initialOwner","type":"address"},{"internalType":"address","name":"_ogpuAddress","type":"address"},{"internalType":"address","name":"_openNFTAddress","type":"address"},{"internalType":"address","name":"_uniswapRouterAddress","type":"address"},{"internalType":"address","name":"_weth","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"OwnableInvalidOwner","type":"error"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"OwnableUnauthorizedAccount","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"ogpu","type":"address"},{"indexed":false,"internalType":"address","name":"nft","type":"address"},{"indexed":false,"internalType":"address","name":"router","type":"address"},{"indexed":false,"internalType":"address","name":"weth","type":"address"}],"name":"AddressesSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"buyer","type":"address"},{"indexed":false,"internalType":"uint256","name":"ogpuAmount","type":"uint256"}],"name":"Contributed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"FundsWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"maxBuy","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minBuyPerTx","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"maxBuyPerAddress","type":"uint256"}],"name":"LimitsUpdated","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":false,"internalType":"bool","name":"isPublic","type":"bool"}],"name":"SalePublicStatusChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"isOpen","type":"bool"}],"name":"SaleStatusChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"token","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"TokensWithdrawn","type":"event"},{"inputs":[],"name":"MAX_BUY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_BUY_PER_ADDRESS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_BUY_PER_TX","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"OGPU","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"OGPUAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"contribute","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"contributeOGPU","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"contributionAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"contributors","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"getBestNFT","outputs":[{"internalType":"enum TakoSwapPrivateSaleETH.NFTType","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_address","type":"address"}],"name":"getContributorAmount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getContributors","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"isSaleOpen","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"openNFT","outputs":[{"internalType":"contract IOpenNFT","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"openNFTAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"reentrancyLock","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"startSale","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"startTime","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"stopSale","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"totalContributed","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"uniswapRouter","outputs":[{"internalType":"contract IUniswapV2Router02","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"uniswapRouterAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_maxBuy","type":"uint256"},{"internalType":"uint256","name":"_minBuyPerTx","type":"uint256"},{"internalType":"uint256","name":"_maxBuyPerAddress","type":"uint256"}],"name":"updateLimits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdrawOGPU","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenAddress","type":"address"}],"name":"withdrawTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]