Anatomy of a memecoin honeypot
I found a memecoin contract in an old repo — DarkPepe (DEPE), the copy-pasted “token with a blacklist” template that’s been deployed thousands of times. It compiles, it trades, it has a cute name. It also hands the deployer a button that freezes your tokens after you buy. Here is that button, in full:
mapping(address => bool) public blacklists;
function blacklist(address _address, bool _isBlacklisting) external onlyOwner {
blacklists[_address] = _isBlacklisting;
}
function _beforeTokenTransfer(address from, address to, uint256 amount) override internal {
require(!blacklists[to] && !blacklists[from], "Blacklisted");
...
}
Every transfer runs _beforeTokenTransfer first. So the moment the owner calls blacklist(you, true), every transfer touching your address reverts. You can’t sell. You can’t move it to another wallet. You can’t even receive more. Your bag is frozen, on-chain, at the deployer’s discretion. You buy at the top, they flip the switch, and the exit is gone. That’s a honeypot — and it’s not hidden in assembly or a proxy, it’s eleven lines of plain Solidity.
There are two more levers in the same contract:
// in _beforeTokenTransfer:
if (uniswapV2Pair == address(0)) {
require(from == owner() || to == owner(), "trading is not started");
return;
}
if (limited && from == uniswapV2Pair) {
require(balanceOf(to) + amount <= maxHoldingAmount && ... >= minHoldingAmount, "Forbid");
}
The first means that until the owner sets the pair, only the owner can move tokens — they decide if and when trading ever opens, and can simply never open it. The second lets setRule(...) cap how much anyone can buy, down to zero. Mint 100% of supply to yourself, retain ownership, and you hold every lever.
blacklist mapping checked on every transfer. A token nobody can rug simply has no privileged function to call.Proving it, and the antidote
I didn’t want to just assert this, so I wrote three Foundry tests against the real DarkPepe bytecode. One buys in as a holder, has the owner blacklist them, and asserts the next sell reverts with "Blacklisted" — and that they can no longer receive either. One shows that before trading is “started,” a non-owner transfer reverts. They pass. The honeypot is exactly as advertised.
Then the antidote, also tested: SafeToken — a deliberately un-ruggable ERC-20. Fixed supply minted once, no owner, no blacklist, no transfer gate, no mint, no holding caps. There is no function any party can call to freeze or seize a balance, so the test that tries to trap a holder has nothing to call. That’s the whole point: safety here isn’t a feature you add, it’s privilege you remove.
The takeaway
This is the most actionable post I’ll write, so here it is plainly: before you ape a token, read two things — its _beforeTokenTransfer / _update hook, and every onlyOwner function. A blacklists mapping, a tradingEnabled flag, a setRule that gates the pair, a _mint the owner can still call — any one of them means the deployer can stop you selling or dilute you at will. The contract will look friendly and trade fine right up until it doesn’t. The exit being open today doesn’t mean it’s open tomorrow if someone else holds the key.
The audit, the proofs, and the trap-free reference are at github.com/0xSoftBoi/01.