ERC-721 Standard Violations Remediation
Overview
NFT contracts that deviate from ERC-721 break in marketplaces, wallets, and bridges that expect standard behavior. The most damaging deviation is a safeTransferFrom that never calls onERC721Received on a contract recipient: NFTs sent to a contract that cannot handle them become permanently locked. Missing ERC-165 interface declarations and malformed Transfer events compound the problem.
Related Detector: ERC-721 Standard Violations
Recommended Fix
Before (Vulnerable)
function safeTransferFrom(address from, address to, uint256 tokenId) public {
_transfer(from, to, tokenId);
// No onERC721Received check -- NFTs can be locked in contracts
}
After (Fixed)
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract MyNFT is ERC721 {
constructor() ERC721("MyNFT", "MNFT") {}
// safeTransferFrom, ERC-165 support, and correct events are inherited
}
Inheriting from a reviewed ERC-721 implementation guarantees that safeTransferFrom invokes the recipient callback, that supportsInterface reports the correct interface IDs, and that Transfer and Approval events carry the standard indexed parameters. The callback check rejects transfers to contracts that do not implement IERC721Receiver, preventing silent loss.
Alternative Mitigations
- If you must write a custom implementation, call
IERC721Receiver(to).onERC721Received(...)whenevertois a contract and require the returned magic value0x150b7a02. - Declare ERC-165 support for both the
IERC721interface ID and theIERC165interface ID so external integrations can detect capability. - Emit
Transfer(from, to, tokenId)on every mint, transfer, and burn, with all three parameters indexed.
Common Mistakes
- Implementing
transferFromcorrectly but leavingsafeTransferFromwithout the receiver callback. - Returning
truefromsupportsInterfacefor interfaces the contract does not actually implement, which misleads integrators. - Skipping the Transfer event on mint or burn, which breaks indexers and marketplace inventory.