0
# Introspection
1
2
OpenZeppelin provides interface detection and registry patterns including ERC165 for interface introspection and ERC1820 for registry-based interface implementation discovery. These utilities enable dynamic interface detection and service discovery in smart contracts.
3
4
## Capabilities
5
6
### ERC165 - Interface Detection Standard
7
8
Standard interface detection mechanism that allows contracts to declare which interfaces they support.
9
10
```solidity { .api }
11
/**
12
* Interface of the ERC165 standard as defined in the EIP
13
*/
14
interface IERC165 {
15
/**
16
* Returns true if this contract implements the interface defined by interfaceId
17
*/
18
function supportsInterface(bytes4 interfaceId) external view returns (bool);
19
}
20
21
/**
22
* Implementation of the IERC165 interface
23
*/
24
abstract contract ERC165 is IERC165 {
25
/**
26
* See IERC165.supportsInterface
27
*/
28
function supportsInterface(bytes4 interfaceId) public view virtual override returns (bool);
29
}
30
```
31
32
**Usage Example:**
33
34
```solidity
35
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
36
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
37
38
contract MyNFT is ERC165, IERC721 {
39
// ERC721 implementation...
40
41
function supportsInterface(bytes4 interfaceId)
42
public view virtual override(ERC165, IERC165) returns (bool)
43
{
44
return
45
interfaceId == type(IERC721).interfaceId ||
46
super.supportsInterface(interfaceId);
47
}
48
}
49
```
50
51
### ERC165Checker - Interface Detection Utility
52
53
Library to query support of an interface declared via ERC165 by a contract.
54
55
```solidity { .api }
56
/**
57
* Library used to query support of an interface declared via ERC165
58
*/
59
library ERC165Checker {
60
/**
61
* Returns true if contract supports the ERC165 interface
62
*/
63
function supportsERC165(address account) internal view returns (bool);
64
65
/**
66
* Returns true if contract supports the given interface
67
*/
68
function supportsInterface(address account, bytes4 interfaceId) internal view returns (bool);
69
70
/**
71
* Returns a boolean array where each value corresponds to the interfaces passed in
72
*/
73
function getSupportedInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool[] memory);
74
75
/**
76
* Returns true if contract supports all the given interfaces
77
*/
78
function supportsAllInterfaces(address account, bytes4[] memory interfaceIds) internal view returns (bool);
79
}
80
```
81
82
**Usage Example:**
83
84
```solidity
85
import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
86
87
contract InterfaceDetector {
88
using ERC165Checker for address;
89
90
function checkNFTSupport(address contractAddr) external view returns (bool) {
91
return contractAddr.supportsInterface(type(IERC721).interfaceId);
92
}
93
94
function checkMultipleInterfaces(address contractAddr) external view returns (bool) {
95
bytes4[] memory interfaces = new bytes4[](2);
96
interfaces[0] = type(IERC721).interfaceId;
97
interfaces[1] = type(IERC721Metadata).interfaceId;
98
99
return contractAddr.supportsAllInterfaces(interfaces);
100
}
101
102
function getInterfaceSupport(address contractAddr) external view returns (bool[] memory) {
103
bytes4[] memory interfaces = new bytes4[](3);
104
interfaces[0] = type(IERC20).interfaceId;
105
interfaces[1] = type(IERC721).interfaceId;
106
interfaces[2] = type(IERC1155).interfaceId;
107
108
return ERC165Checker.getSupportedInterfaces(contractAddr, interfaces);
109
}
110
}
111
```
112
113
### ERC1820Implementer - Registry Interface Implementation
114
115
Base contract for ERC1820 implementer contracts that handle interface implementations.
116
117
```solidity { .api }
118
/**
119
* Implementation of the IERC1820Implementer interface
120
*/
121
contract ERC1820Implementer is IERC1820Implementer {
122
/**
123
* Bytes32 storage slot for mapping interface name hashes to implementer addresses
124
*/
125
mapping(bytes32 => bool) private _supportedInterfaces;
126
127
/**
128
* See IERC1820Implementer.canImplementInterfaceForAddress
129
*/
130
function canImplementInterfaceForAddress(bytes32 interfaceHash, address account)
131
external view virtual override returns (bytes32);
132
133
/**
134
* Declares the contract as willing to be an implementer of interfaceHash for account
135
*/
136
function _registerInterfaceForAddress(bytes32 interfaceHash, address account) internal virtual;
137
138
/**
139
* Sets or unsets an interface for the calling account
140
*/
141
function _setInterface(string memory interfaceName, bool canImplement) internal;
142
}
143
```
144
145
### IERC1820Implementer - ERC1820 Implementer Interface
146
147
Interface for ERC1820 implementer contracts.
148
149
```solidity { .api }
150
/**
151
* Interface for an ERC1820 implementer
152
*/
153
interface IERC1820Implementer {
154
/**
155
* Returns a special value (ERC1820_ACCEPT_MAGIC) if this contract implements interfaceHash for account
156
*/
157
function canImplementInterfaceForAddress(bytes32 interfaceHash, address account) external view returns (bytes32);
158
}
159
```
160
161
### IERC1820Registry - ERC1820 Registry Interface
162
163
Interface for the global ERC1820 registry contract.
164
165
```solidity { .api }
166
/**
167
* Interface of the global ERC1820 Registry
168
*/
169
interface IERC1820Registry {
170
/**
171
* Sets newManager as the manager for account
172
*/
173
function setManager(address account, address newManager) external;
174
175
/**
176
* Returns the manager for account
177
*/
178
function getManager(address account) external view returns (address);
179
180
/**
181
* Sets the implementer contract for interfaceHash and account to implementer
182
*/
183
function setInterfaceImplementer(address account, bytes32 _interfaceHash, address implementer) external;
184
185
/**
186
* Returns the implementer of interfaceHash for account
187
*/
188
function getInterfaceImplementer(address account, bytes32 _interfaceHash) external view returns (address);
189
190
/**
191
* Computes the keccak256 hash of an interface name
192
*/
193
function interfaceHash(string calldata interfaceName) external pure returns (bytes32);
194
195
/**
196
* Updates the cache with whether the contract implements an ERC165 interface or not
197
*/
198
function updateERC165Cache(address account, bytes4 interfaceId) external;
199
200
/**
201
* Checks whether a contract implements an ERC165 interface or not
202
*/
203
function implementsERC165Interface(address account, bytes4 interfaceId) external view returns (bool);
204
205
/**
206
* Checks whether a contract implements an ERC165 interface or not without using nor updating the cache
207
*/
208
function implementsERC165InterfaceNoCache(address account, bytes4 interfaceId) external view returns (bool);
209
210
event InterfaceImplementerSet(address indexed account, bytes32 indexed interfaceHash, address indexed implementer);
211
event ManagerChanged(address indexed account, address indexed newManager);
212
}
213
```
214
215
## Introspection Patterns
216
217
### Dynamic Interface Detection
218
219
```solidity
220
import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
221
222
contract UniversalTokenHandler {
223
using ERC165Checker for address;
224
225
bytes4 private constant IERC20_INTERFACE_ID = 0x36372b07;
226
bytes4 private constant IERC721_INTERFACE_ID = 0x80ac58cd;
227
bytes4 private constant IERC1155_INTERFACE_ID = 0xd9b67a26;
228
229
enum TokenType { UNKNOWN, ERC20, ERC721, ERC1155 }
230
231
function detectTokenType(address token) public view returns (TokenType) {
232
if (token.supportsInterface(IERC1155_INTERFACE_ID)) {
233
return TokenType.ERC1155;
234
} else if (token.supportsInterface(IERC721_INTERFACE_ID)) {
235
return TokenType.ERC721;
236
} else if (token.supportsInterface(IERC20_INTERFACE_ID)) {
237
return TokenType.ERC20;
238
} else {
239
return TokenType.UNKNOWN;
240
}
241
}
242
243
function handleTokenTransfer(
244
address token,
245
address from,
246
address to,
247
uint256 amount
248
) external {
249
TokenType tokenType = detectTokenType(token);
250
251
if (tokenType == TokenType.ERC20) {
252
IERC20(token).transferFrom(from, to, amount);
253
} else if (tokenType == TokenType.ERC721) {
254
IERC721(token).transferFrom(from, to, amount);
255
} else if (tokenType == TokenType.ERC1155) {
256
IERC1155(token).safeTransferFrom(from, to, amount, 1, "");
257
} else {
258
revert("Unsupported token type");
259
}
260
}
261
}
262
```
263
264
### Custom Interface Registration
265
266
```solidity
267
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
268
269
interface ICustomService {
270
function processData(bytes calldata data) external returns (bool);
271
function getServiceInfo() external view returns (string memory);
272
}
273
274
contract CustomServiceProvider is ERC165, ICustomService {
275
string private _serviceInfo;
276
277
constructor(string memory serviceInfo) {
278
_serviceInfo = serviceInfo;
279
}
280
281
function processData(bytes calldata data) external override returns (bool) {
282
// Service implementation
283
return true;
284
}
285
286
function getServiceInfo() external view override returns (string memory) {
287
return _serviceInfo;
288
}
289
290
function supportsInterface(bytes4 interfaceId)
291
public view virtual override returns (bool)
292
{
293
return
294
interfaceId == type(ICustomService).interfaceId ||
295
super.supportsInterface(interfaceId);
296
}
297
}
298
299
contract ServiceConsumer {
300
using ERC165Checker for address;
301
302
function useServiceIfSupported(address provider, bytes calldata data) external {
303
if (provider.supportsInterface(type(ICustomService).interfaceId)) {
304
ICustomService service = ICustomService(provider);
305
bool success = service.processData(data);
306
require(success, "Service processing failed");
307
}
308
}
309
}
310
```
311
312
### ERC1820 Registry Usage
313
314
```solidity
315
import "@openzeppelin/contracts/utils/introspection/ERC1820Implementer.sol";
316
317
// Registry address (deployed once per network)
318
IERC1820Registry constant ERC1820_REGISTRY = IERC1820Registry(0x1820a4B7618BdE71Dce8cdc73aAB6C95905faD24);
319
320
interface IDataProcessor {
321
function processUserData(bytes calldata data) external returns (bytes memory);
322
}
323
324
contract DataProcessorImplementer is ERC1820Implementer, IDataProcessor {
325
bytes32 private constant DATA_PROCESSOR_HASH = keccak256("DataProcessor");
326
327
constructor() {
328
_setInterface("DataProcessor", true);
329
}
330
331
function processUserData(bytes calldata data) external override returns (bytes memory) {
332
// Processing logic
333
return abi.encode("Processed:", data);
334
}
335
336
function canImplementInterfaceForAddress(bytes32 interfaceHash, address account)
337
external view virtual override returns (bytes32)
338
{
339
if (interfaceHash == DATA_PROCESSOR_HASH) {
340
return ERC1820_ACCEPT_MAGIC;
341
} else {
342
return bytes32(0x00);
343
}
344
}
345
}
346
347
contract DataUser {
348
bytes32 private constant DATA_PROCESSOR_HASH = keccak256("DataProcessor");
349
350
function processData(bytes calldata data) external returns (bytes memory) {
351
address implementer = ERC1820_REGISTRY.getInterfaceImplementer(
352
address(this),
353
DATA_PROCESSOR_HASH
354
);
355
356
require(implementer != address(0), "No data processor registered");
357
358
return IDataProcessor(implementer).processUserData(data);
359
}
360
361
function setDataProcessor(address processor) external {
362
ERC1820_REGISTRY.setInterfaceImplementer(
363
address(this),
364
DATA_PROCESSOR_HASH,
365
processor
366
);
367
}
368
}
369
```
370
371
### Plugin Architecture with Interface Detection
372
373
```solidity
374
import "@openzeppelin/contracts/utils/introspection/ERC165.sol";
375
import "@openzeppelin/contracts/access/Ownable.sol";
376
377
interface IPlugin {
378
function getPluginName() external view returns (string memory);
379
function execute(bytes calldata data) external returns (bool);
380
}
381
382
contract PluginManager is Ownable {
383
using ERC165Checker for address;
384
385
address[] public plugins;
386
mapping(address => bool) public isRegistered;
387
388
event PluginRegistered(address indexed plugin, string name);
389
event PluginRemoved(address indexed plugin);
390
391
function registerPlugin(address plugin) external onlyOwner {
392
require(plugin.supportsInterface(type(IPlugin).interfaceId), "Not a valid plugin");
393
require(!isRegistered[plugin], "Plugin already registered");
394
395
plugins.push(plugin);
396
isRegistered[plugin] = true;
397
398
string memory name = IPlugin(plugin).getPluginName();
399
emit PluginRegistered(plugin, name);
400
}
401
402
function removePlugin(address plugin) external onlyOwner {
403
require(isRegistered[plugin], "Plugin not registered");
404
405
for (uint i = 0; i < plugins.length; i++) {
406
if (plugins[i] == plugin) {
407
plugins[i] = plugins[plugins.length - 1];
408
plugins.pop();
409
break;
410
}
411
}
412
413
isRegistered[plugin] = false;
414
emit PluginRemoved(plugin);
415
}
416
417
function executePlugin(address plugin, bytes calldata data) external returns (bool) {
418
require(isRegistered[plugin], "Plugin not registered");
419
return IPlugin(plugin).execute(data);
420
}
421
422
function executeAllPlugins(bytes calldata data) external returns (bool[] memory) {
423
bool[] memory results = new bool[](plugins.length);
424
425
for (uint i = 0; i < plugins.length; i++) {
426
results[i] = IPlugin(plugins[i]).execute(data);
427
}
428
429
return results;
430
}
431
432
function getPluginCount() external view returns (uint256) {
433
return plugins.length;
434
}
435
436
function getPluginInfo() external view returns (address[] memory, string[] memory) {
437
string[] memory names = new string[](plugins.length);
438
439
for (uint i = 0; i < plugins.length; i++) {
440
names[i] = IPlugin(plugins[i]).getPluginName();
441
}
442
443
return (plugins, names);
444
}
445
}
446
447
contract ExamplePlugin is ERC165, IPlugin {
448
string private _name;
449
450
constructor(string memory name) {
451
_name = name;
452
}
453
454
function getPluginName() external view override returns (string memory) {
455
return _name;
456
}
457
458
function execute(bytes calldata data) external override returns (bool) {
459
// Plugin-specific logic
460
return true;
461
}
462
463
function supportsInterface(bytes4 interfaceId)
464
public view virtual override returns (bool)
465
{
466
return
467
interfaceId == type(IPlugin).interfaceId ||
468
super.supportsInterface(interfaceId);
469
}
470
}
471
```
472
473
### Advanced Interface Detection
474
475
```solidity
476
import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
477
478
contract AdvancedInterfaceDetector {
479
using ERC165Checker for address;
480
481
struct InterfaceInfo {
482
bytes4 interfaceId;
483
string name;
484
bool required;
485
}
486
487
mapping(string => InterfaceInfo[]) public protocolInterfaces;
488
489
function defineProtocol(
490
string memory protocolName,
491
bytes4[] memory interfaceIds,
492
string[] memory interfaceNames,
493
bool[] memory required
494
) external {
495
require(
496
interfaceIds.length == interfaceNames.length &&
497
interfaceIds.length == required.length,
498
"Array length mismatch"
499
);
500
501
delete protocolInterfaces[protocolName];
502
503
for (uint i = 0; i < interfaceIds.length; i++) {
504
protocolInterfaces[protocolName].push(InterfaceInfo({
505
interfaceId: interfaceIds[i],
506
name: interfaceNames[i],
507
required: required[i]
508
}));
509
}
510
}
511
512
function checkProtocolCompliance(
513
address contractAddr,
514
string memory protocolName
515
) external view returns (bool isCompliant, string[] memory missing) {
516
InterfaceInfo[] memory interfaces = protocolInterfaces[protocolName];
517
string[] memory temp = new string[](interfaces.length);
518
uint missingCount = 0;
519
520
for (uint i = 0; i < interfaces.length; i++) {
521
bool supports = contractAddr.supportsInterface(interfaces[i].interfaceId);
522
523
if (!supports && interfaces[i].required) {
524
temp[missingCount] = interfaces[i].name;
525
missingCount++;
526
}
527
}
528
529
isCompliant = (missingCount == 0);
530
missing = new string[](missingCount);
531
532
for (uint i = 0; i < missingCount; i++) {
533
missing[i] = temp[i];
534
}
535
}
536
}
537
```