Fear NFTs Honeypot
What happened
De.Fi's REKT database lists Fear NFTs (FEAR) on BNB Smart Chain as an exit scam / honeypot, noting that the owner could disable the transfer function and so restrict users from selling their tokens. The verified source implements this through a burnTokenCheck modifier on every transfer: the owner and a _safeOwner address pass freely, whitelisted addresses pass, and any other sender can send tokens only to the hard-coded _unirouter address unless the transfer amount is below an owner-set _sellAmount, which starts at zero. The owner can also whitelist or blacklist addresses, change _safeOwner, and mint new tokens to itself.
The code does not show whether any of these powers were used on FEAR holders.
How it happened
- The constructor sets
_ownerand_safeOwnerto the same address and mintsinitialSupply*(10**18)to it. The contract has no function that changes_owner. - Every
transferandtransferFromruns through_approveCheck, which carries the modifierburnTokenCheck(sender,recipient,amount). - First branch:
if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}. While the two are equal, the first transfer sent by the owner makes its recipient the_safeOwner. The code does not show which address that was. - Privileged senders:
if (sender == _owner || sender == _safeOwner || recipient == _owner)lets the transfer run. If the owner sends to itself,_sellAmount = amountrecords a threshold. - Whitelisted senders (
_whiteAddress[sender] == true) also pass. Blacklisted senders hitrequire((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance"), so they can only send to_unirouter. - Every other sender falls to
if (amount < _sellAmount){ if(recipient == _safeOwner){_blackAddress[sender] = true; ...} _; }else{require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");_;}._sellAmountis initialised to0, so until the owner sets a threshold theamount < _sellAmounttest is false for every amount, and an ordinary holder's transfer reverts unless the recipient is_unirouter. If a threshold is set, smaller transfers pass, and a transfer to_safeOwnerbelow the threshold succeeds but adds the sender to_blackAddress. - The owner controls who is exempt:
increaseAllowance(address[])whitelists and un-blacklists,addApprove(address[])blacklists and un-whitelists,decreaseAllowance(address)replaces_safeOwner, andmultiTransferwhitelists the firstapprovecountreceivers and gives them a maximum allowance to_unirouter. All are gated byrequire(msg.sender == _owner, "!owner"). The revert message reads as a balance error, not a restriction. function _mint(address account, uint256 amount) publicis owner-gated and credits_balances[_owner], so the owner can add supply to its own balance after deployment. No transaction showing any of these functions being called was opened.
Protocol details
Build your security review skills
Work through hands-on labs covering real exploit mechanics, review techniques, and defensive patterns.