Rug Pull Red Flags: How to Spot a Risky Token Contract
Most rug pulls do not rely on novel exploits. They rely on features that were written into the contract from the start — features that look unremarkable until the deployer decides to use them. Reading a token contract for danger is therefore less about finding bugs and more about finding intentional trapdoors. This guide walks through the six most common rug pull red flags, shows what they look like in Solidity, and explains what each one enables an attacker to do.
1. Minting Without a Supply Cap
An uncapped mint function lets the owner inflate the token supply at will. In a liquidity pool, newly minted tokens can be swapped for the pooled ETH or stablecoin, draining value from every other holder instantly.
// Vulnerable: owner can mint any amount at any time
function mint(address to, uint256 amount) external onlyOwner {
_mint(to, amount);
}
// Safer: enforce a hard cap at mint time
uint256 public constant MAX_SUPPLY = 1_000_000 * 10 ** 18;
function mint(address to, uint256 amount) external onlyOwner {
require(totalSupply() + amount <= MAX_SUPPLY, "Cap exceeded");
_mint(to, amount);
}
The fix is a constant or immutable cap checked inside the mint function, or better yet, minting the entire supply at deployment and removing the mint function entirely. Any token where an owner address retains unrestricted minting power after launch is a red flag regardless of what the project's documentation promises.
2. Hidden or Transferable Owner Privileges
A single onlyOwner modifier is not automatically dangerous, but several patterns make it much riskier than it first appears.
- No timelock: Owner actions take effect in the same transaction, giving holders zero time to react.
- Transferable ownership with no multisig: A single private key controls everything. If that key is compromised or sold, the new holder inherits all privileges.
- Hidden admin slots in proxies: Storage slot collisions or undocumented initializer functions can leave admin access invisible to a casual reviewer.
- Functions that change fee recipients, tax rates, or whitelist status: These are often buried in the contract and not mentioned in documentation.
When reviewing a contract, list every function gated by an access modifier and ask: what is the worst-case outcome if this function is called by a malicious actor who has acquired that role? If the answer is "drain all liquidity" or "freeze all transfers," that privilege needs a timelock or governance delay.
3. Blacklist and Pause Functions
Blacklist and pause mechanisms are frequently justified as anti-bot or compliance tools. Sometimes they are legitimate. Often they are the mechanism the deployer intends to use to trap holders before exiting.
// Pause and blacklist used together create a honeypot
mapping(address => bool) private _blacklisted;
bool private _paused;
function _transfer(address from, address to, uint256 amount) internal override {
require(!_paused, "Transfers paused");
require(!_blacklisted[from], "Sender blacklisted");
super._transfer(from, to, amount);
}
function blacklist(address account) external onlyOwner {
_blacklisted[account] = true;
}
function pause() external onlyOwner {
_paused = true;
}
The danger pattern here is asymmetric: the owner can keep selling while all other holders are blocked. A legitimate project that genuinely needs a pause mechanism should scope it narrowly, put it behind a multisig, and publish a clear policy on when it will be used. Blanket blacklisting with no appeal process and no timelock is a red flag. See the ERC-20 token audit guide for a deeper look at transfer restriction patterns.
4. Honeypot Transfer Logic
A honeypot is a token that lets buyers in but blocks or taxes sells so severely that holders cannot exit. The logic is almost always inside the _transfer or _beforeTokenTransfer hook and is often obfuscated.
// Honeypot example: sell tax that can be set to 100%
uint256 public sellTax; // owner can set this to 10000 (100%)
function _transfer(address from, address to, uint256 amount) internal override {
if (to == uniswapPair) {
uint256 tax = (amount * sellTax) / 10000;
super._transfer(from, address(this), tax);
super._transfer(from, to, amount - tax);
} else {
super._transfer(from, to, amount);
}
}
function setSellTax(uint256 tax) external onlyOwner {
sellTax = tax; // no upper bound check
}
Warning signs to look for in transfer logic:
- Tax or fee rates that are settable by the owner with no maximum cap enforced in the setter
- Conditional logic that behaves differently when
toorfromis a DEX pair address - Whitelisted addresses (often the deployer's wallet) that bypass all fees
- Complex internal accounting that is difficult to trace — deliberately so
The safest approach for any token that has launch fees is to hardcode the maximum fee in a constant and require that any fee value set by the owner is below that constant, ideally with a renounced setter after launch.
5. Unlocked or Deployer-Controlled Liquidity
Token contracts themselves rarely manage liquidity directly, but the deployer's behavior around liquidity is a major rug pull vector. The red flag at the contract level is the absence of any mechanism that prevents the deployer from pulling LP tokens.
What to check:
- Are LP tokens sent to a time-lock contract at launch, or do they stay in the deployer wallet?
- Does the token contract have a function that allows the owner to withdraw tokens or ETH from the contract balance — which may include accumulated fees destined for the liquidity pool?
- Is there a
rescueTokensorsweepfunction that can drain arbitrary ERC-20 balances held by the contract?
// Common rug vector: owner can drain any token held by the contract
function rescueTokens(address token, uint256 amount) external onlyOwner {
IERC20(token).transfer(owner(), amount);
}
This function is sometimes marketed as a recovery tool for accidentally sent tokens. It becomes a rug vector when the contract accumulates fee tokens or when LP tokens are sent to it. Verify whether this function can be called on the LP token itself.
6. Proxy Upgradeability Without Governance
Upgradeable proxy patterns — UUPS, Transparent Proxy, Beacon — are legitimate architectural choices for complex protocols. For a simple ERC-20 token, they are almost always a red flag. An upgradeable token contract means the logic can be replaced entirely after launch. The deployer can push a new implementation that mints unlimited tokens, removes transfer restrictions, or changes the total supply variable in storage.
Key questions when a token uses a proxy:
- Who controls the upgrade key? Is it a single EOA or a multisig with a timelock?
- Is the upgrade function protected by a governance vote or a minimum delay?
- Is there a published upgrade policy and what events are emitted when an upgrade occurs?
The guide to upgradeable contract security covers storage collision risks and safe proxy patterns in detail. For token contracts specifically, consider whether upgradeability is actually necessary. If it is not, use an immutable implementation.
How to Systematically Check a Token Contract
Manual review of every function is time-consuming and easy to get wrong. Static analysis tools catch a significant proportion of the patterns described above automatically. Slither, for example, flags unrestricted mint functions, centralization risks, and missing event emissions on state-changing owner functions. Mythril can reason about reachability of dangerous states. Running these tools before trading or deploying is practical due diligence, not overkill.
Before investing in or launching a token, run an automated scan to surface the most common dangerous patterns quickly. For a structured pre-launch checklist, see Is My Token Safe? A Pre-Launch Security Checklist.
Summary: The Six Red Flags
- Uncapped mint: Owner can inflate supply and dump into liquidity pools.
- Hidden owner privileges: Undocumented admin functions or transferable keys with no timelock.
- Blacklist and pause: Owner can freeze holders while continuing to sell.
- Flexible sell tax: Fees settable to 100% trap holders after buy-in.
- Unlocked liquidity: LP tokens or accumulated fees accessible to the deployer.
- Ungoverned proxy: Contract logic can be replaced entirely post-launch by a single key.
None of these patterns are invisible. They are all readable in the contract source code or bytecode. The barrier is knowing what to look for and taking the time to look before money changes hands.
Scan your contract before you ship
Run an automated, transparent security scan — seven industry tools in parallel, every finding labeled with its source tool. It is not a substitute for a full manual audit, but it is a fast first line of defense.
Scan a contractFrequently asked questions
What is the most common rug pull mechanism in ERC-20 tokens?
Uncapped mint functions and hidden owner withdrawal mechanisms are the most frequently used. An unrestricted mint lets the deployer create tokens and sell them into a liquidity pool, draining the paired asset. Owner-controlled sweep functions can extract accumulated fees or LP tokens directly.
Can a blacklist or pause function always be considered malicious?
Not always. Regulated stablecoins and some DeFi protocols have legitimate compliance or emergency-pause needs. The risk level depends on who controls the function, whether a timelock or multisig is required, and whether the scope is narrow. A pause controlled by a single EOA with no delay and no stated policy is a red flag even if the intent is benign.
How can I tell if a token is a honeypot before buying?
Review the transfer function and any hooks it calls for conditional logic that applies different fees to sells versus buys, or that whitelists specific addresses. Check whether fee or tax setters have an enforced maximum. Simulation tools that test a buy-then-sell sequence on a fork can also reveal honeypot behavior without risking real funds.
Is an upgradeable token contract always dangerous?
Upgradeability itself is not inherently dangerous, but it concentrates significant risk in whoever controls the upgrade key. For a simple token, upgradeability rarely adds value and always adds risk. When it is present, check that upgrades require a multisig with a timelock and that an event is emitted on upgrade so holders can react.
Does automated scanning catch all rug pull red flags?
Automated tools reliably detect structural patterns like unrestricted mint functions, centralization risks, missing access controls, and dangerous proxy configurations. They are less effective at detecting intent or highly obfuscated logic. Automated scanning is a necessary first step, but it does not replace careful manual review of all owner-privileged functions, especially in high-value contracts.