DeFi Security Alliance

Token risk

Honeypot Token Detection: How Checkers Work and the Four Contract Patterns That Beat Them

Every honeypot token detection service answers with a simulated trade, not with a statement about the token. A checker buys and sells against one live pair at one block, then reports what happened to that trade. This guide takes the method apart, names the four contract patterns that survive it, and reports our own run of 150 tokens through two public checkers on September 4, 2026.

Glass honey jar with a one way funnel lid and a coin trapped inside, illustrating a honeypot token that lets buyers in but never out.

Key facts

Clean is not safe
Of 89 tokens that honeypot.is and GoPlus both called clean, 49 carried at least one owner gated GoPlus capability: is_mintable on 20, is_blacklisted on 11, slippage_modifiable on 11, owner_change_balance on 10
The SQUID control
The Squid Game token of 2021 passes a honeypot simulation today, with isHoneypot false, all taxes zero and 3,098 of 3,098 holder sells successful. GoPlus returns a full result for it with no is_honeypot key at all
What honeypot.is measures
One simulated buy and one simulated sell against one live pair on Ethereum, BNB Smart Chain or Base, with the router, the pair and the chain named in the response and no API key required
Capability is not verdict
Live USDT returns is_honeypot 0 while is_blacklisted, is_mintable, owner_change_balance, slippage_modifiable and transfer_pausable are all set to 1
Static analysis baseline
HoneyBadger analyzed more than 2 million smart contracts, flagged 690 honeypots, confirmed 87 percent of them by hand and found 240 victims with over $90,000 in profit, USENIX Security 2019

How buy-sell simulation works

A honeypot token is one that takes money on the way in and refuses to let it out. The buy confirms. Selling reverts, or costs more than the position is worth, or quietly routes the proceeds somewhere else. Checkers answer that question the direct way, by running the trade before you do.

The method fits into one sentence, and the vendor supplies it: the service "simulates a buy and a sell transaction to determine if the token is a honeypot or not." The honeypot.is homepage claims "extra checks to minimize false results" and names three chains, Ethereum, BNB Smart Chain and Base. Arbitrum is not on that list.

Call the v2 API yourself and the shape of the answer becomes clear. No key is needed, and the docs say so outright: "API Key system is not yet implemented." simulationSuccess is its own boolean with simulationError beside it, which keeps the sell failed apart from we could not run the test. Verdicts sit in honeypotResult.isHoneypot with a honeypotReason string. Trade economics live in a different object: simulationResult.buyTax, sellTax, transferTax, maxBuy and maxSell. One warning repeats across several of those objects in the reference: "This field is not guaranteed to be present".

The response also names what the trade ran against: our USDT call came back carrying the router, the exact pair and the chain. One pair. One block. That is the entire scope of the measurement, and every claim built on it inherits that scope.

Two other signals ride along. holderAnalysis is not a simulation at all, it is a retrospective count of holders, successful, failed, siphoned and averageTax, assembled from sells that real wallets already made. contractCode reports openSource, rootOpenSource, isProxy and hasProxyCalls as separate booleans, which is the vendor conceding that source verification and proxy status get checked outside the trade.

GoPlus Security takes the other road. Instead of trading against a DEX pair it reads the contract and the ownership graph, then returns roughly thirty string flags. Vendor wording on the headline field is careful: a honeypot result "means that the token maybe cannot be sold because of the token contract's function". Maybe. RugDoc, which simulates too, is blunt about its own status: "This is an experimental service, provided as-is."

Owner-gated blacklists applied after launch

CertiK's write-up of lists the blacklist first: "Once a victim purchases the scam token, they are added to a blacklist." Selling checks the wallet against that list and reverts. None of it exists at launch, which is the point. The list starts empty.

Disguise matters as much as mechanism. CertiK records the maintaining function under the name ApprovalForAll, with entries kept in a list called _snapshot. A reader skimming verified source for the word blacklist finds nothing.

