Monkey Token Honeypot

Reported loss Not disclosed
Ethereum
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

  1. 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));).
  2. transfer and transferFrom both call _approveCheck(sender, recipient, amount), which runs under burnTokenCheck(sender,recipient,amount). For transferFrom, sender is the token holder, not the router.
  3. The modifier's first branch is if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}: while owner and _safeOwner are the same address, the owner's next outgoing transfer sets _safeOwner to its recipient. After that, transfers from the owner or _safeOwner, or to the owner, always pass (if (sender == _owner || sender == _safeOwner || recipient == _owner)).
  4. Whitelisted senders (_whiteAddress) pass. For every other sender the code reaches require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");, so the transfer succeeds only if the recipient is the hard-coded _unirouter 0x05fF2B0D…608C7F; any other recipient reverts although the balance is sufficient. Blacklisted senders hit the same require. The code does not show whether that address matches the liquidity pair or any router actually used.
  5. The exception for ordinary holders is if (amount < _sellAmount). _sellAmount starts at 0 (uint256 private _sellAmount = 0;) and is set only when the owner sends tokens to itself while _safeOwner is a different address (if (sender == _owner && sender == recipient){_sellAmount = amount;}). Below that amount a holder's transfer passes, and if the recipient is _safeOwner the holder is added to the blacklist (_blackAddress[sender] = true; _whiteAddress[sender] = false;).
  6. The owner-only setters have misleading names: increaseAllowance(address[]) whitelists and un-blacklists, addApprove(address[]) blacklists and un-whitelists, and decreaseAllowance(address safeOwner) replaces _safeOwner. multiTransfer also whitelists the first approvecount receivers and approves _unirouter for the maximum amount on their behalf.
  7. function _mint(address account, uint256 amount) public requires msg.sender == _owner and adds the amount to _totalSupply and to _balances[_owner], so the owner can mint further supply to itself. The code does not show whether this was used.
  8. A comment and the constants releaseTime = 6 and _lockedtime = '262800 > 6' claim the liquidity is locked for 6 months, but no code in the file reads them or restricts liquidity. _owner is assigned only in the constructor and there is no ownership-transfer or renounce function.

Protocol details

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

Build your security review skills

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