CosmoKitty 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
- The constructor creates the PancakeSwap pair, gives the whole supply to the deployer, and
tradingEnabledis declaredfalse(bool public tradingEnabled = false; // To avoid snipers). - In
_transfer, any transfer where neither side isowner()must passrequire(tradingEnabled, "Trading is not enabled");. Buys (from the pair) and sells (to the pair) both fall under this check, so while the flag isfalseordinary holders cannot trade. - The flag is not a one-way launch latch:
setTradingEnabled(bool _enabled) public onlyOwnercan set it back tofalseat any time, andenableTrading()only sets it totrue. - Independently of that flag,
removeAccount(address account)isonlyJanitorOrOwnerand sets_isRemoved[account] = true;. Line one of_transferisrequire(!_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. setMaxTxPercent(uint256 maxTxPercent)is alsoonlyJanitorOrOwnerand sets_maxTxAmount = _tTotal.mul(maxTxPercent).div(10**2);with no minimum._transferrequiresamount <= _maxTxAmountfor non-owner transfers whendoSwapForRouteris true or neither side is the router, and also requiresamount > 0, so a value of 0 would make those transfers revert.- The three fee setters,
setTaxFeePromille,setLiquidityFeePromilleandsetRewardFeePromille, areonlyOwnerand have no cap. The defaults are 10, 40 and 50 per mille (10% in total). In_getRValuesthe recipient receivesrAmount.sub(rFee).sub(rLiquidity)while the sender is debited the fullrAmount, 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. - 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 bothrenounceOwnership()andlock(uint256 time)(which stores_previousOwnersounlock()can restore it), so the source does not show which happened or whether the owner powers are permanently gone. The janitor role is separate (lockJanitorandunlockJanitoruse the same save-and-restore pattern) and the current janitor was not read. - What the code allows and what happened are separate: no transaction was opened showing
setTradingEnabled(false),removeAccount, orsetMaxTxPercentbeing called, and the BscScan comments page cited by De.Fi was not retrievable, so any holder reports could not be checked.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.