//SPDX-License-Identifier: MIT
pragma solidity 0.8.19;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
/// @title PUK: An erc20 token with sell fees
contract PUK is ERC20, Ownable {
///ERRORS //
error MaxFeeLimitExceeded();
error ZeroAddressNotAllowed();
error UpdateBoolValue();
error AmountNotInRange();
error CanNotModifyMainPair();
error NotAuthorized();
/// @notice Max fees Limit
uint16 constant private MAX_FEE = 6;
/// @notice Minimum tax tokens limit required to swap for BNB
uint256 constant private MIN_SWAP_AT_AMOUNT = 1e3 * 1e18;
/// @notice max limit to swap collected tax for bnb per tx
uint256 constant private MAX_SWAP_AT_AMOUNT = 5e9 * 1e18;
/// @notice global burn address
address constant private DEAD = address(0xdead);
/// @notice fees on every sell
uint16 public sellFees = 6;
/// @notice fees wallet to receive bnb
address public feeWallet = address(0x123);
/// @notice pancakswap main pair address (TOKEN/BNB)
address public uniswapV2Pair;
/// @notice router address for pancakeswap
IUniswapV2Router02 public uniswapV2Router;
/// @notice max Supply for token
uint256 maxSupply = 5e11 * 1e18; // 500 Billion
/// @notice collected tax token amount after that it will be swapped for bnb
uint256 swapTokensAtAmount = (maxSupply * 10) / 100000; // 0.01% of the supply
/// @notice mapping for users excluded from fees
mapping(address => bool) public isExcludedFromFees;
/// @notice mapping for liquidity pairs addresses
mapping(address=> bool) public isLiquidityPair;
/// @notice allows collected tokens fees to swapped for bnb
bool public swapEnabled = true;
bool swapping;
///events
event SwapTokensAmountUpdated (uint256 indexed newAmount);
event FeeWalletUpdated(address indexed newFeeWallet);
event ExcludedFromFees (address indexed account, bool value);
event NewLPUpdated(address indexed lp, bool value);
event FeesUpdated( uint16 indexed sellFee);
/// @dev create an erc20 token using openzeppelin ownable and erc20.
/// sets the pancakeswap router address, create main pair, exclude the
/// owner, feewallet, burn address and token itself from fees. mint the
/// maxSupply to the owner during deployment.
constructor() ERC20("Punk Rabbit", "PUK"){
IUniswapV2Router02 _uniswapV2Router = IUniswapV2Router02(
0x10ED43C718714eb63d5aA57B78B54704E256024E//Pancakeswap V2 Router
);
uniswapV2Router = _uniswapV2Router;
uniswapV2Pair = IUniswapV2Factory(_uniswapV2Router.factory())
.createPair(address(this), _uniswapV2Router.WETH());
isLiquidityPair[uniswapV2Pair] = true;
isExcludedFromFees[msg.sender] = true;
isExcludedFromFees[address(this)] = true;
isExcludedFromFees[feeWallet] = true;
isExcludedFromFees[DEAD] = true;
_mint(owner(), maxSupply);
}
///@dev update fees wallet to receive BNB
///@param _newFeeWallet: new wallet address for fees
///Requirements -
/// _newFeeWallet address should not be zero address.
function updateFeesWallet (address _newFeeWallet) external onlyOwner {
if(_newFeeWallet == address(0)){
revert ZeroAddressNotAllowed();
}
feeWallet = _newFeeWallet;
emit FeeWalletUpdated(_newFeeWallet);
}
///@dev update fees for sell
///@param sell: new sell fees
///Requirements-
/// sell should be less than equal to MAX_FEE
function updateFees (uint16 sell) external onlyOwner {
if(sell > MAX_FEE){
revert MaxFeeLimitExceeded();
}
sellFees = sell;
emit FeesUpdated( sell);
}
///@dev exclude or include in fee mapping
///@param user: user to exclude or include in fee
function excludeFromFees (address user, bool isExcluded) external onlyOwner {
if(isExcludedFromFees[user] == isExcluded){
revert UpdateBoolValue();
}
isExcludedFromFees[user] = isExcluded;
emit ExcludedFromFees(user, isExcluded);
}
///@dev add or remove new pairs
///@param newPair; new pair address
///@param value: boolean value true true for adding, false for removing
///Requirements -
///Can't modify uniswapV2Pair (main pair)
function setLiquidityPairs (address newPair, bool value) external onlyOwner{
if(newPair == uniswapV2Pair){
revert CanNotModifyMainPair();
}
isLiquidityPair[newPair] = value;
emit NewLPUpdated(newPair, value);
}
///@dev update the swap token amount
///@param _newSwapAmount: new token amount to swap threshold
///Requirements--
/// amount must greator than equal to MIN_SWAP_AT_AMOUNT
function setSwapTokensAtAmount (uint256 _newSwapAmount) external onlyOwner {
if(_newSwapAmount < MIN_SWAP_AT_AMOUNT && _newSwapAmount > MAX_SWAP_AT_AMOUNT){
revert AmountNotInRange();
}
swapTokensAtAmount = _newSwapAmount;
emit SwapTokensAmountUpdated(_newSwapAmount);
}
///@notice dev can claim stucked tokens, if sent by someone accidently
///@param token: address to token to be resuced
function claimStuckedTokens (address token) external {
if(msg.sender != feeWallet){
revert NotAuthorized();
}
IERC20 tkn = IERC20(token);
uint256 balance = tkn.balanceOf(address(this));
tkn.transfer(feeWallet, balance);
}
///@notice transfer function to manage token transfer/fees/limits
function _transfer(
address from,
address to,
uint256 amount
) internal override {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
require(amount > 0, "Transfer amount must be greater than zero");
uint256 contractBalance = balanceOf(address(this));
if (
swapEnabled &&
!swapping &&
!isLiquidityPair[from] &&
!isExcludedFromFees[from] &&
!isExcludedFromFees[to] &&
contractBalance >=swapTokensAtAmount
) {
if(contractBalance > MAX_SWAP_AT_AMOUNT){
contractBalance = MAX_SWAP_AT_AMOUNT;
}
swapping = true;
swapTokensForEth(contractBalance);
swapping = false;
}
bool takeFee = !swapping;
// if any account belongs to _isExcludedFromFee account then remove the fee
if (isExcludedFromFees[from] || isExcludedFromFees[to]) {
takeFee = false;
}
uint256 fees = 0;
// only take fees on buys/sells, do not take on wallet transfers
if (takeFee) {
//on sell
if ( isLiquidityPair[to] && sellFees > 0) {
fees = (amount * sellFees) / 100;
}
if (fees > 0) {
super._transfer(from, address(this), fees);
}
amount -= fees;
}
super._transfer(from, to, amount);
}
///@notice private function to swap tax to BNB
function swapTokensForEth(uint256 tokenAmount) private {
// generate the uniswap pair path of token -> wbnb
address[] memory path = new address[](2);
path[0] = address(this);
path[1] = uniswapV2Router.WETH();
if(allowance(address(this), address(uniswapV2Router)) < tokenAmount){
_approve(address(this), address(uniswapV2Router), type(uint256).max);
}
// make the swap
uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
tokenAmount,
0, // accept any amount of BNB
path,
feeWallet,
block.timestamp
);
}
}