mapping(address => bool) private _snapshot;

function ApprovalForAll(address holder, bool blocked) external onlyOwner {
    _snapshot[holder] = blocked;
}

function _transfer(address from, address to, uint256 amount) internal {
        require(!_snapshot[from], "transfer failed");
    // ... normal balance update
}
Owner gated blacklist checked inside the transfer path. Illustrative, not from a deployed contract.

GoPlus exposes the capability as is_blacklisted: "If there is a blacklist, some addresses may not be able to trade normally". Its blunter sibling is transfer_pausable, "whether trading can be pausable by token contract". Neither field is a verdict. Both describe what an owner may do later.

Measurement separates the two ideas better than argument does. USDT on Ethereum returns is_blacklisted: "1" and is_honeypot: "0" in the same response. Tether can freeze an address, and USDT is not a honeypot. Capability is not intent, and one boolean cannot carry both.

Technique two skips the block and rewrites the arithmetic instead: "Rather than block users from selling the tokens they buy, this method changes a user's token balance to an amount specified by the contract creator", executed through a function named increaseAllowance(). Sell whatever you like. Your balance is whatever the deployer last wrote, and GoPlus keeps a field for exactly that, owner_change_balance, which live USDT also returns as 1.

Time-delayed and supply-threshold sell locks

Technique three is arithmetic rather than a list. Holders "can only sell tokens above a certain threshold which is set to an extremely high number", tracked in a structure CertiK found named hulkinfo. Required sell size exceeds the victim's entire holding, so every attempt fails on a bounds check that looks like dust protection.

Time works the same way. GoPlus calls that one trading_cooldown, and set wide enough a cooldown is a lock. Supply-side variants get their own fields: cannot_sell_all, is_anti_whale and anti_whale_modifiable, the last being the limit plus a setter.

uint256 private hulkinfo = type(uint256).max;
uint256 private cooldown = 365 days;
mapping(address => uint256) private lastBuyAt;

function _beforeSell(address seller, uint256 amount) internal view {
    require(amount >= hulkinfo, "amount too low");
    require(block.timestamp >= lastBuyAt[seller] + cooldown, "cooldown");
}
Threshold and cooldown locks on the sell path. Illustrative, not from a deployed contract.

Simulation meets this pattern on unequal terms. Caps do get reported, as maxBuy and maxSell, yet the verdict comes from a trade whose size the service chose.

Squid Game remains the clearest version. Selling required Marbles earned through gameplay, and entry cost 456 SQUID, worth $456 at a dollar a token.

Dynamic fee setters

A tax is a number in storage. Numbers in storage have setters.

GoPlus names the capability slippage_modifiable, "whether the trading tax can be modifiable by token contract", and keeps a per-address version called personal_slippage_modifiable, "whether the owner can set a different tax rate for every assigned address". That second field describes a contract able to charge you 99 percent and everybody else 3. An empty string means the tax is unknown.

uint256 public sellFee = 300; // basis points
mapping(address => uint256) public personalSellFee;

function setSellFee(uint256 bps) external onlyOwner { sellFee = bps; }

function setSellFeeFor(address holder, uint256 bps) external onlyOwner {
    personalSellFee[holder] = bps;
}
Global and per address fee setters. Illustrative, not from a deployed contract.

USDT again, one response: slippage_modifiable: "1" next to sell_tax: "0". Zero now. Settable later. Two statements about two different moments, and a simulation only ever reads the first.

In our own run, 11 of the 89 tokens both checkers called clean carried slippage_modifiable and one carried the per-address version. Six of the 150 addresses sampled already charged a sell tax above 10 percent on one checker or the other. That problem, at least, is visible before you buy.

Upgradeable transfer logic behind a proxy

Everything above assumes the code you read is the code that runs. Proxies break that assumption cleanly.

