--- name: "ERC Interface & Extension Standards (EIP-165, EIP-173, ERC-3156)" description: Use when implementing interface detection, contract ownership, flash loans, payable tokens, or cross-chain CCIP-read. Covers all Ethereum interface and utility extension standards. ecosystem: ethereum type: standard-guide source: official confidence: high version: 1.0.0 time_sensitivity: evergreen tags: - ethereum - erc - standard - eip-165 - eip-173 - erc-1820 - erc-3156 - erc-1363 - erc-3668 - interface-detection - flash-loan - ownership - ccip-read updated_at: 2026-03-26T00:00:00.000Z --- # ERC Interface & Extension Standards (EIP-165, EIP-173, ERC-3156) ## Overview This guide covers Ethereum's interface detection, ownership, flash loan, payable token, and off-chain lookup standards. These are utility standards that make contracts interoperable and composable. ## EIP-165 — Standard Interface Detection EIP-165 allows callers to query whether a contract implements a specific interface, identified by a 4-byte interface ID. ```solidity interface IERC165 { // Returns true if contract implements the interface function supportsInterface(bytes4 interfaceId) external view returns (bool); } ``` ### Computing Interface IDs An interface ID is the XOR of all function selectors in the interface: ```solidity // Compute at compile time bytes4 constant ERC721_INTERFACE_ID = type(IERC721).interfaceId; // = transferFrom.selector ^ safeTransferFrom.selector ^ ... (XOR of all selectors) // Common interface IDs bytes4 constant ERC165_ID = 0x01ffc9a7; bytes4 constant ERC721_ID = 0x80ac58cd; bytes4 constant ERC721Meta_ID = 0x5b5e139f; bytes4 constant ERC1155_ID = 0xd9b67a26; bytes4 constant ERC2981_ID = 0x2a55205a; bytes4 constant ERC4907_ID = 0xad092b5c; ``` ### Implementation ```solidity import "@openzeppelin/contracts/utils/introspection/ERC165.sol"; contract MyToken is ERC165, IERC721 { function supportsInterface(bytes4 interfaceId) public view override returns (bool) { return interfaceId == type(IERC721).interfaceId || interfaceId == type(IERC721Metadata).interfaceId || super.supportsInterface(interfaceId); // Handles ERC165 itself } } ``` ### Querying Interface Support (ethers.js) ```typescript import { ethers } from "ethers"; async function supportsInterface( contractAddress: string, interfaceId: string, // e.g., "0x80ac58cd" provider: ethers.Provider ): Promise { const contract = new ethers.Contract( contractAddress, ["function supportsInterface(bytes4) view returns (bool)"], provider ); try { return await contract.supportsInterface(interfaceId); } catch { return false; // Contract doesn't implement ERC-165 } } // Example: check if contract is ERC-721 const isNFT = await supportsInterface(address, "0x80ac58cd", provider); ``` **Never assume** a contract supports an interface without checking — always use EIP-165 for robust integrations. ## EIP-173 — Contract Ownership Standard EIP-173 standardizes contract ownership with a `owner()` getter and `transferOwnership()` function. ```solidity interface IERC173 { event OwnershipTransferred( address indexed previousOwner, address indexed newOwner ); function owner() external view returns (address); function transferOwnership(address newOwner) external; } ``` ### OpenZeppelin Ownable2Step (Recommended) Always use two-step ownership transfer to prevent accidentally setting owner to wrong address: ```solidity import "@openzeppelin/contracts/access/Ownable2Step.sol"; contract MyContract is Ownable2Step { constructor() Ownable(msg.sender) {} function adminFunction() external onlyOwner { // Only owner can call this } } // Transfer: owner calls transferOwnership(newOwner) // Then: newOwner must call acceptOwnership() // This prevents accidental loss of ownership ``` ### Renouncing Ownership ```solidity // Permanently removes ownership — use with extreme caution contract.renounceOwnership(); // Sets owner to address(0) ``` **Common pattern**: Renounce ownership after deploying to make a contract truly immutable and trustless. ## ERC-1820 — Pseudo-introspection Registry ERC-1820 provides a global registry where contracts and addresses can register which interfaces they implement. ```solidity interface IERC1820Registry { function setInterfaceImplementer( address account, bytes32 interfaceHash, address implementer ) external; function getInterfaceImplementer( address account, bytes32 interfaceHash ) external view returns (address); function interfaceHash(string calldata interfaceName) external pure returns (bytes32); } // Singleton at same address on all chains: // 0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24 ``` **When to use**: ERC-777 token hooks rely on ERC-1820 (legacy). For new contracts, prefer EIP-165 directly. ## ERC-3156 — Flash Loans ERC-3156 standardizes flash loan interfaces for both lenders and borrowers. ```solidity interface IERC3156FlashLender { // Maximum flash loan amount for a token function maxFlashLoan(address token) external view returns (uint256); // Fee for flash loan (in token units) function flashFee(address token, uint256 amount) external view returns (uint256); // Initiate flash loan function flashLoan( IERC3156FlashBorrower receiver, address token, uint256 amount, bytes calldata data ) external returns (bool); } interface IERC3156FlashBorrower { // Called by lender during flash loan function onFlashLoan( address initiator, // Who initiated the loan address token, // Token borrowed uint256 amount, // Amount borrowed uint256 fee, // Fee to repay bytes calldata data // Arbitrary data ) external returns (bytes32); // Must return keccak256("ERC3156FlashBorrower.onFlashLoan") } ``` ### Flash Loan Borrower Implementation ```solidity import "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol"; contract MyArbitrageur is IERC3156FlashBorrower { bytes32 private constant CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan"); IERC3156FlashLender public immutable lender; constructor(address _lender) { lender = IERC3156FlashLender(_lender); } function executeArbitrage(address token, uint256 amount) external { // 1. Initiate flash loan lender.flashLoan(this, token, amount, abi.encode(/* params */)); } function onFlashLoan( address initiator, address token, uint256 amount, uint256 fee, bytes calldata data ) external override returns (bytes32) { require(msg.sender == address(lender), "Untrusted lender"); require(initiator == address(this), "Untrusted initiator"); // 2. Use the funds (arbitrage, liquidation, etc.) // ... your logic here ... // 3. Approve repayment (amount + fee) IERC20(token).approve(address(lender), amount + fee); return CALLBACK_SUCCESS; } } ``` ### Flash Loan Security ```solidity // Always validate both msg.sender and initiator require(msg.sender == address(lender), "Untrusted lender"); require(initiator == address(this), "External call not allowed"); // Reentrancy protection modifier nonReentrant() { ... } // Verify repayment in lender contract require( IERC20(token).balanceOf(address(this)) >= balanceBefore + fee, "Flash loan not repaid" ); ``` ## ERC-1363 — Payable Token ERC-1363 extends ERC-20 to allow contracts to receive tokens and be notified in a single transaction (like ETH `receive()` for tokens). ```solidity interface IERC1363 is IERC20 { // Transfer tokens and call receiver's onTransferReceived function transferAndCall(address to, uint256 amount) external returns (bool); function transferAndCall(address to, uint256 amount, bytes calldata data) external returns (bool); // TransferFrom and call receiver function transferFromAndCall(address from, address to, uint256 amount) external returns (bool); function transferFromAndCall(address from, address to, uint256 amount, bytes calldata data) external returns (bool); // Approve and call spender's onApprovalReceived function approveAndCall(address spender, uint256 amount) external returns (bool); function approveAndCall(address spender, uint256 amount, bytes calldata data) external returns (bool); } interface IERC1363Receiver { function onTransferReceived( address operator, address from, uint256 amount, bytes calldata data ) external returns (bytes4); // Must return 0x88a7ca5c } ``` **Use case**: Token-powered subscriptions, single-step token deposits to contracts, token-gated access. ## ERC-3668 — CCIP-Read (Off-Chain Lookups) ERC-3668 (Cross-Chain Interoperability Protocol) standardizes off-chain data lookups for contracts. Enables ENS wildcard resolution and off-chain data without trusting a centralized gateway. ```solidity // Contract signals off-chain lookup needed by reverting with: error OffchainLookup( address sender, string[] urls, // HTTPS gateway URLs to query bytes callData, // Data to send to gateway bytes4 callbackFunction, // Function to call with response bytes extraData // Forwarded to callback ); // Callback function receives gateway response function resolveWithProof( bytes calldata response, bytes calldata extraData ) external view returns (bytes memory); ``` **Flow**: Client calls contract → Contract reverts with `OffchainLookup` → Client queries gateway → Client calls callback with proof → Contract verifies and returns data. **Use cases**: ENS L2 resolution, off-chain NFT metadata verification, cross-chain data attestations. ## Interface IDs Quick Reference ```typescript const INTERFACE_IDS = { ERC165: "0x01ffc9a7", ERC173: "0x7f5828d0", // ownerOf + transferOwnership ERC721: "0x80ac58cd", ERC721Metadata: "0x5b5e139f", ERC721Enumerable: "0x780e9d63", ERC1155: "0xd9b67a26", ERC1155Meta: "0x0e89341c", ERC2981: "0x2a55205a", ERC4906: "0x49064906", ERC4907: "0xad092b5c", ERC5192: "0xb45a3c0e", }; ```