NFT smart contract audit

NFT contract audits, built around how mints actually fail.

ERC-721 and ERC-1155 collections rarely lose money to exotic bugs. They lose it to a supply cap that can be exceeded, an allowlist that can be replayed, a reveal that was never actually random, and a withdraw function nobody checked. Those are the things this audit goes after first.

Audit my NFT contractScan a testnet mint free

Why NFT contracts fail differently

An ERC-20 token is, structurally, a mapping from address to balance with rules about who may change it. An NFT contract is a state machine with phases — closed, allowlist, public, sold out — carrying a price, a supply cap, per-wallet limits, a signature or Merkle allowlist, and a dependency on metadata that usually lives somewhere else entirely.

More moving parts means more places to be wrong, and the failures are concentrated in the mint. That is also the moment of maximum adversarial attention: thousands of people, many running bots, hitting one function in the same few blocks. A mint bug is discovered immediately, by people motivated to exploit it, against a contract that cannot be patched.

The mint path

This is where the audit spends most of its effort, and roughly in this order.

Supply cap enforcement

The cap must hold under every path — public mint, allowlist mint, team reserve, airdrop — and it must hold under reentrancy. _safeMint calls onERC721Received on a contract recipient, which hands execution to the buyer mid-loop. If the supply counter has not been updated before that call, a contract can re-enter and mint past the cap. This is not theoretical; it is the most common serious NFT bug there is.

// Vulnerable: safeMint hands control to the buyer's contract
// before totalMinted is updated, so the cap can be blown past.
function mint(uint256 qty) external payable {
    require(totalMinted + qty <= MAX_SUPPLY, "sold out");
    require(msg.value == PRICE * qty, "wrong price");
    for (uint256 i = 0; i < qty; i++) {
        _safeMint(msg.sender, totalMinted + i);  // external callback
    }
    totalMinted += qty;   // updated last — re-entered before this
}

Settle state before the callback, and guard the function:

// Fixed: state settled before the callback, and guarded.
function mint(uint256 qty) external payable nonReentrant {
    require(qty > 0 && qty <= MAX_PER_TX, "bad quantity");
    require(totalMinted + qty <= MAX_SUPPLY, "sold out");
    require(msg.value == PRICE * qty, "wrong price");
    uint256 startId = totalMinted;
    totalMinted += qty;   // effect first
    for (uint256 i = 0; i < qty; i++) {
        _safeMint(msg.sender, startId + i);
    }
}

Price and payment

Strict equality on msg.value rather than a minimum, or the difference is silently kept. Overpayment either refunded or documented. And a withdraw path that actually works — funds locked forever in a contract with no withdraw, or a withdraw sending to a hardcoded address that turns out to be wrong, is a recurring and entirely avoidable loss.

Allowlist verification

Merkle proofs must be bound to the claimant and the quantity, and claims must be recorded so a valid proof cannot be replayed. Signature allowlists need a nonce and a deadline, and the signer address must be changeable in case the key is compromised. Signature schemes that omit the contract address or chain id can be replayed across deployments.

Per-wallet limits

Worth auditing but also worth being honest about: a per-wallet cap enforced on msg.sender is trivially bypassed with fresh addresses. The finding is usually not "this is broken" but "this does not do what your community thinks it does".

Reveal, metadata and randomness

If token IDs map to traits, the assignment must not be predictable before the reveal, or bots will mint only the rare ones. Seeding a shuffle from block.timestamp or blockhash means the sequencer or proposer chooses your rare traits — the weak-randomness detector flags exactly this.

Metadata is the other half. A baseURI that stays mutable forever means the team can change every image after sale. That may be entirely intended — collections do fix broken art — but it is a trust assumption your buyers deserve to see stated, and the audit reports it as a centralization finding rather than pretending it is a bug.

Owner privileges buyers should know about

For NFT contracts, the centralization findings are usually the most valuable part of the report, because they answer what secondary buyers actually want to know:

  • Can the owner mint more after the collection sells out?
  • Can the owner change metadata after reveal, or is baseURI frozen?
  • Can the owner pause transfers, or blacklist a holder?
  • Can royalties be changed, and who receives them?
  • Is ownership renounceable, and would renouncing brick anything the contract still needs?

None of these are bugs. All of them belong in a report, which is why the engine surfaces them as their own finding class rather than burying them.

Royalties

ERC-2981 is a signal, not an enforcement mechanism — most marketplaces decide for themselves whether to honour it. The audit checks that the interface is implemented correctly, that the recipient and basis points are sane, and that any transfer-blocking enforcement is disclosed, since a contract that blocks transfers to non-honouring marketplaces will strand holders when those marketplaces are where the liquidity is.

Getting your collection audited

Before launch, deploy to a testnet and scan it free as many times as you need while the code moves. When it settles, run a paid audit on the mainnet contract for the record — most single collections fit Starter, while a collection with staking or a marketplace alongside it fits Pro. The pricing page has the detail, and the Solidity audit page covers the general detector suite that runs underneath all of this.

FAQ

Frequently asked questions

What does an NFT smart contract audit check?

The mint path first — supply caps, per-wallet limits, price handling and allowlist verification — then reentrancy through safeMint callbacks, the metadata and reveal mechanism, royalty configuration, the withdraw path, and every owner privilege that could be used to mint extra supply or freeze transfers after launch.

Do you audit ERC-721 and ERC-1155?

Both, along with the common extensions — ERC-721A and similar gas-optimised variants, ERC-2981 royalties, and the OpenZeppelin access-control and pausable modules most collections build on.

Why do NFT contracts need a different audit from tokens?

Because the risk sits in different places. An ERC-20 is mostly a balance mapping; an NFT contract is a mint state machine with phases, allowlists, prices and a supply cap, plus an off-chain metadata dependency. Most NFT incidents are mint logic failures and metadata problems, not transfer bugs.

Can you audit before the collection launches?

Yes, and that is the right time. Paste the Solidity or upload the project, or deploy to a testnet and scan it free as many times as you like while you iterate. A mint bug found after launch usually cannot be fixed, because the contract is immutable and the supply is already wrong.

What does an NFT contract audit cost?

Most single NFT contracts fit the Starter tier at $150. A collection with a separate staking or marketplace contract fits Pro at $250, which covers up to five contracts or a full project upload.

Can you check a collection I am thinking of buying into?

Yes. Run an audit on the deployed contract address — it is public and verified for most collections. The centralization findings answer the questions buyers actually care about: can the team mint more, can they change metadata after reveal, can they block transfers.

Audit the mint before the mint audits you

Paste your ERC-721 or ERC-1155, or point at a deployed address. Findings come back with line numbers and fixes.

Start your auditSee pricing