GoPlus reports is_proxy with no judgment attached. Danger comes from the neighbors, hidden_owner and external_call.

address private implementation;

function upgradeTo(address newLogic) external onlyOwner {
    implementation = newLogic; // transfer() is now whatever newLogic says
}

fallback() external payable {
    // delegatecall into implementation, storage stays here
}
Upgradeable transfer logic behind a proxy. Illustrative, not from a deployed contract.

The simulator splits the same question in two, contractCode.isProxy and contractCode.hasProxyCalls, and keeps openSource apart from rootOpenSource. A proxy can be verified on the explorer while the implementation behind it is not. Our call on the SQUID contract returned that exact shape: openSource: false with rootOpenSource: true, isProxy: true and hasProxyCalls: true.

Five of the 89 clean tokens in our sample were proxies and four carried hidden_owner, with no token in either group appearing in the other. One of the five proxies sits on BSC at 0x2598c30330d5771ae9f983979209486ae26de875, a token calling itself AI, clean on both checkers with is_proxy set. Nothing there is evidence of a scam. It is evidence that the sell path just tested is replaceable by whoever controls the proxy, and no checker announces the swap. Upgrade review is its own discipline, and the question it asks is who holds the proxy admin slot rather than who owns the logic, which is the whole subject of an upgradeable contract audit.

The four owner-gated patterns, the GoPlus fields that describe them and what a single simulated trade returns against each.
Pattern Mechanism GoPlus field What a simulation sees
Owner-gated blacklist A list the transfer function checks, empty at launch and written after the buy is_blacklisted, transfer_pausable, owner_change_balance A sell that succeeds, because the probe wallet is not on the list
Threshold and cooldown lock A minimum sell size above the whole position, or a cooldown wide enough to be a lock trading_cooldown, cannot_sell_all, is_anti_whale, anti_whale_modifiable One trade at a size and a moment the service chose, so the bound never binds
Dynamic fee setter A tax stored as a number, with a global setter and a per-address setter slippage_modifiable, personal_slippage_modifiable Today's tax reading, on a scale from 0 to 1
Upgradeable transfer logic A proxy pointing at logic the admin can replace is_proxy, hidden_owner, external_call The code in place at the block it ran, with no notice of a later swap

Why a clean scan is a statement about one block

The vendor says it first and says it plainly: "This is not a foolproof method. Just because it's not a honeypot now, does not mean it won't change!" A simulation measures one pair at one block, and it expires the moment the owner sends a transaction.

The life of one clean verdict across four blocks A block timeline for a single token. The launch block opens the pair and selling works. One scan block later a checker simulates a buy and a sell and returns a clean verdict, which a dashed band marks as valid in that block alone. Further right, a single owner transaction writes a blacklist entry, raises the sell fee or points the proxy at new code. In the last block a holder's sell reverts, while the clean verdict published earlier is unchanged and still on screen. verdict valid here only launch block scan block owner block sell block pair opens selling works one simulated buy one simulated sell verdict clean one owner transaction writes a blacklist entry, a fee or a new proxy target a holder tries to sell and the call reverts time in blocks
A checker's answer describes the block it ran in. Nothing revokes it when the owner changes the rules two hours later.
  1. Launch block. The pair opens and selling works.
  2. Scan block. A checker simulates one buy and one sell, then returns a clean verdict that is valid in that block alone.
  3. Owner block. One owner transaction lands.
  4. Sell block. That transaction wrote a blacklist entry, raised the sell fee or pointed the proxy at new code, so a holder trying to sell now watches the call revert, while the clean verdict published two blocks ago is unchanged and still on screen.

Academic work lands in the same place. RPHunter, published , reports 229 incidents that "exhibit no obvious code risks and primarily initiate Rug Pulls through unlocked liquidity", then concludes that "relying solely on code or transaction information to detect Rug Pulls can lead to false negatives and false positives."

Our run of 150 tokens through two checkers

