Most honeypot checkers test tokens by simulating a buy and sell transaction. If the sell reverts, the token is flagged. Simple enough — but sophisticated scammers figured this out years ago. Today’s honeypot contracts are built to pass simulations while still trapping real buyers.
This article breaks down the actual Solidity patterns used to build these traps, how each one works at the bytecode level, and the signals DexScanr uses to surface them before you get caught.
This is a technical deep dive aimed at traders and developers who want to understand exactly how honeypot code works. No Solidity experience required — each pattern is explained from first principles.
Pattern 1: Transfer Function Override
The most common honeypot pattern overrides the internal _transfer function to block sells selectively. ERC-20 tokens use _transfer for every movement of tokens — buys, sells, and wallet transfers. A scammer can embed logic that checks whether tokens are moving to the liquidity pool (a sell) and revert only in that case.
// Looks like a normal transfer function function _transfer(address from, address to, uint256 amount) internal override { if (to == uniswapPair) { require(_canSell[from], "Transfer failed"); } super._transfer(from, to, amount); }
The trap: _canSell is a mapping that the owner controls. It defaults to false for all buyers. The owner’s wallet has _canSell[owner] = true, so the owner can exit freely. Every other holder is permanently locked.
Simulation tools that call balanceOf after a simulated sell see a non-zero balance and assume the sell worked — but the actual transaction reverts on-chain for real wallets.
Any contract with a _canSell, _whitelist, or tradingEnabled mapping that is checked inside _transfer should be treated as a honeypot until proven otherwise.
Pattern 2: tx.origin Gating
tx.origin in Solidity always refers to the original externally owned account (EOA) that initiated the transaction — not the contract calling the function. This is different from msg.sender, which changes as calls pass through contracts.
Honeypot authors exploit this to detect simulation environments. Most simulation tools call contracts directly from a script, so tx.origin is a known simulation address. The honeypot checks for this and allows the simulated sell to succeed while blocking real sells.
function _transfer(address from, address to, uint256 amount) internal override {
if (tx.origin == simulatorAddress) {
super._transfer(from, to, amount);
return;
}
// Real users hit this path
require(_allowedToSell[from], "Transfer failed");
super._transfer(from, to, amount);
}
This is particularly dangerous because the token will show as safe on every checker that relies purely on transaction simulation.
“The simulation says safe. The blockchain says trapped. tx.origin gating lives in that gap.”
Pattern 3: Modifier Short-Circuit
Solidity modifiers run before the function body executes. A honeypot can place the revert condition inside a modifier so it’s invisible when reading just the function logic.
modifier canTransfer(address sender) { require(block.number > _lockBlock[sender], "Locked"); _; } function transfer(address to, uint256 amount) public canTransfer(msg.sender) returns (bool) { // Looks clean here — trap is in the modifier return _transfer(msg.sender, to, amount); }
In this pattern, _lockBlock[sender] is set to an astronomically high block number for all buyers. The owner sets their own _lockBlock to zero. Buyers are permanently locked without any explicit blacklist or sell restriction visible in the main function body.
Pattern 4: Fee Manipulation to 100%
Many legitimate tokens have configurable sell fees. Honeypots abuse this by allowing the owner to set the sell tax to 100% at any time — or setting it to 99% from deployment so no meaningful amount ever leaves a wallet.
uint256 public sellFee = 99; // 99% — effectively locked function setSellFee(uint256 fee) external onlyOwner { require(fee <= 100, "Invalid"); // allows 100% sellFee = fee; } function _transfer(address from, address to, uint256 amount) internal override { if (to == uniswapPair) { uint256 feeAmount = amount * sellFee / 100; super._transfer(from, address(this), feeAmount); amount -= feeAmount; } super._transfer(from, to, amount); }
A 99% sell fee means a buyer who spends 1 ETH on a token receives back 0.01 ETH worth of tokens on exit. The rest goes to the contract. DexScanr flags any token with a sell tax above 10% as high risk.
Pattern 5: Hidden Blacklist via Internal Mapping
This pattern pre-populates a blacklist mapping at deployment, blocking specific addresses from selling. Unlike obvious blacklist functions, this version adds addresses silently in the constructor or in a separate private function called once after deployment.
mapping(address => bool) private _blacklisted;
constructor() {
// Owner is not blacklisted — everyone else will be added
_blacklisted[owner()] = false;
}
// Called once after deployment — not visible in constructor
function _initBlacklist(address[] calldata wallets) external onlyOwner {
for (uint i = 0; i < wallets.length; i++) {
_blacklisted[wallets[i]] = true;
}
}
function _transfer(address from, address to, uint256 amount) internal override {
require(!_blacklisted[from], "Transfer failed");
super._transfer(from, to, amount);
}
Because the blacklist is populated after deployment, block explorers show a clean constructor. Buyers who purchase before the blacklist function is called may be able to sell briefly — then the owner calls _initBlacklist with all buyer addresses and exits.
// SCAN BEFORE YOU BUY
DexScanr checks for all five patterns above before you commit a single dollar.
Pattern 6: Proxy Contract Swap Trap
Proxy contracts separate logic from storage. A honeypot uses this to deploy a token that looks safe initially — the logic contract passes all checks. After liquidity builds and buyers are in, the owner upgrades the logic contract to one that blocks sells.
The token address stays the same. The liquidity is real. But the rules change underneath buyers without any on-chain warning visible to holders. This is covered in depth in our Proxy Contract Risks article.
Proxy upgrades happen silently. There is no event that most wallets surface to holders. By the time buyers notice, the owner has already exited.
How DexScanr Detects These Patterns
Static simulation alone misses patterns 1, 2, and 6 entirely. DexScanr combines multiple detection layers:
- Bytecode analysis — checks for known dangerous opcode sequences without relying on simulation
- Ownership checks — flags contracts where owner privileges include fee changes, blacklisting, or transfer control
- Tax extraction — reads actual buy and sell tax values from contract state, not from simulation output
- Proxy detection — identifies EIP-1967 and custom proxy patterns and flags upgradeable contracts
- Liquidity lock verification — checks whether LP tokens are locked or held by the deployer
No single check catches everything. The combination of 12+ checks running in parallel is what catches the patterns that fool individual tools.
What To Do When You See These Red Flags
Before buying any token: check the contract source on Etherscan, look for owner-controlled mappings in _transfer, verify sell tax is under 5%, and confirm LP is locked. If any of these checks fails, walk away.
If a token shows any of the patterns above — even one — treat it as a honeypot. Scammers rarely use just one pattern. They layer them so that if one is detected, others remain hidden. A contract with a suspicious modifier and a configurable fee function is almost certainly a trap.
Use DexScanr on every token before you buy. The extension runs directly on DexScreener so you see the risk score before you ever open a chart. It takes five seconds and it’s free.
// STOP GETTING TRAPPED
12+ risk checks. 5 chains. Results in under 5 seconds. Free to install.