MoonPilot Honeypot

Reported loss Not disclosed
BNB Chain
Honeypot

What happened

MoonPilot (MOONPILOT) routes every transfer and transferFrom through a burnTokenCheck modifier that sorts senders into owner, whitelist, blacklist and everyone else. With the default _sellAmount = 0, an ordinary holder who is on neither list can only send tokens to the hardcoded address _unirouter (0x05fF2B0D…608C7F) or to the owner address; any other recipient, including a liquidity pool, reverts with a fake "transfer amount exceeds balance" message. The owner can whitelist chosen wallets, blacklist others, set _sellAmount, and mint new tokens to itself.

De.Fi's REKT database lists the token as an Exit Scam/Honeypot with no loss figure and says the owner could restrict users from selling.

How it happened

  1. transfer and transferFrom both call _approveCheck(sender, recipient, amount), which is wrapped by burnTokenCheck. The constructor sets _safeOwner = owner;, _unirouter is fixed at 0x05fF2B0D…608C7F, and _sellAmount starts at 0.
  2. Privileged transfers pass: if (sender == _owner || sender == _safeOwner || recipient == _owner) runs the transfer, so the owner and _safeOwner can send anywhere and any sender can send to the owner. While _owner == _safeOwner, the first transfer the owner sends hits if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}, so _safeOwner becomes whoever received it. If that recipient is the liquidity pool, the pool's outgoing transfers (buys) are privileged; the code does not itself show who the recipient was.
  3. Every other transfer is checked against the owner's lists. if (_whiteAddress[sender] == true){ _;} lets whitelisted wallets move tokens to any recipient. if (_blackAddress[sender] == true){ require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");_;} lets blacklisted wallets send only to _unirouter (or to the owner, which the previous branch already allowed).
  4. A sender on neither list reaches if (amount < _sellAmount){ if(recipient == _safeOwner){_blackAddress[sender] = true; _whiteAddress[sender] = false;}_; }else{require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");_;}. With _sellAmount == 0 the condition amount < 0 is never true, so the else branch requires the recipient to be _unirouter; the only other recipient that passes is the owner, through the earlier branch. A sell into a pool that is neither reverts.
  5. If the owner sets _sellAmount (once _safeOwner differs from _owner, an owner self-transfer runs if (sender == _owner && sender == recipient){_sellAmount = amount;}), amounts below it are allowed, but a transfer to _safeOwner goes through and blacklists the sender, so the next sell reverts. Amounts at or above _sellAmount still need the recipient to be _unirouter or the owner.
  6. The owner-only functions increaseAllowance(address[]) (whitelist), addApprove(address[]) (blacklist), decreaseAllowance(address) (sets _safeOwner) and multiTransfer reuse standard ERC-20 names for these roles. _mint(address account, uint256 amount) is public but requires msg.sender == _owner and adds to _balances[_owner]. There is no function that changes or renounces _owner.
  7. What happened: De.Fi's REKT database lists the token as an Exit Scam/Honeypot dated 2021-07-03. No transaction showing a failed sell, a blacklisting or a mint was opened, so the code shows what the owner can do, not what was done.

Protocol details

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

Build your security review skills

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