ERC-721 Standard Violations
Overview
Remediation Guide: ERC-721 Standard Violations Remediation
The ERC-721 violations detector first classifies a contract as an NFT by checking for the core selectors (balanceOf, ownerOf, transferFrom), then verifies it against the standard. It reports five finding types:
- Missing required function — a core ERC-721 selector is absent from the dispatch table.
- Missing ERC-165 support — no
supportsInterface(bytes4)implementation, breaking interface discovery for marketplaces and wallets. - Missing
onERC721Receivedcheck —safeTransferFromdoes not invoke the receiver callback, so tokens sent to contracts that cannot handle them are lost. - Missing zero-address check — transfer or mint paths accept
address(0), effectively burning tokens by accident. - Potential reentrancy in safe transfer — state is updated after the receiver callback, letting a malicious receiver re-enter mid-transfer.
Why This Is an Issue
Marketplaces, wallets, and bridges assume standard behavior. A missing receiver callback means every safeTransferFrom to a non-receiver contract permanently locks the NFT — the exact failure safeTransferFrom exists to prevent. Missing ERC-165 declarations make platforms refuse to list the collection or, worse, mis-handle it. And because the onERC721Received callback hands control to the recipient, a transfer implementation that updates ownership after the callback is a reentrancy vector: the receiver can call back into the collection while ownership state is stale.
How to Resolve
// Before: "safe" transfer that never checks the receiver
function safeTransferFrom(address from, address to, uint256 id) external {
_transfer(from, to, id);
}
// After: callback verified, state updated before the external call
function safeTransferFrom(address from, address to, uint256 id) external {
_transfer(from, to, id); // effects first
if (to.code.length > 0) {
require(
IERC721Receiver(to).onERC721Received(msg.sender, from, id, "")
== IERC721Receiver.onERC721Received.selector,
"unsafe recipient"
);
}
}
Prefer inheriting an audited implementation (OpenZeppelin ERC721) over reimplementing the standard.
Examples
Vulnerable Code
contract BareNFT {
mapping(uint256 => address) public ownerOf;
function transferFrom(address from, address to, uint256 id) external {
require(ownerOf[id] == from, "not owner");
// Missing: zero-address check — 'to' may be address(0)
ownerOf[id] = to;
// Missing: Transfer event, approval clearing, ERC-165, safe variant
}
}
Fixed Code
contract StandardNFT is ERC721 {
constructor() ERC721("Standard", "STD") {}
// Inherits compliant transfer, safe transfer with callback,
// zero-address checks, events, and ERC-165 declarations.
}
Sample Sigvex Output
{
"detector_id": "erc721-violations",
"severity": "high",
"confidence": 0.7,
"description": "Missing onERC721Received check in safeTransferFrom(address,address,uint256): the receiver callback is never invoked, so transfers to contracts can permanently lock tokens.",
"location": { "function": "safeTransferFrom(address,address,uint256)", "offset": 0 }
}
Detection Methodology
The detector matches the contract’s dispatch table against the canonical ERC-721 and ERC-165 selector sets, then inspects each safe-transfer function body for an external call carrying the onERC721Received selector, for zero-address comparison branches on transfer paths, and for the ordering of storage writes relative to the receiver callback. Findings carry individual confidences (roughly 0.70–0.80) reflecting known false-positive sources — for example, a callback check implemented in a parent contract or library that the flattened bytecode obscures.
Limitations
- Soulbound or deliberately restricted tokens that omit transfer functions by design are flagged for missing functions; confidence is reduced but not zero.
- ERC-165 support declared in a proxy’s implementation contract is not visible when scanning the proxy alone.
- Event emission correctness (parameter indexing) is not fully verified from bytecode.
Related Detectors
- ERC-1155 Standard Violations — the multi-token equivalent
- ERC-20 Standard Violations — fungible-token compliance
- Token Hook Reentrancy — reentrancy via token callbacks
- Royalty Bypass — ERC-2981 royalty circumvention