Jigstack.org Honeypot

Reported loss Not disclosed
Ethereum
Honeypot

What happened

De.Fi's REKT database lists Jigstack.org (STAK) on Ethereum as an Exit Scam/Honeypot, saying the contract owner could disable the transfer function, which restricted users in selling their tokens. In the verified source, every transfer and transferFrom runs through the burnTokenCheck modifier. For a holder who is not the owner, the _safeOwner or whitelisted, that modifier reverts any transfer unless the recipient is the hard-coded _unirouter address (or the owner), because the only other way through, amount < _sellAmount, is closed while _sellAmount still has its initial value of 0.

The owner also has setters that decide who is exempt.

How it happened

  1. The constructor sets _owner = owner; and _safeOwner = owner; and mints the initial supply to the owner, so the owner starts as the only privileged sender.
  2. The first time the owner sends tokens anywhere, if (_owner == _safeOwner && sender == _owner){_safeOwner = recipient;_;} re-points _safeOwner at that recipient. From then on _safeOwner passes the check as a sender (sender == _owner || sender == _safeOwner || recipient == _owner). The code does not show who that recipient was.
  3. A holder who is not the owner, _safeOwner or whitelisted reaches if (amount < _sellAmount){ ... }else{require((sender == _safeOwner)||(recipient == _unirouter), "ERC20: transfer amount exceeds balance");_;}. _sellAmount is initialised to 0, so amount < _sellAmount is false for every amount and the require reverts unless recipient == _unirouter. A transfer to the owner still passes through the recipient == _owner branch. A reverted transfer leaves the holder's balance unchanged. A sell that delivers tokens to any other address, such as a pair contract that is not _unirouter, therefore fails; the code does not show what address the pair has.
  4. The owner decides who is exempt. increaseAllowance(address[] receivers) sets _whiteAddress[...] = true and clears the blacklist, addApprove(address[] receivers) sets _blackAddress[...] = true and clears the whitelist, and decreaseAllowance(address safeOwner) sets _safeOwner, all behind require(msg.sender == _owner, "!owner") despite their ERC-20-style names. Whitelisted senders always pass; blacklisted senders pass only when recipient == _unirouter. multiTransfer also whitelists its first approvecount receivers and gives each a maximum allowance for _unirouter.
  5. When _safeOwner already differs from _owner, an owner transfer to itself (if (sender == _owner && sender == recipient){_sellAmount = amount;}) sets _sellAmount. Once it is non-zero, a non-exempt holder can send amounts below it to any recipient, but doing so to _safeOwner runs _blackAddress[sender] = true; _whiteAddress[sender] = false;, which moves the sender onto the blacklist while that transfer still completes.
  6. _mint is public and only checks msg.sender == _owner; it adds the amount to _totalSupply and to _balances[_owner] with no cap. _owner is assigned only in the constructor, so the code has no function that changes or renounces it.
  7. What the code allows is separate from what happened. De.Fi's REKT database lists the token as an Exit Scam/Honeypot; the Token Sniffer proof link returned 404, so no transaction showing a blocked sell, a blacklist entry or a mint was checked.

Protocol details

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

Build your security review skills

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