Offchain / L2 Resolvers
The source of truth for a name and its subdomains does not always have to be onchain or on Ethereum L1 at all. By leveraging EIP-3668 (CCIP Read) in a Resolver, developers can effectively defer resolution to an L2 or offchain API.
How does CCIP Read work?
CCIP Read (Cross Chain Interoperability Protocol) is a specification that defines a standard error smart contracts can throw if they want to trigger an offchain HTTP request.
error OffchainLookup(
address sender,
string[] urls,
bytes callData,
bytes4 callbackFunction,
bytes extraData
)
When a contract reverts with the OffchainLookup
error, it is up to clients (wagmi, viem, ethers, etc.) to handle it appropriatrely. Thankfully, many clients support CCIP Read out of the box so it's transparent to application developers in most cases.
How does ENS use CCIP Read?
CCIP Read can be used to make ENS name resolution more flexible. Instead of storing all data on Ethereum L1, developers can implement CCIP Read in a Resolver to store data (subnames, records, etc.) on L2 networks or even an offchain database.
To resolve an offchain/L2 name using CCIP Read, the steps are as follows:
- A user types "example.eth" into their wallet.
- The wallet's client calls
resolve()
on example.eth's Resolver. - The Resolver reverts with an
OffchainLookup
error. - The client makes a request to the gateway URL specified in the error with the calldata from the error.
- The gateway processes the request and returns data to the client. This is where the data is fetched from L2 or an offchain database.
- The client calls the callback function specified in the error with the data returned from the gateway, which usually performs some sort of validation.
- If the callback function validates the data, the client returns the result to the user.
While this might sound complex, it all happens under the hood and is completely transparent to application developers.
Offchain Subname Example
An example of offchain ENS names powered by CCIP Read can be found at offchain.ens.gregskril.com.
The name offchaindemo.eth with Resolver 0x35b9...E237, reverts with OffchainLookup
and directs the client to a Gateway URL.
The Gateway returns the relevant information from an offchain database, signed by a trusted private key which the smart contract can verify. This prevents a compromised Gateway from returning false information.
Offchain ENS Subnames
Offchain vs L2 Resolvers
From the perspective of the L1 Resolver contract, the process of resolving an L2 name is exactly the same as resolving on offchain name. The differences come from the Gateway implementation and the Resolver's callback function.
For names that are stored offchain like the example above, the Gateway would read from a normal web2 database and the Resolver's callback function would simply verify the Gateway operator's signature.
For names that are stored on L2, the Gateway would make an RPC call to the relevant L2 and the Resolver's callback function would ideally verify the response by using the L2's state roon on L1 (this assumes knowledge of how L2's work).
To implement trustless L2 resolution, developers should use a solution like Unruggable Gateways.
Solution for fetching proofs of data from rollup chains and verifying that data on Layer 1 Ethereum.
Writing a CCIP Read Gateway
A gateway is an offchain API endpoint that implements the Gateway Interface specified in EIP-3668. It is responsible for decoding the calldata from an OffchainLookup
error and returning a relevant response.
Implementing the Endpoint
Your gateway must implement either a GET
or POST
endpoint with {sender}
and {data}
parameters, and be stored in the implementing smart contract. The OffchainLookup
error will include this URL, which is how the client knows where to send the request.
// POST if URL does not include '{data}' parameter
URL: https://example.com/gateway
Method: POST
Body:
sender: "0x..."
data: "0x..."
- Name
sender
- Type
- address
- Description
Lowercased address of the contract reverting with the
OffchainLookup
error.
- Name
data
- Type
- bytes
- Description
0x prefixed bytes of the data passed to the
OffchainLookup
error.
Example Gateway Implementation
The most basic gateway implementation is to return a static value without doing any signing. We even have a library @ensdomains/ccip-read-router to abstract decoding the calldata.
Trust Assumptions
As explained in Offchain vs L2 Resolvers, trust assumptions are up to the implementing developer and can range from fully trusted to full trustless.
The worst case scenario of a trusted implementation is that a malicious actor gains control of the gateway and can return false information.
The worst case scenario of a trustless implementation is that a malicious actor can take a gateway offline, but it can never return false data.
Writing an Offchain/L2 Resolver
See Writing a Resolver for more information on how to implement a Resolver with CCIP Read.
Testing your offchain names
To test your implementation, search the relevant name in the ENS Manager App. Make sure that you've configured your test name to return a result for common data like an ETH address or common text records like avatar
or description
. If you set an arbitrary text record key like test
, the manager app has no way of knowing that it exists.