Hi guys, I create this token, honestly i couldn’t tested properly locally because of the router i’m setting as i couldn’t create a mock router locally using Foundry. However, i was able to deploy the contract on BSC Testnet and verify the contract.
Here is my challenge after deploying this contract… Using pancakeswap
- I can add liquidity successfully.
- I can buy/sell when the wallet is add to excluded wallets
- I can buy/sell if i toggle tax fee off
But
- I can’t buy/sell if i toggle tax on
- I can’t buy/sell if the wallet is not included in isExcludedFromFee mapping
Debug:
When i check the event logs on bsc scan, i noticed that the _update() is not called at all and no event was logged. So when fee is enabled and i’m transacting from a wallet not added to isExcludedFromFee, my metamask did not pop out for me to confirm transaction and then the transaction timed out with error “Unknown error: “Failed to fetch”. Try increasing your slippage tolerance.”
Code to reproduce
/*
* Tokenomics
*
* Name - CocoToken
* Symbol - COCO
* MAX Supply - 2.1 Million
* Tax 5% - only applicable for the first 1 year from the time liquidity is provided (0% thereafter)
* 5% tax distribution
- 1% to liquidity pool
- 1% to marketing wallet (sent as BNB)
- 1% to ‘buyback’ wallet (sent as BNB)
- 2% to RewardHolderNFT Contract (sent as COCO)
* Other features
- Anti-dump Max Sell no more than 0.5% of supply (105k) over 72 hours – only applicable for 6 months (0% thereafter)
*/
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {SafeMath} from "./library/SafeMath.sol";
import {ERC20Capped} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Capped.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {IUniswapV2Factory} from "./interface/IUniswapV2Factory.sol";
import {IUniswapV2Router02} from "./interface/IUniswapV2Router02.sol";
// import {RewardHolderNFT} from "./RewardHolderNFT.sol";
contract CocoToken is ERC20Capped, Ownable, ReentrancyGuard {
using SafeMath for uint256;
// Errors
error CocoToken__InvalidAddress();
error CocoToken__TransferFromZeroAddress();
error CocoToken__AddressCannotBeZeroAddress();
error CocoToken__AmountMustBeGreaterThanZero();
error CocoToken__AntiDumpLimitExceeded();
// State variables
address private uniswapV2Pair;
IUniswapV2Router02 private uniswapV2Router;
// State constants and immutables
uint256 private constant MAX_SUPPLY = 2100000 * 10 ** 4;
uint256 private constant ANTI_DUMP_DURATION = 72 hours;
uint256 private constant ANTI_DUMP_PERIOD = 6 * 30 days;
uint256 private constant TAX_PERIOD = 365 days;
uint256 private constant MAX_SELL_PERCENT = 5; // 0.5% (5/1000)
address payable public immutable nftRewardContract;
uint256 public taxFee = 500; // 5%
uint256 public deployTime;
address payable public marketingWallet;
address payable public buyBackWallet;
// State mappings
mapping(address => bool) private isExcludedFromFee;
mapping(address => uint256) private soldAmounts;
mapping(address => uint256) private sellTimestamps;
// Events
event DebugEvent(string message);
event DebugEvent2(string message, bytes data);
event AccountExcludedFromFee(address account);
event AccountIncludedInFee(address account);
event MarketingWalletUpdated(address newMarketingWallet);
event BuybackWalletUpdated(address newBuybackWallet);
event FeeStateUpdated(bool enabled);
event SwapAttempted(uint256 tokenAmount, uint256 minETHExpected, uint256 ethReceived);
event LiquidityAdded(uint256 tokenAmount, uint256 ethAmount, bool success);
event AntiDumpChecked(address indexed seller, uint256 sellAmount, bool allowed);
event FeesDistributed(uint256 marketingETH, uint256 buybackETH, uint256 liquidityETH, uint256 nftTokens);
event TaxCalculated(
address indexed sender,
address indexed recipient,
uint256 amount,
uint256 taxAmount,
bool isSell,
bool isExcluded
);
/**
* @notice Constructor that initializes the token, mints the max supply, and assigns wallets.
* @param _marketingWallet The wallet address to receive the marketing fee.
* @param _buyBackWallet This wallet address to receive buy back and burn CORE.
*/
constructor(address _marketingWallet, address _buyBackWallet)
ERC20("CocoToken", "COCO")
ERC20Capped(MAX_SUPPLY)
Ownable(msg.sender)
{
if (_marketingWallet == address(0) || _buyBackWallet == address(0)) {
revert CocoToken__InvalidAddress();
}
deployTime = block.timestamp;
marketingWallet = payable(_marketingWallet);
buyBackWallet = payable(_buyBackWallet);
// Deploy reward nft contract
// RewardHolderNFT claimContract = new RewardHolderNFT(address(this));
// nftRewardContract = payable(claimContract);
IUniswapV2Router02 _router = IUniswapV2Router02(0xD99D1c33F9fC3444f8101754aBC46c52416550D1);
uniswapV2Pair = IUniswapV2Factory(_router.factory()).createPair(address(this), _router.WETH());
uniswapV2Router = _router;
isExcludedFromFee[msg.sender] = true;
isExcludedFromFee[address(this)] = true;
isExcludedFromFee[buyBackWallet] = true;
isExcludedFromFee[marketingWallet] = true;
isExcludedFromFee[nftRewardContract] = true;
// Mint max supply
_mint(msg.sender, MAX_SUPPLY);
}
receive() external payable {}
/**
* @param sender The sender's address.
* @param recipient The recipient's address.
* @param amount The amount of tokens to transfer.
* @dev Internal function to handle token transfers.
* @notice Only buy and sell transactions are subject to fees.
*/
function _update(address sender, address recipient, uint256 amount) internal override {
if (amount <= 0) {
revert CocoToken__AmountMustBeGreaterThanZero();
}
uint256 tax = _calculateTax(sender, recipient, amount);
uint256 transferAmount = amount.sub(tax);
if (tax > 0) {
super._update(sender, address(this), tax);
_distributeFees(tax);
}
if (transferAmount > 0) {
super._update(sender, recipient, transferAmount);
}
}
function _calculateTax(address sender, address recipient, uint256 amount) private returns (uint256) {
uint256 taxAmount;
bool isSell = recipient == uniswapV2Pair;
bool excluded = isExcludedFromFee[sender] || isExcludedFromFee[recipient];
if (excluded) {
taxAmount = 0;
emit TaxCalculated(sender, recipient, amount, taxAmount, isSell, excluded);
return taxAmount;
}
if (block.timestamp <= deployTime + TAX_PERIOD) {
taxAmount = amount.mul(taxFee).div(10000);
emit TaxCalculated(sender, recipient, amount, taxAmount, isSell, excluded);
return taxAmount;
}
if (isSell && block.timestamp <= deployTime + ANTI_DUMP_PERIOD) {
if (!_checkAntiDump(sender, amount)) {
revert CocoToken__AntiDumpLimitExceeded();
}
}
taxAmount = 0;
emit TaxCalculated(sender, recipient, amount, taxAmount, isSell, excluded);
return taxAmount;
}
function _checkAntiDump(address seller, uint256 amount) private returns (bool) {
// Reset if last sell was beyond the anti-dump duration
if (block.timestamp > sellTimestamps[seller] + ANTI_DUMP_DURATION) {
soldAmounts[seller] = 0;
sellTimestamps[seller] = block.timestamp;
}
uint256 maxSell = totalSupply().mul(MAX_SELL_PERCENT).div(1000);
uint256 newSoldAmount = soldAmounts[seller] + amount;
if (newSoldAmount > maxSell) {
emit AntiDumpChecked(seller, amount, false);
return false; // Not allowed
}
// Update tracking
soldAmounts[seller] = newSoldAmount;
sellTimestamps[seller] = block.timestamp;
emit AntiDumpChecked(seller, amount, true);
return true; // Allowed
}
function _distributeFees(uint256 taxAmount) private {
uint256 liquidity = taxAmount.mul(1).div(5); // 1%
uint256 marketing = taxAmount.mul(1).div(5); // 1%
uint256 buyback = taxAmount.mul(1).div(5); // 1%
uint256 nftTokens = taxAmount.mul(2).div(5); // 2%
// Send tokens to NFT contract
super._update(address(this), nftRewardContract, nftTokens);
// Process CORE conversions
uint256 totalCOREConversion = marketing.add(buyback).add(liquidity.div(2));
uint256 initialBalance = address(this).balance;
_swapTokensForEth(totalCOREConversion);
uint256 newBalance = address(this).balance.sub(initialBalance);
// Calculate proportional CORE amounts
uint256 marketingCORE = newBalance.mul(marketing).div(totalCOREConversion);
uint256 buybackCORE = newBalance.mul(buyback).div(totalCOREConversion);
uint256 liquidityCORE = newBalance.mul(liquidity.div(2)).div(totalCOREConversion);
// Send CORE to destinations
payable(marketingWallet).transfer(marketingCORE);
payable(buyBackWallet).transfer(buybackCORE);
// Add liquidity with remaining tokens and CORE
if (liquidityCORE > 0) {
_addLiquidity(liquidity.div(2), liquidityCORE);
}
emit FeesDistributed(marketingCORE, buybackCORE, liquidityCORE, nftTokens);
}
function _addLiquidity(uint256 tokenAmount, uint256 COREAmount) private {
_approve(address(this), address(uniswapV2Router), tokenAmount);
try uniswapV2Router.addLiquidityETH{value: COREAmount}(
address(this), tokenAmount, 0, 0, owner(), block.timestamp
) {
emit LiquidityAdded(tokenAmount, COREAmount, true);
} catch {
emit LiquidityAdded(tokenAmount, COREAmount, false);
}
}
function _swapTokensForEth(uint256 tokenAmount) private {
address[] memory path = new address[](2);
path[0] = address(this);
path[1] = uniswapV2Router.WETH();
_approve(address(this), address(uniswapV2Router), tokenAmount);
// Adjust for tax fee to calculate expected received tokens
uint256 adjustedAmount = tokenAmount.mul(10000 - taxFee).div(10000);
uint256 minETH = uniswapV2Router.getAmountsOut(adjustedAmount, path)[1].mul(99).div(100);
uint256 ethBefore = address(this).balance;
uniswapV2Router.swapExactTokensForETHSupportingFeeOnTransferTokens(
tokenAmount, minETH, path, address(this), block.timestamp
);
uint256 ethReceived = address(this).balance - ethBefore;
emit SwapAttempted(tokenAmount, minETH, ethReceived);
}
function contractBalanceSwap() external onlyOwner {
uint256 contractBalance = balanceOf(address(this));
_swapTokensForEth(contractBalance);
}
// Administrative Functions
function excludeFromFee(address account) external onlyOwner {
isExcludedFromFee[account] = true;
emit AccountExcludedFromFee(account);
}
function includeInFee(address account) external onlyOwner {
isExcludedFromFee[account] = false;
emit AccountIncludedInFee(account);
}
function setBuybackWallet(address _buybackWalletAddr) external onlyOwner {
if (_buybackWalletAddr == address(0)) revert CocoToken__AddressCannotBeZeroAddress();
buyBackWallet = payable(_buybackWalletAddr);
emit BuybackWalletUpdated(_buybackWalletAddr);
}
function setMarketingWallet(address _marketingWalletAddr) external onlyOwner {
if (_marketingWalletAddr == address(0)) revert CocoToken__AddressCannotBeZeroAddress();
marketingWallet = payable(_marketingWalletAddr);
emit MarketingWalletUpdated(_marketingWalletAddr);
}
function setFeeState(bool enabled) external onlyOwner {
taxFee = enabled ? 500 : 0;
emit FeeStateUpdated(enabled);
}
function decimals() public view virtual override returns (uint8) {
return 4;
}
function verifyPairCreation() external view returns (bool exists, address pairAddress) {
address token0 = address(this);
address token1 = uniswapV2Router.WETH();
address computedPair = IUniswapV2Factory(uniswapV2Router.factory()).getPair(token0, token1);
return (computedPair != address(0), computedPair);
}
}
Environment
Contract complied and deployed successfully on Remix