Nagasaki Shiba Honeypot

Reported loss Not disclosed
Ethereum
Honeypot

What happened

De.Fi's REKT database lists Nagasaki Shiba (NIBBA, Ethereum contract 0x2f1b026a…7b230e) as an exit scam/honeypot, noting that the contract owner could disable the transfer function, which restricted users in selling their tokens. In the verified source, every transfer runs through a burnTokenCheck modifier. While _sellAmount is still 0, and unless the sender is _owner, _safeOwner, a whitelisted address, or the recipient is _owner, a transfer reverts when its recipient is not the hard-coded _unirouter address, and a sell to a liquidity pool has a different recipient.

The owner-only functions increaseAllowance, addApprove and decreaseAllowance change the whitelist, the blacklist and _safeOwner, so the owner can decide who may sell. De.Fi gives no loss figure, and the code alone does not show who was able to sell.

How it happened

  1. The constructor sets _owner = owner; and _safeOwner = owner;, then calls _mint(owner, initialSupply*(10**18)); followed by _mint(0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B, initialSupply*(10**18));. _mint requires msg.sender == _owner, so for the deployment to succeed _owner must be the deployer 0xc9634Bfa…86F825.
  2. _mint always credits _balances[_owner] = _balances[_owner].add(amount); and only the emitted Transfer event names account. Both constructor mints therefore land in the owner's balance, while the second Transfer event names 0xAb5801a7…9aeC9B, so an event-based holder view and balanceOf can disagree.
  3. transfer and transferFrom both call _approveCheck(sender, recipient, amount), which carries the modifier burnTokenCheck(sender,recipient,amount). In transferFrom, sender is the holder whose tokens move, not the router.
  4. Branch 1: if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;}. While _safeOwner still equals _owner, the first transfer from the owner passes and re-points _safeOwner to that transfer's recipient. Branch 2: if (sender == _owner || sender == _safeOwner || recipient == _owner) always passes; inside it, if (sender == _owner && sender == recipient){_sellAmount = amount;} lets an owner self-transfer set _sellAmount once _safeOwner differs from _owner.
  5. Branch 3: if _whiteAddress[sender] == true the transfer passes. Branch 4: if _blackAddress[sender] == true, the line require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance"); applies. sender == _safeOwner was already handled in branch 2, so in practice only a transfer to _unirouter passes.
  6. Branch 5, every other sender: if (amount < _sellAmount) the transfer passes, and if recipient == _safeOwner the sender is moved to the blacklist (_blackAddress[sender] = true; _whiteAddress[sender] = false;). Otherwise the same require as branch 4 runs. _sellAmount starts at 0 (uint256 private _sellAmount = 0;), so amount < _sellAmount is false for every amount until the owner sets it.
  7. A holder who is not whitelisted and sells through a router sends tokens to a pool address, not to _unirouter, so the require reverts with the message ERC20: transfer amount exceeds balance, even though the balance is not touched at that point. Buys are transfers from the pool, so they pass only if the pool is _safeOwner (for example when the first owner transfer went to the pool), is whitelisted, or amount < _sellAmount.
  8. The owner controls the lists: increaseAllowance(address[] memory receivers) whitelists and un-blacklists, addApprove(address[] memory receivers) blacklists and un-whitelists, and decreaseAllowance(address safeOwner) sets _safeOwner. multiTransfer sends tokens and whitelists the first approvecount receivers. No function reassigns _owner, and _mint stays public but owner-only.

Protocol details

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

Build your security review skills

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