The premise
There is a persistent belief that rug pulls involve hidden backdoors that only an expert could spot. Some do. Most do not. The overwhelming majority use plainly written, publicly verified functions that anyone could have read before buying — an owner-only mint, a transfer gate, a fee setter with no cap.
These are not exploits. They are powers, and they are usually disclosed by the code itself. The failure is not that the code was deceptive; it is that nobody looked. Everything below is checkable in about ten minutes on a verified contract, without being able to write Solidity.
The question is never "is this contract hacked?" It is "what can the owner do to me, and do I trust them not to?"
1. Unlimited mint
Search the source for mint, _mint, or issue. If a mint function exists, is owner-callable, and has no supply cap, the circulating supply is a number the owner may change at will.
// Red flag: no cap, owner-only, callable forever.
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
}Four words decide whether this is acceptable: capped (is there a MAX_SUPPLY check?), who (a single key, or a timelocked multisig?), when (can it still be called after launch?), and disclosed (does the project tell holders this exists?). A mint used for a scheduled emissions curve is normal. An uncapped one held by one key and unmentioned in the docs is the most common rug there is.
2. Transfer blocking and honeypots
A honeypot lets you buy and prevents you selling. The mechanism is almost always a check in a transfer hook — _beforeTokenTransfer, _update, or a custom _transfer override.
// Red flag: the owner decides who is allowed to sell.
function _beforeTokenTransfer(address from, address to, uint256 amount)
internal override
{
require(!blacklisted[from], "blacklisted");
require(tradingEnabled || from == owner(), "trading not enabled");
}Look for a blacklist or blocklist mapping, a trading-enabled flag, a maximum transaction size the owner can lower to zero, or a cooldown between sells. Any of these means selling is permitted rather than guaranteed.
Some of these have honest uses — a trading flag is standard for a fair launch, to stop snipers buying before liquidity is added. The question is whether it can be turned back off after launch, and who holds that switch.
3. Fees with no ceiling
A tax on buys and sells is a legitimate mechanic. A fee setter with no upper bound is not — it is a sell blocker with extra steps, because a 100% sell fee is a sell that returns nothing.
// Red flag: no upper bound. 100% is reachable.
function setFees(uint256 _buy, uint256 _sell) external onlyOwner {
buyFee = _buy;
sellFee = _sell;
}What you want to see is a hard cap enforced in the setter, something like require(_sell <= 10, "max 10%"). If the cap exists only in the documentation and not in the code, the documentation is not the thing that runs.
4. Upgradeable logic
If the contract is behind a proxy, today's code is not necessarily tomorrow's. Everything you just checked can be replaced by an upgrade, in one transaction, with no warning.
Upgradeability is not inherently a red flag — serious protocols use it to ship fixes. What matters is who controls the upgrade and how fast they can do it. An upgrade behind a multisig with a 48-hour timelock is a governed process you could react to. An upgrade behind one key is a promise. Check the explorer's proxy tab for the admin address and whether it is a timelock contract or an EOA.
5. Unlocked liquidity
This one is not in the token contract at all, which is exactly why it gets missed. The classic rug is pulling the liquidity pool: the deployer holds the LP tokens, and when they redeem them, the pool empties and the token becomes unsellable at any price.
Check where the LP tokens went. Burned to a dead address, or held by a recognised locker with a visible unlock date, is meaningfully different from sitting in the deployer's wallet. And check the unlock date — a lock expiring next week is a countdown, not a guarantee.
6. Ownership theatre
"Ownership renounced" is offered as proof of safety constantly, and it is worth less than it sounds in both directions.
Renouncing after a hidden mint has already inflated supply protects nobody. Renouncing while a second privileged role still exists — an authorized mapping, an operator, a taxWallet with special rights — leaves the powers intact under another name. Search for every modifier in the contract, not just onlyOwner.
Renouncement can also be actively harmful: a renounced contract that still needs its owner to enable trading is permanently frozen. The useful question is not whether ownership was renounced, but which addresses currently hold which powers.
7. Unverified source
If the source is not verified on the explorer, none of the above can be checked, and no reasoning about the contract's behaviour is possible. Verification is free and takes a minute. A team that has not done it has either not bothered — which tells you something — or does not want the code read, which tells you more.
Treat unverified as its own answer. There is no shortage of verified contracts to look at instead.
A ten-minute check, in order
- Is the source verified? If not, stop.
- Is it a proxy? If yes, who controls upgrades, and is there a timelock?
- Search
mint. Capped? Who can call it? - Search
blacklist,_beforeTokenTransfer,tradingEnabled,maxTx. Can selling be turned off? - Search
feeandtax. Is there a hard cap in the setter? - Search
onlyOwnerand every other modifier. List every privileged function. - Check the LP tokens. Burned, locked with a date, or in a wallet?
- Run an audit on the address. The centralization findings do steps 3 to 6 mechanically and will not get bored halfway through.
That last step is what the engine is genuinely good at: enumerating owner powers exhaustively from a deployed address. Point it at the contract before you buy — an audit on a live address costs less than most positions people take without one, and the centralization section of the report is written to answer exactly these questions.