CosmoKitty Honeypot

Reported loss Not disclosed
BNB Chain
Honeypot

What happened

CosmoKitty (COSMO) is a BNB Smart Chain reflection token whose _transfer function can be shut for everyone except the owner. tradingEnabled starts as false, setTradingEnabled(bool) lets the owner switch it off again, and while it is off every transfer that does not involve the owner address reverts, including sells into the pair. The owner or the separate janitor role can also call removeAccount on any address, which permanently blocks that address from sending or receiving, and can set the maximum transaction size with no lower bound.

De.Fi's REKT database lists the token as an Exit Scam/Honeypot dated 2021-06-17 with no loss amount; the BscScan comments page it cites could not be read.

How it happened

  1. The constructor creates the PancakeSwap pair, gives the whole supply to the deployer, and tradingEnabled is declared false (bool public tradingEnabled = false; // To avoid snipers).
  2. In _transfer, any transfer where neither side is owner() must pass require(tradingEnabled, "Trading is not enabled");. Buys (from the pair) and sells (to the pair) both fall under this check, so while the flag is false ordinary holders cannot trade.
  3. The flag is not a one-way launch latch: setTradingEnabled(bool _enabled) public onlyOwner can set it back to false at any time, and enableTrading() only sets it to true.
  4. Independently of that flag, removeAccount(address account) is onlyJanitorOrOwner and sets _isRemoved[account] = true;. Line one of _transfer is require(!_isRemoved[from] && !_isRemoved[to], "Account removed!");, and no function in the source ever resets _isRemoved. A removed holder cannot send, and if the pair address were removed, all buys and sells would revert.
  5. setMaxTxPercent(uint256 maxTxPercent) is also onlyJanitorOrOwner and sets _maxTxAmount = _tTotal.mul(maxTxPercent).div(10**2); with no minimum. _transfer requires amount <= _maxTxAmount for non-owner transfers when doSwapForRouter is true or neither side is the router, and also requires amount > 0, so a value of 0 would make those transfers revert.
  6. The three fee setters, setTaxFeePromille, setLiquidityFeePromille and setRewardFeePromille, are onlyOwner and have no cap. The defaults are 10, 40 and 50 per mille (10% in total). In _getRValues the recipient receives rAmount.sub(rFee).sub(rLiquidity) while the sender is debited the full rAmount, so the three fees together at 1000 per mille would leave the recipient with nothing, and a total above 1000 would revert on the subtraction.
  7. Who holds these roles: the supplied metadata shows owner() reading as the zero address on 2026-09-30, but that is a current-state read and the code offers both renounceOwnership() and lock(uint256 time) (which stores _previousOwner so unlock() can restore it), so the source does not show which happened or whether the owner powers are permanently gone. The janitor role is separate (lockJanitor and unlockJanitor use the same save-and-restore pattern) and the current janitor was not read.
  8. What the code allows and what happened are separate: no transaction was opened showing setTradingEnabled(false), removeAccount, or setMaxTxPercent being called, and the BscScan comments page cited by De.Fi was not retrievable, so any holder reports could not be checked.

Protocol details

Classification Token
Protocol Type Exit Scam/Honeypot
Affected asset / contract COSMO

Build your security review skills

Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.