EA Sports Token Honeypot

Reported loss Not disclosed
Ethereum
Honeypot

What happened

EA Sports Token (EA) is an Ethereum token, contract 0x5ea43cbc…1962e8, whose every transfer and transferFrom runs through a burnTokenCheck modifier. Unless the sender is the owner, the _safeOwner address or a whitelisted address, the transfer reverts unless its recipient is the Uniswap V2 router address 0x7a250d56…F2488D or the owner, and the revert message imitates a balance error. A sell through the router moves tokens to the liquidity pair, so it reverts for ordinary holders.

The owner controls the whitelist, a blacklist, _safeOwner and a public _mint. De.Fi's REKT database lists EA as an exit scam/honeypot in which the contract owner could disable the transfer function, restricting users from selling their tokens. The sources opened here do not show which address _safeOwner held on-chain or any specific holder's failed sell.

How it happened

  1. The constructor sets _owner = owner; and _safeOwner = owner;. Because the constructor's _mint calls require msg.sender == _owner and the deployment succeeded, the owner is the deployer, 0x862e7b37…051360.
  2. The constructor calls _mint ten times. Each call credits _balances[_owner] whatever account is, while its Transfer event names account: five events name 0x862e7b37…051360 and five name 0xeaeAEAea…EAeAeA. All ten amounts are therefore held by the owner, and event logs do not match balances.
  3. Every transfer path calls _approveCheck, which carries the burnTokenCheck modifier. Its first branch, if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}, moves _safeOwner to the recipient of the owner's first outgoing transfer. If that transfer is the liquidity deposit (transferFrom(owner, pair, ...)), the pair becomes _safeOwner; the code alone does not show the on-chain value. The owner, _safeOwner, and any transfer to the owner then pass: if (sender == _owner || sender == _safeOwner || recipient == _owner).
  4. A holder who is not whitelisted or blacklisted reaches the last branch. _sellAmount defaults to 0, so amount < _sellAmount is false and the code runs require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");. A router sell is transferFrom(holder, pair, amount): the sender is not _safeOwner and the recipient is the pair, not _unirouter, so it reverts and the holder's balance is not debited. Wallet-to-wallet transfers revert the same way; only transfers to _unirouter or the owner pass.
  5. The owner can change who passes: increaseAllowance (despite its name) sets _whiteAddress to true and _blackAddress to false, addApprove does the reverse, decreaseAllowance(address safeOwner) replaces _safeOwner, and multiTransfer whitelists its first approvecount recipients and gives each a maximum allowance to _unirouter. All are require(msg.sender == _owner, "!owner"). _mint is public and owner-only, so the owner can mint more at any time.
  6. A transfer from the owner to the owner (when _owner != _safeOwner) runs _sellAmount = amount;. With _sellAmount above zero, a non-listed holder's transfer of a smaller amount passes, and if the recipient is _safeOwner the holder is added by _blackAddress[sender] = true;, after which only transfers to _unirouter pass. Transfers of _sellAmount or more still revert.

Protocol details

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

Build your security review skills

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