How does CCIP Read work on a contract level?

I want to update a little tool I’m working on to allow smart contracts to treat ENS names in a similar way they treat addresses. This would allow someone to make a contract permission or token “soul bound” to an ens name. This would be specially important for DNS integration, since it meant you could declare the owner of your contract to be any website.

But I’d like to update it to CCPI read but I don’t fully understand it. Where can I read how to interact with CCPI read on a contract level? I understand it would mean multiple async calls. Is there documentation somewhere?


Have you read EIP 3668? If you still have questions after that I’m happy to answer them.

1 Like

What Nick said, but TLDR for those just passing by to pique your interest:

  • A smart contract function reverts with a special OffchainLookup(address,string[],bytes,bytes4,bytes) error, which contains the info on where to grab the off-chain data (and potentially some args to keep for later)
  • The client is responsible for going to the provided location and retrieving the data (details in the spec)
  • The client then calls a callback function (named by convention, details in spec) with the off-chain data, a “proof” from the data source (that the data is legit), and possibly those args I mentioned in the first bullet, and the result is the final result of this function call

It’s a clever use of solidity revert semantics, but also unique so understandable it can be confusing! That said, most devs probably won’t have to worry about this much, as ethers already supports doing this flow natively for you as of the latest version :slightly_smiling_face:


Thanks for the replies. And I assume that, in the context of ENS, it will be implemented in the resolver level? Do we have an example working resolver using it? Also, how does that apply to subdomains? If I want to check an address at, will that still exist in the registry, will it be a wildcard resolver on or even a generic TLD resolver for .bar?

1 Like

Yea, at the resolver level.

Here’s one that ENS Core made that demonstrates how to serve off-chain data: GitHub - ensdomains/offchain-resolver

In that example (assuming .bar is a DNS TLD, otherwise assume that is .eth), foo is registered with the ENSRegistrar on L1, the off-chain lookup-enabled resolver is set as the resolver for, and a lookup for would result in a wildcard lookup to the resolver, which will determine how to get records for

So the actual records for could be on an L2, or they could be entirely off-chain. But thanks to wildcard lookups, doesn’t have to appear anywhere on L1.

1 Like

And this is deployed on *.offchainexample.eth.

Check out ENSIP 10.

Just to clarify, the return types of the callback function must be the same as the return types of the original reverting function?

function someFunc() external view returns(foo memory string, bar memory uint, baz memory bool) {
    revert OffchainLookup(

function callback(bytes calldata response, bytes calldata extraData) external view returns(foo memory string, bar memory uint, baz memory bool) {

If callback instead returned just (foo memory string), this would be invalid, correct?

Must the contract which implements resolve() also implement whatever methods are passed into resolve()? Reason I ask is that the pseudocode has this line:

return resolver[func].decodeReturnData(result);

Here, resolver may be set to the ENSIP-10 supporting “resolve” contract, and func would be the original method (eg: addr()). If the parent resolver doesn’t also implement func, this code would fail.

1 Like

That’s right.

That’s right. The client will attempt to decode it with the original function’s return parameters, which may succeed or fail depending on whether the two signatures are abi-compatible.

No; it can choose to implement those methods or not.


Thanks for the clarification.

For ENSIP-10, is there any limit as to what functions can be “resolved” via resolve()? Are we limited to the standard resolver interface functions defined by the ENSIPs, or can clients call resolve(name, someCustomFunction(input))?

Just as with the legacy system, anyone can define new resolver functions - though it’s recommended to standardise them if they’re going to be used widely.

Should ENSIP-10 wildcard resolvers implement the Registry owner, and ttl functions? And should clients check for wildcard resolvers if a domain’s owner == address(0)?

For example, if there is currently no registry owner of a.parent.eth, but parent.eth implements ENSIP-10, would it be correct to say that the registry owner of a.parent.eth is parent.resolve(a.parent.eth, ENS.owner.selector)? Or does a.parent.eth have no registry owner, but it does have a resolver (the parent.eth wildcard resolver)?

No; those are part of the registry ABI, not part of the resolver ABI.

Clients should check parent names if the current name’s resolver is 0; they should not check or care about owner values when resolving names. The exact resolution process is spelled out in ENSIP-10.

a.parent.eth does not have an owner of its own.