So we measured how far a clean verdict travels.

Run date
Addresses sampled
150 distinct token addresses
Chains
Ethereum, BNB Smart Chain and Base
Source feeds
DexScreener's public token profile and boost feeds plus its search endpoint
Search terms
over 31 generic terms from a 34 term list, moon, gold, based, trump and floki among them
Both verdicts known
91 of the 150
No usable simulation
48 of the 150

Each address went through honeypot.is v2 and the GoPlus token_security endpoint.

Of the 150, exactly 91 came back with a usable verdict from both services. Eighty-nine were clean on both. Two were flagged by honeypot.is alone, both on BNB Smart Chain: 0xfb6115445bff7b52feb98650c87f44907e58f802, which GoPlus called clean while reporting is_proxy, and 0xd0dfc951cd7787859f0ed61966bc243038888888, clean on GoPlus with is_blacklisted set. Zero were flagged by GoPlus alone. Disagreement is rare in this sample. Agreement is not the same as safety.

Those two disagreements are worth a second look, because the reflex is to treat the odd tool out as a false positive. Read simulationError against isHoneypot before you believe that. A honeypot verdict arriving with simulationSuccess: false and an error string is a service reporting that it could not run the trade at all, which is a gap and not a finding. Both BSC tokens here came back the other way, with simulationSuccess: true, no error and honeypotReason reading HONEYPOT DETECTED, so a sell was simulated and it failed. GoPlus calling the same addresses clean does not contradict that, since its answer describes a capability rather than a trade.

Among the 89 both called clean, 49 carried at least one owner-gated GoPlus capability, which is 55 percent. That is a sample of what a retail buyer meets in a search box rather than a random draw. Frequencies: is_mintable on 20, is_blacklisted on 11, slippage_modifiable on 11, owner_change_balance on 10, transfer_pausable on 8, anti_whale_modifiable on 7, is_proxy on 5 and hidden_owner on 4, then can_take_back_ownership, personal_slippage_modifiable and trading_cooldown on one each.

150 tokens on Ethereum, BNB Smart Chain and Base through honeypot.is v2 and GoPlus token_security, .
Result Tokens Out of Share
Both verdicts known 91 150 sampled
Both call it clean 89 91
Flagged by honeypot.is only 2 91
Flagged by GoPlus only 0 91
One or more owner switches, clean on both 49 89 55 percent
No owner switch, clean on both 40 89
Exactly one switch, clean on both 32 89
Two switches, clean on both 8 89
Three or more switches, clean on both 9 89
Sell tax above 10 percent on either checker 6 150
Closed source 5 150
No usable simulation returned 48 150
GoPlus returned no is_honeypot key 21 150

A rule for reading the flags

Flags are not verdicts, so a usable rule has to say what each one costs a buyer. Ours has two tiers.

  • Stop outright on hidden_owner, can_take_back_ownership or personal_slippage_modifiable in any token at all, because each one hands an owner a lever that can be aimed at a single holder.
  • Stop on is_proxy as well, unless the proxy admin can be named and its holder checked.
  • Treat is_mintable and is_blacklisted as conditional. They are ordinary on a stablecoin run by an identified issuer, and disqualifying on a token whose deployer cannot be identified.

Applied to the 89 tokens both checkers called clean on , that rule rejects 10 of the 89 outright on one of the first four fields, and sends another 26 of the 89 into the conditional group, carrying is_mintable or is_blacklisted with none of the outright flags set. The remaining 53 pass the rule, which is not the same as passing an audit.

Two denominators sit outside the headline. The 48 with no usable simulation were mostly dead or illiquid pairs from the boost feeds, and GoPlus returned no is_honeypot key for 21 of the 150. Silence is the failure mode a hurried buyer is least likely to notice.

Two GoPlus API traps

Running these endpoints produced two findings worth recording. GoPlus documents multi-address batching, and a batch did return code: 1 with the message OK. It returned data for only the first address of every batch, on all three chains, while the same addresses queried one at a time answered correctly. Our script therefore sends one address per request.

