Skip to content

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:

  1. A user types "example.eth" into their wallet.
  2. The wallet's client calls resolve() on example.eth's Resolver.
  3. The Resolver reverts with an OffchainLookup error.
  4. The client makes a request to the gateway URL specified in the error with the calldata from the error.
  5. The gateway processes the request and returns data to the client. This is where the data is fetched from L2 or an offchain database.
  6. The client calls the callback function specified in the error with the data returned from the gateway, which usually performs some sort of validation.
  7. 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.

gskril/ens-offchain-registrar

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.

unruggable-labs/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
// 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.

Basic Gateway Implementation

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.