Token Audit Modules
>-
Token Audit Modules¶
Every Token Audit runs up to 11 independent verification modules. Each module produces a status (pass / fail / warn / n/a), a risk score, and a set of specific findings called invariants. All 11 modules are always shown in the results.
Module Overview¶
n/a is not a pass. Modules where the checked feature is absent return n/a - the feature simply isn't in this contract. Modules that are not yet active show a Coming Soon badge.
1. Live Simulation Coming Soon¶
Forks the blockchain at the current block, deploys the contract in an isolated environment, and executes a real buy and sell. Confirms whether sells are actually possible and measures the effective tax on both sides.
This module will provide the most direct honeypot signal available - a confirmed failed sell is definitive evidence that a token is a honeypot, regardless of what the source code analysis shows.
2. Ownership & Access¶
Checks who controls the contract and what they can do with that control.
What it checks:
- Is the owner address an EOA (single key) or a contract (multisig / timelock)?
- Is ownership renounced (set to address(0))?
- What capabilities does the owner have - can they mint, pause, blacklist, upgrade, or drain?
- Is there a timelock on privileged functions?
- Are there hidden shadow-owner addresses hardcoded into the contract?
Blast radius measures the maximum damage the current owner can cause in a single transaction - from none (renounced) through low, medium, high, to critical.
Key findings:
| Finding | Severity | Description |
|---|---|---|
| Unguarded ownership transfer | Critical | Anyone can take ownership - no access control |
| EOA owner with high capability | High | Single key controls mint, pause, blacklist, upgrade, or drain |
| Hidden owner | Critical | Hardcoded address found - possible shadow control |
| No timelock | High | Privileged functions take effect immediately with no delay |
| Broken renounce | High | renounceOwnership() does not actually set owner to zero |
| Owner unresolvable | High | owner() reverts or returns an unresolvable address - ownership state is opaque |
| Renounce missing | Medium | renounceOwnership() is absent - owner cannot voluntarily give up control. Advisory; no score impact. |
| Roles not separated | Advisory | AccessControl roles not well-separated - same role controls multiple risk-tier capabilities. No score impact. |
3. Liquidity & Pools¶
Discovers all liquidity pools for the token across known DEXes and checks whether liquidity is locked and for how long.
What it checks:
- Pool discovery across PancakeSwap V2/V3, Uniswap V2/V3, and other major DEXes on each chain
- Total pool TVL in USD
- Whether LP tokens are locked in a recognised locker (PinkLock, UniCrypt, TeamFinance, Mudra, or burned)
- Lock expiry date and percentage of pool supply locked
Note: LP lock detection is available for BSC and Ethereum. V3 NFT positions are tracked via the NonfungiblePositionManager.
Key findings:
| Finding | Severity | Description |
|---|---|---|
| No pool found | Low | No pool found via CREATE2 discovery on any known factory |
| LP unlocked | High | LP tokens not held by any recognised locker - pool can be drained at any time |
| Lock expired | High | LP lock has expired - liquidity can be withdrawn immediately |
| Lock expiring soon | Medium | LP lock expires within 30 days |
| Partial lock | Medium | Lock covers less than 80% of pool supply |
| Low TVL | Medium / High | Pool TVL below $1,000 or $100 - trivially manipulable |
| Unknown factory pool | High | Pool deployed by an unrecognised factory |
4. Supply & Mint¶
Checks whether the token supply is fixed or can be inflated, and whether the deployer holds a disproportionate share.
What it checks:
- Is there a mint() function, and who can call it?
- Is there a mint cap, and is it mutable?
- Are there non-standard functions that write to totalSupply?
- Does totalSupply match the sum of all balances?
- What percentage of supply does the deployer currently hold?
- Is the burn mechanism real (tokens removed from supply) or fake (tokens sent to zero but still counted)?
Key findings:
| Finding | Severity | Description |
|---|---|---|
| Hidden mint | Critical | Non-standard function can inflate supply |
| No mint cap | Critical | Supply can be inflated without limit |
| Supply inconsistency | Critical | totalSupply and sum of balances do not match |
| Fake burn | High | Tokens sent to zero address are not removed from total supply |
| Deployer holds 100% | High | No distribution has occurred - full rug risk |
| Active inflation | Critical | Current supply exceeds deploy-time supply - inflation confirmed on-chain |
| Mutable mint cap | High | Mint cap exists but is mutable - owner can raise the cap at any time |
| Transfer supply change | Critical | _transfer() call graph adds to totalSupply - inflationary transfer path |
| Deployer holds 80% | Medium | Deployer holds 80% or more of supply - highly concentrated; manipulation risk |
5. Transfer Integrity¶
Checks whether tokens are transferred correctly - that the sender's balance decreases by exactly the amount the recipient receives, and no funds leak to unauthorised addresses.
What it checks:
- Does every debit have a matching credit?
- Does transferFrom() respect the approved allowance?
- Are there more than one recipient address in the _transfer() path?
- Can the owner set fees, and is there an upper bound?
- Is the Transfer event emitted correctly?
Tokens with fee setter functions also get a Live Monitoring flag, indicating that the tax rate should be watched for changes.
Key findings:
| Finding | Severity | Description |
|---|---|---|
| Conservation failure | Critical | Tokens disappear - balance decreases without a matching credit |
| Unauthorized recipient | High | More than one address receives tokens on every transfer |
transferFrom() overdrain | Critical | More tokens can be pulled than the approved allowance |
| Unbounded fee setter | Critical | Owner can set transfer fees to 100% with no cap |
| High-bound fee setter | High | Fee cap exists but is above 25% |
| Low-bound fee setter | Medium | Fee cap exists and is at or below 25% - fees are capped but still settable by owner |
| Event missing | Medium | Transfer event not emitted - breaks tracking and compliance tools |
| Assembly in transfer | High | Dangerous inline assembly in the _transfer() call graph |
| Phantom balanceOf | Critical | balanceOf/transfer mapping identity mismatch - phantom balance where reads and writes differ |
6. Pausability¶
Checks whether the contract has a pause mechanism, who controls it, and whether it can be abused.
pause() function - returns n/a otherwiseWhat it checks:
- Is pause() access-controlled, or can anyone call it?
- Is the pauser a single EOA key?
- Does unpause() exist? Without it, the contract can be permanently frozen.
- Is the pause applied symmetrically - does it block both buys and sells, or only sells?
- Has the contract been paused before, and how many times?
- Is the contract currently paused?
Any contract that reaches this module automatically receives a Live Monitoring flag, regardless of findings - a pausable contract warrants ongoing monitoring even if the current configuration looks safe.
Key findings:
| Finding | Severity | Description |
|---|---|---|
| Unguarded pause | Critical | Anyone can freeze all transfers |
| No unpause | Critical | Contract can be permanently frozen with no recovery |
| Currently paused | Critical | All transfers are frozen right now |
| Asymmetric pause | Critical | Pause blocks sells but not buys - classic honeypot technique |
| EOA pauser | High | Single key can freeze all transfers instantly |
| Abusive pause history | Critical | Contract has been paused more than 3 times |
| Hidden pauser | High | Pauser address variable is private - not readable on-chain |
| Conditional pause | Critical | Conditional pause that can selectively block transfers for specific addresses |
| Mint not paused | High | mint() has no whenNotPaused modifier - deployer can mint while all transfers are frozen |
| Unpause restricted | Medium | pause() is access-controlled but unpause() auth pattern was not detected - verify manually |
| Paused multiple times | High | Contract has been paused multiple times - recurring pause usage |
| Paused once | Medium | Contract has been paused once - may be a legitimate emergency stop; monitor |
7. Approve Integrity¶
Checks whether the approve() function correctly and only updates the allowance mapping - and nothing else.
What it checks:
- Does approve() write to any state beyond the allowance mapping?
- Are there dangerous inline operations or external calls in the approve() call path?
- Does the amount parameter actually flow through to the allowance write?
- Is the Approval event emitted?
Key findings:
| Finding | Severity | Description |
|---|---|---|
| Extra state write | Critical | approve() writes to extra state - possible sell gate |
| External call in approve | Critical | Non-view external call can block or redirect allowance writes |
| Amount not flowing | Critical | The amount parameter does not reach the allowance - silent-zero or event spoofing |
| Assembly in approve | High | Dangerous inline operations in the approve path |
| Event missing | High | Approval event never emitted |
| Forbidden opcode | Critical | Forbidden opcode (CALL, DELEGATECALL, CREATE2, SELFDESTRUCT) found in the approve() bytecode path |
| Double emit | Medium | Approval event emitted more than once - double-emit anomaly |
8. Permit (Gasless Approvals)¶
Checks the EIP-2612 permit() implementation - the function that allows off-chain signatures to set allowances without a gas transaction.
permit() function - returns n/a otherwiseWhat it checks:
- Is the nonce incremented after each use? Without this, the same signature can be replayed unlimited times.
- Is the deadline enforced?
- Is chainId included in the domain separator? Without it, signatures can be replayed across chains.
- Is the ecrecover() result verified against the owner address?
- Do permit() and approve() write to the same allowance slot?
Key findings:
| Finding | Severity | Description |
|---|---|---|
| Nonce not incremented | Critical | Same signature can be replayed unlimited times |
| Deadline not enforced | Critical | Permit signatures never expire |
| Chain ID missing | Critical | Signatures valid across all chains |
ecrecover() not checked | Critical | Anyone can forge a permit for any address |
| Different allowance slot | Critical | Permits grant allowances invisible to allowance() |
| Preloaded permit | Critical | Deployer may have pre-granted unlimited allowance in the constructor |
| Deadline wrong order | High | Deadline comparison uses wrong operator - expired signatures may be accepted |
| Domain separator mutable | High | DOMAIN_SEPARATOR is reassigned after construction - may be changeable by owner |
| Chain ID hardcoded | High | chainId is hardcoded - permit signatures valid across chains if contract is deployed at the same address |
| Chain ID mutable | High | chainId assignment is mutable - owner can change the domain separator |
| Zero address not rejected | High | ecrecover() zero-address result is not rejected - invalid signatures may match address(0) approvals |
| Phishing risk signal | Medium | permit() selector detected in bytecode but source is unavailable - correctness cannot be verified |
9. Reentrancy¶
Checks whether the contract is vulnerable to reentrancy attacks - where an external call during a transfer allows an attacker to re-enter the contract before state is updated.
What it checks:
- Does the contract make external calls before updating balances (violating Checks-Effects-Interactions)?
- Are reentrancy guards present on external-calling functions?
- Is there a cross-function reentrancy path between _transfer() and mint()?
Key findings:
| Finding | Severity | Description |
|---|---|---|
| CEI violation | Critical | External call before balance write - classic reentrancy path |
| No reentrancy guard | High | External-calling function has no protection |
| ERC-677 callback early | Critical | Callback fires before balance update |
| Cross-function mint reentrancy | High | Transfer has external calls but mint is unguarded |
| Read-only reentrancy | Medium | External calls in transfer path without guard - read-only reentrancy risk allowing price oracle manipulation |
| ETH call without guard | High | .call{value:} pattern without reentrancy guard - ETH transfer can re-enter |
| Legacy ETH transfer | Low | .transfer() or .send() legacy ETH pattern - gas-limited but still reentrancy-adjacent |
| ApproveAndCall dispatcher | High | External payable function grants allowance then forwards arbitrary .call{value:}(data) to a caller-controlled address without nonReentrant - any function selector can be encoded in data, including privileged functions on the token itself |
| OFT delegate hijack | Critical | Contract exposes both an approveAndCall-style dispatcher and public lzReceive/setDelegate - attacker can replace the LayerZero delegate then forge messages to mint arbitrary supply |
10. Arithmetic & Fees Coming Soon¶
Will check fee calculation logic and arithmetic safety - unbounded fee accumulation, rounding errors that favour the contract, and overflow/underflow patterns in fee math.
11. Event Integrity Coming Soon¶
Will verify that the contract emits correct ERC-20 events on every state change - Transfer on balance writes, Approval on allowance writes - in the right order and with the correct parameters.
Honeypot Risk Score¶
In addition to the 11 modules, the audit runs a dedicated honeypot pattern analysis across the full contract code. This produces a separate honeypot_risk_score (0-100+) based on the weighted sum of dangerous patterns found.
| Score | Classification |
|---|---|
| 0-24 | Clean |
| 25-49 | Suspicious |
| 50-79 | High Risk |
| ≥ 80 | Honeypot |
Key findings:
| Finding | Severity | Description |
|---|---|---|
| Controlled launch mode | Critical | Launch mode gate prevents transfers until deployer enables trading |
| External call in transfer | Critical | staticcall/delegatecall/call found inside a transfer function |
| Transfer kill switch | Critical | Kill switch or emergency stop that permanently disables all transfers |
| SHA256 privilege check | Critical | sha256(abi.encodePacked(msg.sender)) used as an auth gate - obfuscated access control |
| Upgradeable proxy - EOA admin | Critical | Transparent proxy with EOA admin - contract logic can be replaced by a single key |
| UUPS proxy - EOA owner | Critical | UUPS upgradeable proxy with EOA owner - upgrade authority held by a single key |
| Assembly in transfer | High | Inline assembly block inside a transfer function (not a known safe storage pattern) |
| Transfer admin only | High | Transfer restricted to admin/owner only - regular users cannot sell |
| Max uint balance manipulation | High | type(uint256).max written to a balance/allowance mapping - underflow blacklist pattern |
| Custom transfer entry point | High | Non-standard public transfer-like function - hidden sell path that routes tokens differently |
| Layered transfer delegation | High | Multiple layers of transfer delegation with opaque logic |
| Missing standard functions | High | Standard ERC-20 functions (transfer, approve, allowance) are missing |
| UUPS proxy - unknown auth | High | UUPS proxy with unidentified upgrade authority |
| Excessive branching | Medium | Excessive conditional branches in transfer function - complex sell restriction logic |
| Obfuscated variable names | Medium | Variable names appear deliberately obfuscated |
| Unexpected events in transfer | Medium | Non-standard events emitted inside transfer functions |
| Blacklist | Medium | Blacklist mechanism present - targeted addresses can be blocked from transferring |
| UUPS proxy - contract owner | Medium | UUPS proxy owned by a contract - upgrade authority exists but is not a plain EOA |
| Proxy - generic contract admin | Medium | Transparent proxy admin is a generic contract (not a timelock or multisig) |
Behavioral Signals¶
Two additional signals feed into the aggregate verdict from ChainAware's behavioral database - independently of the code analysis:
| Signal | Description |
|---|---|
behavioral_fraud_score |
Fraud probability (0.0-1.0) for the deployer and feeder wallets, derived from 20M+ wallet profiles |
behavioral_is_honeypot |
Whether the behavioral scanner classified this contract as a honeypot based on on-chain trading patterns |
A contract can pass all static analysis modules but still carry a non-zero behavioral fraud score if the deployer wallet has a history of fraudulent deployments or the trading pattern matches known honeypot behaviour.
Further Reading¶
- Token Audit: 15,008 CoinGecko & CoinMarketCap Tokens Analysed - the largest Token Audit study to date, covering tokens from both CoinGecko and CoinMarketCap
- Token Audit: 10,000 CoinGecko Tokens Analysed - 129 checks across 9 modules applied to 12,998 contracts; 55.2% HIGH RISK, 35.9% without mint caps, 25.7% with unlocked liquidity
- Web3 Trust Verification Systems in 2026
See also: Verdicts | Token Audit Overview