Throttling is the second trap, and it shaped the script rather than showing up in this run: GoPlus has been observed signaling a rate limit inside an HTTP 200 body carrying code: 4029 and the message too many requests rather than as an HTTP 429, so the survey throttled to one call every two seconds and checked the body code instead of the status. No 4029 was hit on .

What an auditor checks that a simulation cannot

Count the outputs. A simulation returns one verdict, while GoPlus needs 18 booleans to describe what an owner may do to a token.

is_honeypot
the headline flag, worded by the vendor as the token maybe cannot be sold
cannot_sell_all
the holder cannot sell the entire position in one transaction
cannot_buy
buying is blocked by the contract
transfer_pausable
trading can be pausable by token contract
is_blacklisted
some addresses may not be able to trade normally
is_whitelisted
a list of addresses exempt from the rules the other holders meet
slippage_modifiable
the trading tax can be modifiable by token contract
personal_slippage_modifiable
whether the owner can set a different tax rate for every assigned address
trading_cooldown
the contract has a trading-cool-down mechanism
is_anti_whale
a cap on the size of a single trade or a single holding
anti_whale_modifiable
that same cap with a setter behind it
owner_change_balance
the owner can write a holder's balance to a number of its choosing
is_proxy
1 for a proxy and 0 for anything else
hidden_owner
the contract has owners that ordinary inspection misses
selfdestruct
the contract can destroy itself
external_call
a primary method can call into other contracts while it executes
is_mintable
new supply can be created after launch
can_take_back_ownership
ownership can return to a previous holder after it looks renounced

That ratio is the argument. An auditor's unit of analysis is the privilege, not the trade.

Ownership context is the second thing a trade never sees. GoPlus returns creator_address, creator_percent, owner_address, owner_percent, holder_count and a holders array with per-address percent, is_contract and is_locked. One field does work no single-token check can: honeypot_with_same_creator ties the token to earlier honeypots from the same deployer.

Deployer history is where the real casework sits. CertiK traced one funding address, 0xC5535F839071b854a8deC65d3b30E36AB91229AB, to 979 honeypot creators between and , at a peak rate of one contract every 30 minutes, for roughly $58.7k in total and about $60 taken per victim. Per-contract analysis would have found 979 tiny frauds. Cluster analysis found one operator.

Code analysis at scale works too, when it looks for the right thing. HoneyBadger examined more than 2 million smart contracts, flagged 690 as honeypots, confirmed 87 percent of those by hand and identified 240 victims with profits above $90,000, published at USENIX Security in .

An audit reaches the parts none of that touches: who holds the admin keys, what a timelock does and does not cover, whether the LP position is locked and until when, and which invariants survive adversarial input. Property testing is the practical form of that last question, described in our guide to smart contract fuzzing. Reading the document is a separate skill, so start at the scope statement and the dates as the audit report walkthrough sets out, then confirm the file is genuine before trusting any badge, which our note on fake audit reports covers. Our CertiK company analysis shows how far method varies between firms, the member directory lists the firms that publish, and the checkers described here sit in tools.

Tokens that passed a checker and still exited

SQUID is the canonical case and it is also our control. The token launched in at a cent, reached $4.42 within 72 hours and peaked at $2,861.80 at 09:35 London time on . Five minutes later it traded at $0.0007926, a fall of 99.9999 percent.

Ask honeypot.is about that address today and it says the token is fine.

Control set run alongside the sample on , excluded from every aggregate above.
Token Chain and address honeypot.is GoPlus
SQUID BNB Smart Chain, 0x87230146E138d3F296a9a77e497A2A83012e9Bc5
  • isHoneypot false
  • simulation succeeded
  • all taxes 0
  • 3,098 of 3,098 holder sells successful
  • risk high on closed source alone
