Monkey Token Honeypot
What happened
Monkey Token (MONKEY) routes every transfer and transferFrom through a burnTokenCheck modifier that lets the owner, a _safeOwner address and whitelisted holders move tokens freely, while any other holder can only send tokens to the hard-coded _unirouter address (or to the owner), and otherwise hits a revert that reads "transfer amount exceeds balance". The owner controls the whitelist, the blacklist and _safeOwner, and can mint more supply. De.Fi's REKT database lists the token as an exit scam/honeypot dated 2021-04-26 and says the contract owner could disable the transfer function, which restricted users in selling their tokens.
No transaction was opened, so the code shows what the owner can do, not what was done.
How it happened
- The constructor stores the owner and the same address as
_safeOwner, then mints the initial supply to the owner (_owner = owner; _safeOwner = owner; _mint(_owner, initialSupply*(10**18));). transferandtransferFromboth call_approveCheck(sender, recipient, amount), which runs underburnTokenCheck(sender,recipient,amount). FortransferFrom,senderis the token holder, not the router.- The modifier's first branch is
if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}: while owner and_safeOwnerare the same address, the owner's next outgoing transfer sets_safeOwnerto its recipient. After that, transfers from the owner or_safeOwner, or to the owner, always pass (if (sender == _owner || sender == _safeOwner || recipient == _owner)). - Whitelisted senders (
_whiteAddress) pass. For every other sender the code reachesrequire((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");, so the transfer succeeds only if the recipient is the hard-coded_unirouter0x05fF2B0D…608C7F; any other recipient reverts although the balance is sufficient. Blacklisted senders hit the samerequire. The code does not show whether that address matches the liquidity pair or any router actually used. - The exception for ordinary holders is
if (amount < _sellAmount)._sellAmountstarts at 0 (uint256 private _sellAmount = 0;) and is set only when the owner sends tokens to itself while_safeOwneris a different address (if (sender == _owner && sender == recipient){_sellAmount = amount;}). Below that amount a holder's transfer passes, and if the recipient is_safeOwnerthe holder is added to the blacklist (_blackAddress[sender] = true; _whiteAddress[sender] = false;). - The owner-only setters have misleading names:
increaseAllowance(address[])whitelists and un-blacklists,addApprove(address[])blacklists and un-whitelists, anddecreaseAllowance(address safeOwner)replaces_safeOwner.multiTransferalso whitelists the firstapprovecountreceivers and approves_unirouterfor the maximum amount on their behalf. function _mint(address account, uint256 amount) publicrequiresmsg.sender == _ownerand adds the amount to_totalSupplyand to_balances[_owner], so the owner can mint further supply to itself. The code does not show whether this was used.- A comment and the constants
releaseTime = 6and_lockedtime = '262800 > 6'claim the liquidity is locked for 6 months, but no code in the file reads them or restricts liquidity._owneris assigned only in the constructor and there is no ownership-transfer or renounce function.
Protocol details
Evidence
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.