// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.6;
// TattooMoney.io Public Sale Contract - via StableCoins, ETH and wBTC
//
// USE ONLY OWN WALLET (Metamask, TrustWallet, Trezor, Ledger...)
// DO NOT SEND FROM EXCHANGES OR ANY SERVICES
//
// Use ONLY ETH network, ERC20 tokens (Not Binance/Tron/whatever!)
//
// Set approval to contract address or use USDC authorization first
//
// DO NOT SEND STABLE TOKENS DIRECTLY - IT WILL NOT COUNT THAT!
//
// send ONLY round number of USDT/USDC/DAI!
// ie 20, 500, 2000 NOT 20.1, 500.5, 2000.3
// contract will IGNORE decimals!
//
// Need 150k gas limit.
// Use proper pay* function
contract TattooMoneyPublicSaleII {
uint256 private constant DECIMALS_TAT2 = 6;
uint256 private constant DECIMALS_DAI = 18;
uint256 private constant DECIMALS_USD = 6;
uint256 private constant DECIMALS_WBTC = 8;
/// max tokens per user is 1363636 as $15000 is AML limit
uint256 public constant maxTokens = 1363636363636;
/// contract starts accepting transfers
uint256 public dateStart;
/// hard time limit
uint256 public dateEnd;
/// total collected USD
uint256 public usdCollected;
/// sale is limited by tokens count
uint256 public tokensLimit;
/// tokens sold in this sale
uint256 public tokensSold;
uint256 public tokensforadolar;
// addresses of tokens
address public tat2 = 0x960773318c1AeaB5dA6605C49266165af56435fa;
address public usdc = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
address public dai = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address public wbtc = 0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599;
address public usdt = 0xdAC17F958D2ee523a2206206994597C13D831ec7;
address public wbtcoracle = 0xF4030086522a5bEEa4988F8cA5B36dbC97BeE88c;
address public ethoracle = 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419;
address public owner;
address public newOwner;
bool public saleEnded;
// deposited USD tokens per token address
mapping(address => uint256) private _deposited;
/// Tokens bought by user
mapping(address => uint256) public tokensBoughtOf;
event AcceptedUSD(address indexed user, uint256 amount);
event AcceptedWBTC(address indexed user, uint256 amount);
event AcceptedETH(address indexed user, uint256 amount);
string constant ERR_TRANSFER = "Token transfer failed";
string constant ERR_SALE_LIMIT = "Token sale limit reached";
string constant ERR_AML = "AML sale limit reached";
string constant ERR_SOON = "TOO SOON";
/**
Contract constructor
@param _owner adddress of contract owner
@param _tokensLimit maximum tokens that can be sold (round, ie 320123)
@param _startDate sale start timestamp
@param _endDate sale end timestamp
*/
constructor(
address _owner,
uint256 _tokensLimit, // 8666664
uint256 _startDate, // 12-06-2020 22:22:22 GMT (1628799742)
uint256 _endDate, // 26-08-2020 22:22:22 GMT (1630009342)
uint256 _tokensforadolar // Set the numer of tokens for $1 with decimals ( 90909090 )
) {
owner = _owner;
tokensLimit = _tokensLimit * (10**DECIMALS_TAT2);
dateStart = _startDate;
dateEnd = _endDate;
tokensforadolar = _tokensforadolar;
}
/**
Pay in using USDC, use approve/transferFrom
@param amount number of USDC (with decimals)
*/
function payUSDC(uint256 amount) external {
require(
INterfaces(usdc).transferFrom(msg.sender, address(this), amount),
ERR_TRANSFER
);
_pay(msg.sender, amount );
_deposited[usdc] += amount;
}
/**
Pay in using USDT, need set approval first
@param amount USDT amount (with decimals)
*/
function payUSDT(uint256 amount) external {
INterfacesNoR(usdt).transferFrom(msg.sender, address(this), amount);
_pay(msg.sender, amount );
_deposited[usdt] += amount;
}
/**
Pay in using DAI, need set approval first
@param amount number of DAI (with 6 decimals)
*/
function payDAI(uint256 amount) external {
require(
INterfaces(dai).transferFrom(msg.sender, address(this), amount),
ERR_TRANSFER
);
_pay(msg.sender, amount / (10**12));
_deposited[dai] += amount;
}
/**
Pay in using wBTC, need set approval first
@param amount number of wBTC (with decimals)
*/
function paywBTC(uint256 amount) external {
require(
INterfaces(wbtc).transferFrom(msg.sender, address(this), amount),
ERR_TRANSFER
);
_paywBTC(msg.sender, amount );
_deposited[wbtc] += amount;
}
//
// accept ETH
//
// takes about 50k gas
receive() external payable {
_payEth(msg.sender, msg.value);
}
function payETH() external payable {
_payEth(msg.sender, msg.value);
}
/**
Get ETH price from Chainlink.
@return price for 1 ETH with 6 decimals
*/
function tokensPerEth() public view returns (uint256) {
int256 answer;
(, answer, , , ) = INterfaces(ethoracle).latestRoundData();
// geting price with 6 decimals
return uint256((uint256(answer) * tokensforadolar)/10**8);
}
/**
Get BTC price from Chainlink.
@return price for 1 BTC with 6 decimals
*/
function tokensPerwBTC() public view returns (uint256) {
int256 answer;
(, answer, , , ) = INterfaces(wbtcoracle).latestRoundData();
// geting price with 6 decimals
return uint256((uint256(answer) * tokensforadolar)/10**8);
}
/**
How much tokens left to sale
*/
function tokensLeft() external view returns (uint256) {
return tokensLimit - tokensSold;
}
function _payEth(address user, uint256 amount) internal notEnded {
uint256 sold = (amount * tokensPerEth()) / (10**18);
tokensSold += sold;
require(tokensSold <= tokensLimit, ERR_SALE_LIMIT);
tokensBoughtOf[user] += sold;
require(tokensBoughtOf[user] <= maxTokens, ERR_AML);
_sendTokens(user, sold);
emit AcceptedETH(user, amount);
}
function _paywBTC(address user, uint256 amount) internal notEnded {
uint256 sold = (amount * tokensPerwBTC()) / (10**8);
tokensSold += sold;
require(tokensSold <= tokensLimit, ERR_SALE_LIMIT);
tokensBoughtOf[user] += sold;
require(tokensBoughtOf[user] <= maxTokens, ERR_AML);
_sendTokens(user, sold);
emit AcceptedWBTC(user, amount);
}
function _pay(address user, uint256 usd) internal notEnded {
uint256 sold = (usd * tokensforadolar) / (10**6);
tokensSold += sold;
require(tokensSold <= tokensLimit, ERR_SALE_LIMIT);
tokensBoughtOf[user] += sold;
require(tokensBoughtOf[user] <= maxTokens, ERR_AML);
_sendTokens(user, sold);
emit AcceptedUSD(user, usd);
}
function _sendTokens(address user, uint256 amount) internal notEnded {
require(
INterfaces(tat2).transfer(user, amount),
ERR_TRANSFER
);
}
//
// modifiers
//
modifier notEnded() {
require(!saleEnded, "Sale ended");
require(
block.timestamp > dateStart && block.timestamp < dateEnd,
"Too soon or too late"
);
_;
}
modifier onlyOwner() {
require(msg.sender == owner, "Only for contract Owner");
_;
}
/// Take out stables, wBTC and ETH
function takeAll() external onlyOwner {
uint256 amt = INterfaces(usdt).balanceOf(address(this));
if (amt > 0) {
INterfacesNoR(usdt).transfer(owner, amt);
}
amt = INterfaces(usdc).balanceOf(address(this));
if (amt > 0) {
require(INterfaces(usdc).transfer(owner, amt), ERR_TRANSFER);
}
amt = INterfaces(dai).balanceOf(address(this));
if (amt > 0) {
require(INterfaces(dai).transfer(owner, amt), ERR_TRANSFER);
}
amt = INterfaces(wbtc).balanceOf(address(this));
if (amt > 0) {
require(INterfaces(wbtc).transfer(owner, amt), ERR_TRANSFER);
}
amt = INterfaces(tat2).balanceOf(address(this));
if (amt > 0) {
require(INterfaces(tat2).transfer(owner, amt), ERR_TRANSFER);
}
amt = address(this).balance;
if (amt > 0) {
payable(owner).transfer(amt);
}
}
/// we can recover any ERC20!
function recoverErc20(address token) external onlyOwner {
uint256 amt = INterfaces(token).balanceOf(address(this));
if (amt > 0) {
INterfacesNoR(token).transfer(owner, amt); // use broken ERC20 to ignore return value
}
}
/// just in case
function recoverEth() external onlyOwner {
payable(owner).transfer(address(this).balance);
}
function EndSale() external onlyOwner {
saleEnded = true;
}
function changeOwner(address _newOwner) external onlyOwner {
newOwner = _newOwner;
}
function acceptOwnership() external {
require(
msg.sender != address(0) && msg.sender == newOwner,
"Only NewOwner"
);
newOwner = address(0);
owner = msg.sender;
}
}
// Interfaces for contract interaction
interface INterfaces {
function balanceOf(address) external returns (uint256);
function transfer(address, uint256) external returns (bool);
function transferFrom(
address,
address,
uint256
) external returns (bool);
// chainlink ETH/USD, ethoracle
// answer|int256 : 304706968812 - 8 decimals
// chainlink BTC/USD wbtcoracle
// answer|int256 : 4419282000000 - 8 decimals
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
// For tokens that do not return true on transfers eg. USDT
interface INterfacesNoR {
function transfer(address, uint256) external;
function transferFrom(
address,
address,
uint256
) external;
}
// by Patrick
{
"compilationTarget": {
"TattooMoneyPublicSaleII.sol": "TattooMoneyPublicSaleII"
},
"evmVersion": "berlin",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": false,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"uint256","name":"_tokensLimit","type":"uint256"},{"internalType":"uint256","name":"_startDate","type":"uint256"},{"internalType":"uint256","name":"_endDate","type":"uint256"},{"internalType":"uint256","name":"_tokensforadolar","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"AcceptedETH","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"AcceptedUSD","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"user","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"AcceptedWBTC","type":"event"},{"inputs":[],"name":"EndSale","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"changeOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"dai","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dateEnd","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"dateStart","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ethoracle","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxTokens","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"newOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"payDAI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"payETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"payUSDC","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"payUSDT","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"paywBTC","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"recoverErc20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"recoverEth","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"saleEnded","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"takeAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"tat2","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"tokensBoughtOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokensLeft","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokensLimit","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokensPerEth","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokensPerwBTC","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokensSold","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tokensforadolar","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"usdCollected","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"usdc","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"usdt","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"wbtc","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"wbtcoracle","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]