full result returned with no is_honeypot key at all
USDT Ethereum, 0xdAC17F958D2ee523a2206206994597C13D831ec7 isHoneypot false is_honeypot 0, with is_blacklisted, is_mintable, owner_change_balance, slippage_modifiable and transfer_pausable all 1
USDC Ethereum, 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 isHoneypot false is_honeypot 0, is_proxy 1
WETH Ethereum, 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 isHoneypot false is_honeypot 0, no owner switch raised
PEPE Ethereum, 0x6982508145454Ce325dDbE47a25d4ec3d2311933 isHoneypot false is_honeypot 0, with anti_whale_modifiable, is_blacklisted and transfer_pausable set

Read the SQUID row carefully, because it is easy to over-read. It proves that a simulation describes one pair at the block where it ran: selling SQUID against the PancakeSwap pair works today, on whatever liquidity remains. It does not prove the tool is broken, or that the 2021 trap never existed. Dating the measurement is the finding rather than a formality.

Comparison rows say the rest. Five tokens, five risk shapes, one verdict word between them.

Scale, from the academic side. Sampling from the same period found over 10,000 scam tokens on Uniswap and 39,762 potential victims, roughly half of the listed tokens in that labeled sample.

Practical shape for a buyer, then.

  1. Run the checker, and record the verdict together with the block and the day it ran in.
  2. Read the flags it declined to turn into a verdict, because owner privileges outlast today's answer.
  3. Check the deployer, which matters more than either, through honeypot_with_same_creator and the creator address on the explorer.

Where a token is worth real money that work belongs to a firm, and our ranking of top cybersecurity companies is a starting point. Standing checks before a purchase are a crypto due diligence checklist of your own making, and owner backdoors are a separate subject from honeypots, since a smart contract backdoor can sit in a token that sells perfectly well today.

Frequently asked questions

Two checkers disagree about the same token. Which one do I believe?

Neither, until you know why they differ, because the two are answering different questions. A simulator says a sell executed against one pair at one block. A static scanner says the contract contains a function that could block a sell later. In our run of 91 tokens with both verdicts known, disagreement happened twice and both times the simulator was the one flagging, with GoPlus reporting a capability rather than a verdict on the same address. Treat any disagreement as a stop signal and go read the contract, or skip the token.

The owner renounced ownership. Does that end the honeypot risk?

Not when a proxy is in the picture. Renouncing sets the owner of the implementation to the zero address, and it says nothing about who may swap the implementation the proxy points at, which is often a separate admin slot with a separate holder. Check whether the token is a proxy first, then find the admin of the proxy rather than the owner of the logic. GoPlus flags the first half as is_proxy and the second as hidden_owner or can_take_back_ownership, and a token showing any of those with a renounced owner deserves the same suspicion it had before.

My token will not sell. How do I find out why?

Start with the checker's diagnostics rather than its verdict. Read simulationError against isHoneypot first: an error string with simulationSuccess false means the trade never ran, while isHoneypot true with simulationSuccess true means a sell was simulated and it reverted. Then check maxSell against the size of your position, because a cap below your holding fails a sell that a smaller one would pass. Check trading_cooldown next, since a sell attempted inside the window reverts on a timer rather than on anything about you. Then check is_blacklisted and look up your own address, because a list that started empty at launch may now hold exactly one entry. One limit applies to all of it: honeypot.is covers Ethereum, BNB Smart Chain and Base, so on other chains the GoPlus fields and a manual read of the transfer function are what remain.

How often should I re-check a token I already hold?

Before every sell of consequence, and after any announcement that mentions the contract. A clean result describes the block it ran in, so the useful habit is a check immediately before a transaction rather than a check once at purchase. Where the token carries slippage_modifiable, transfer_pausable, is_blacklisted or is_proxy, add a watch on the owner address, because the transaction that changes your position will come from there and will be visible on the explorer before your sell fails.