Skip to content

Commit 240707d

Browse files
makotoArachnid
andauthored
Minimum changes (#57)
* Apply ENSIP 16 changes from PR 41 * Replace graphql with rpcURLs * Add events information * Add more context * Fixed typo * Add requirements, event schema standardization, and key terminology * Add requirements * Add requirements * Add recommendation for GraphQL API recommendation * Change format * Remove graphql api * Apply suggestions from code review Co-authored-by: Nick Johnson <[email protected]> * Change from three to two * Change from SHOULD to MUST * Remove link to namechain and v2 design doc * Add TokenRegenerated event * Remove expiry from SubregistryUpdate * Rename NewSubname to NameRegistered * Add comment about no expiration * Simplify ENSIP 16 * Made some adjustment * Adjustment * Add rpcurl events including reverse registrar events * Minor changes * Fix typo * Rename resource to context, Remove ReverseResolver events, add NameChanged * Update explanation of context * Included the feedback * Remove updatedBy --------- Co-authored-by: Nick Johnson <[email protected]>
1 parent f201be5 commit 240707d

File tree

1 file changed

+135
-130
lines changed

1 file changed

+135
-130
lines changed

ensips/16.md

Lines changed: 135 additions & 130 deletions
Original file line numberDiff line numberDiff line change
@@ -1,185 +1,190 @@
11
---
2-
description: Allows metadata to be queried on EIP-3668 enabled names
2+
description: Provides a discovery mechanism for metadata for names resolved via EIP-3668
33
contributors:
44
- jefflau
5-
- makoto
5+
- matoken.eth
66
ensip:
77
status: draft
88
created: 2022-09-22
9+
updated: 2025-11-03
910
ignoredRules: ["heading:implementation", "heading:open-items"]
1011
---
1112

12-
# ENSIP-16: Offchain Metadata
13+
# ENSIP-16: Metadata Event Discovery
1314

1415
## Abstract
1516

16-
This ENSIP specifies APIs for querying metadata directly on the resolver for EIP-3668 (CCIP Read: Secure offchain data retrieval) enabled names. EIP-3668 will power many of the domains in the future, however since the retrieval mechanism uses wildcard + offchain resolver, there is no standardised way to retrieve important metadata information such as the owner (who can change the records), or which L2/offchain database the records are stored on.
17+
This ENSIP specifies APIs for querying metadata directly on the resolver for EIP-3668 (CCIP Read: Secure offchain data retrieval) enabled names. EIP-3668 will power many domains in the future, however since the retrieval mechanism uses wildcard + offchain resolver, there is no standardised way to retrieve important metadata information such as which L2/offchain database the records are stored on and where JSON RPC endpoint is to find event log information.
1718

1819
## Motivation
1920

20-
With EIP-3668 subdomains already starting to see wide adoption, it is important that there is a way for frontend interfaces to get important metadata to allow a smooth user experience. For instance a UI needs to be able to check if the currently connected user has the right to update an EIP-3668 name.
21+
With EIP-3668 subdomains already starting to see wide adoption, it is important that there is a standardised way to discover and access metadata about offchain names.
2122

22-
This ENSIP addresses this by adding a way of important metadata to be gathered on the offchain resolver, which would likely revert and be also resolved offchain, however there is an option for it to be also left onchain if there value was static and wouldn't need to be changed often.
23+
This ENSIP allows third-party indexing services to listen to the `MetadataChanged` event to automatically discover and index metadata from different chains and smart contracts, without relying on centralised RPC endpoint repositories.
2324

2425
## Specification
2526

26-
The metadata should include 2 different types of info
27-
28-
- Offchain data storage location related info: `graphqlUrl` includes the URL to fetch the metadata.
29-
30-
- Ownership related info: `owner`, `isApprovedForAll` defines who can own or update the given record.
31-
32-
### Context
33-
34-
An optional field "context" is introduced by utilizing an arbitrary bytes string to define the namespace to which a record belongs.
35-
36-
For example, this "context" can refer to the address of the entity that has set a particular record. By associating records with specific addresses, users can confidently manage their records in a trustless manner on Layer 2 without direct access to the ENS Registry contract on the Ethereum mainnet. Please refer to [ENS-Bedrock-Resolver](https://github.com/corpus-io/ENS-Bedrock-Resolver#context) for the reference integration
37-
38-
### Dynamic Metadata
39-
40-
Metadata serves a crucial role in providing valuable insights about a node owner and their specific resolver. In certain scenarios, resolvers may choose to adopt diverse approaches to resolve data based on the node. An example of this would be handling subdomains of a particular node differently. For instance, we could resolve "optimism.foo.eth" using a contract on optimism and "gnosis.foo.eth" using a contract on gnosis.
41-
By passing the name through metadata, we empower the resolution process, enabling CcipResolve flows to become remarkably flexible and scalable. This level of adaptability ensures that our system can accommodate a wide array of use cases, making it more user-friendly and accommodating for a diverse range of scenarios.
42-
43-
## Implementation
44-
45-
### L1
27+
Compliant resolvers MUST implement the following interface:
4628

4729
```solidity
4830
// To be included in
4931
// https://github.com/ensdomains/ens-contracts/blob/staging/contracts/resolvers/Resolver.sol
50-
interface IOffChainResolver {
51-
/** @dev Returns the owner of the resolver on L2
52-
* @param node
53-
* @return owner in bytes32 instead of address to cater for non EVM based owner information
54-
*/
55-
owner(bytes32 node) returns (bytes owner);
56-
57-
// optional.
58-
// this returns data via l2 with EIP-3668 so that non EVM chains can also return information of which address can update the record
59-
// The same function name exists on L2 where delegate returns address instead of bytes
60-
function isApprovedFor(bytes context, bytes32 node, bytes delegate) returns (bool);
32+
interface IOffchainResolverMetadataProvider {
6133
62-
/** @dev Returns the metadata of the resolver on L2
63-
* @return graphqlUrl url of graphql endpoint that provides additional information about the offchain name and its subdomains
34+
/**
35+
* @dev Returns metadata for discovering the location of offchain name data
36+
* @param name DNS-encoded name to query
37+
* @return rpcURLs The JSON RPC endpoint for querying offchain data (optional, may be empty array)
38+
* @return chainId The chain ID where the data is stored (format for non-EVM systems to be determined)
39+
* @return baseRegistry The base registry address on the target chain that emits events (optional, may be zero address)
6440
*/
6541
function metadata(bytes calldata name)
6642
external
6743
view
68-
returns (string memory)
69-
{
70-
return (graphqlUrl);
71-
}
44+
returns (
45+
string[] memory rpcURLs,
46+
uint256 chainId,
47+
address baseRegistry
48+
);
7249
73-
// Optional. If context is dynamic, the event won't be emitted.
7450
event MetadataChanged(
75-
string name,
76-
string graphqlUrl,
51+
bytes name, // DNS-encoded name
52+
string[] rpcURLs, // JSON RPC endpoint (optional, may be empty array)
53+
uint256 chainId, // Chain identifier (format for non-EVM systems to be determined)
54+
address baseRegistry // Base registry address (optional, may be zero address)
7755
);
7856
}
7957
```
8058

81-
### L2 (EVM compatible chain only)
82-
83-
```solidity
84-
// To be included in the contract returned by `metadata` function `storageLocation`
85-
interface IL2Resolver {
86-
/**
87-
* @dev Check to see if the delegate has been approved by the context for the node.
88-
*
89-
* @param context = an arbitrary bytes string to define the namespace to which a record belongs such as the name owner.
90-
* @param node
91-
* @param delegate = an address that is allowed to update record under context
92-
*/
93-
function isApprovedFor(bytes context,bytes32 node,address delegate) returns (bool);
59+
**Requirements:**
60+
- `name` must be provided to call `metadata` function. When indexing `MetadataChanged` event, indexers MUST apply the `name` to all names that have the suffixes of the `name`.
61+
- `chainId` must be provided. For EVM-compatible chains, `chainId` SHOULD match the chain's EIP-155 identifier. A chainId of 0 means a non-EVM chain or off-chain database.
62+
- `rpcURLs` is optional and may be an empty array.
63+
- `baseRegistry` is optional and may be the zero address. If a non-zero address is specified, events will be emitted from this address. The interface of this contract is yet to be determined.
9464

95-
event Approved(
96-
bytes context,
97-
bytes32 indexed node,
98-
address indexed delegate,
99-
bool indexed approved
100-
);
101-
}
102-
```
65+
### metadata function
10366

104-
```javascript
105-
const node = namehash('ccipreadsub.example.eth')
106-
const resolver = await ens.resolver(node)
107-
const owner = await resolver.owner(node)
108-
// 0x123...
109-
const dataLocation = await.resolver.graphqlUrl()
110-
// {
111-
// url: 'http://example.com/ens/graphql',
112-
// }
113-
```
67+
The metadata function allows resolvers to dynamically provide information about where offchain data for a given name can be queried. This function returns the same information as would be emitted in a MetadataChanged event: the JSON RPC endpoint URLs, the chain ID where the data resides, and the base registry address on the target chain.
11468

115-
#### GraphQL schema
69+
### MetadataChanged Event
11670

117-
[GraphQL](https://graphql.org) is a query language for APIs and a runtime for fulfilling those queries with onchain event data. You can use the hosted/decentralised indexing service such as [The Graph](https://thegraph.com), [Goldsky](https://docs.goldsky.com/introduction), [QuickNode](https://marketplace.quicknode.com/add-on/subgraph-hosting) or host your own using The Graph, or [ponder](https://ponder.sh)
71+
The MetadataChanged event emits the same information the `metadata` function returns so that indexers can subscribe to the event as the details change rather than periodically querying the function.
11872

119-
#### L1
73+
**Key Terminology:**
74+
- **registry** = A contract that manages name ownership and hierarchical relationships for a set of subnames. The base registry manages top-level domains (also known as root registry within the v2 contract), while subregistries manage names under a specific parent
75+
- **subregistry** = A registry contract that manages subnames under a parent name. Linked from a parent registry via SubregistryUpdate events
76+
- **registry id/tokenId** = The unique identifier for a name NFT, derived from the labelhash and version id that increments every time the permission of the name changes effectively invalidating any permissions or approvals tied to the old token ID.
77+
- **EAC(Enhanced Access Control)** = a general-purpose access control base class. `resource` is a unique key to manage the resource which changes every time `tokenId` changes. For more detail, read [the namechain README](https://github.com/ensdomains/namechain/tree/main/contracts#access-control). EAC may not be the only way to manage access control and other events may be added in future.
78+
- **node** = In v1, node is a unique identifier of the name derived by a `namehash` logic. In ENS v2, `node` is a placeholder node for compatibility with standard resolver behavior and always set to 0. The full ENS name attached to the resolver needs to be reconstructed by traversing registry hierarchy set by `SubregistryUpdate`.
12079

121-
`Metadata` is an optional schema that indexes `MetadataChanged` event.
80+
### rpcUrls Events
12281

123-
```graphql
82+
`rpcUrls` url endpoint emits the following jsonrpc events when chainId is not `0`. When chainId is `0` it may emit custom events yet to be determined.
12483

125-
type Domain @entity{
126-
id
127-
metadata: Metadata
128-
}
84+
#### Registry Events
12985

130-
type Metadata @entity {
131-
"l1 resolver address"
132-
id: ID!
133-
"Name of the Chain"
134-
name: String
135-
"url of the graphql endpoint"
136-
graphqlUrl: String
137-
}
86+
```solidity
87+
// Emitted when a new subname is registered.
88+
// A subname without expiration should set type(uint256).max
89+
// Context can attach any arbitrary data, such as resource id to keep track of EAC
90+
event NameRegistered(
91+
uint256 indexed tokenId,
92+
string label,
93+
uint64 expiration,
94+
address registeredBy,
95+
uint256 context
96+
);
97+
98+
// Emitted when a new token id is generated
99+
event TokenRegenerated(
100+
uint256 oldTokenId,
101+
uint256 newTokenId,
102+
uint256 context
103+
);
104+
105+
// Standard ERC1155 transfer event for name ownership changes
106+
event TransferSingle(
107+
address indexed operator,
108+
address indexed from,
109+
address indexed to,
110+
uint256 id,
111+
uint256 value // must always be 1
112+
);
113+
114+
// Standard ERC1155 transfer event for multiple name ownership changes
115+
event TransferBatch(
116+
address indexed operator,
117+
address indexed from,
118+
address indexed to,
119+
uint256[] ids,
120+
uint256[] values
121+
);
122+
123+
// Emitted when a name is renewed
124+
125+
event ExpiryUpdated(
126+
uint256 indexed tokenId,
127+
uint64 newExpiration
128+
);
129+
130+
// Emitted when subregistry is updated
131+
event SubregistryUpdated(
132+
uint256 indexed id,
133+
address subregistry
134+
);
135+
136+
// Emitted when resolver is updated
137+
event ResolverUpdated(
138+
uint256 indexed id,
139+
address resolver
140+
);
138141
139142
```
140143

141-
#### L2
142-
143-
L2 graphql URL is discoverable via `metadata` function `graphqlUrl` field.
144-
Because the canonical ownership of the name exists on L1, some L2/offchain storage may choose to allow multiple entities to update the same node namespaced by `context`. When querying the domain data, the query should be filtered by `context` that is returned by `metadata`function `context` field
145-
146-
```graphql
147-
type Domain {
148-
id: ID! # concatenation of context and namehash delimited by `-`
149-
context: Bytes
150-
name: String
151-
namehash: Bytes
152-
labelName: String
153-
labelhash: Bytes
154-
resolvedAddress: Bytes
155-
parent: Domain
156-
subdomains: [Domain]
157-
subdomainCount: Int!
158-
resolver: Resolver!
159-
expiryDate: BigInt
160-
}
144+
#### Resolver Events
161145

162-
type Resolver @entity {
163-
id: ID! # concatenation of node, resolver address and context delimited by `-`
164-
node: Bytes
165-
context: Bytes
166-
address: Bytes
167-
domain: Domain
168-
addr: Bytes
169-
contentHash: Bytes
170-
texts: [String!]
171-
coinTypes: [BigInt!]
172-
}
146+
```solidity
147+
event AddressChanged(
148+
bytes32 indexed node,
149+
uint256 coinType,
150+
bytes newAddress
151+
);
152+
153+
event AddrChanged(
154+
bytes32 indexed node,
155+
address a
156+
);
157+
158+
event TextChanged(
159+
bytes32 indexed node,
160+
string indexed indexedKey,
161+
string key,
162+
string value
163+
);
164+
165+
event ContenthashChanged(
166+
bytes32 indexed node,
167+
bytes hash
168+
);
169+
170+
event NameChanged(
171+
bytes32 indexed node,
172+
string name)
173+
;
173174
```
174175

175-
## Backwards Compatibility
176+
## Rationale
177+
178+
This ENSIP addresses a key limitation of EIP-3668: while it enables offchain data retrieval, it provides no standardized way for third parties to discover where that data lives or how to index it.
179+
180+
By providing metadata at the resolver level, this ENSIP enables:
176181

177-
None
182+
1. **Automatic indexer discovery** - Indexers can discover new L2/offchain data sources by listening to events, without requiring manual configuration or centralized registries of RPC endpoints.
178183

179-
## Open Items
184+
2. **Flexible data sources** - The optional nature of `rpcURLs` and `baseRegistry` accommodates different deployment patterns: from fully decentralized L2 registries that emit events, to custom databases that may emit APIs yet to be determined that are more suitable to index non-EVM chains or offchain database names.
180185

181-
- Should `owner` and `isApprovedForAll` be within graphql or should be own metadata function?
186+
3. **Future extensibility** - By requiring `chainId` but leaving the non-EVM format undefined, this ENSIP establishes the pattern while remaining open to future non-EVM integrations.
182187

183188
## Copyright
184189

185-
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
190+
Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).

0 commit comments

Comments
 (0)