Okay, I think I get it now. Here is my new take using the EVM gateway instead of L2 RPCs. This is a more advanced version of L1Resolver
(from EVM Gateway) where I make 2 requests (to support Registry + Resolver) however the gateway code could be modified to support multiple targets in one request.
My original motivation: I wanted an official registry on L2, not just metadata. I also wanted support for complex resolver contracts (like Wildcard or CCIP).
ENS resolution always starts from L1.
L2 writes would still involve switching to the corresponding chain.
Wildcard or OffchainLookup
resolvers can only be deployed on L1.
Only one contract, just deploy on L1.
L2OnChainResolver
enables wildcard through rewriting + L2 registry which handles most cases.
Deploy a Registry
on each supported L2.
Deploy a ReverseRegistrar
.
(optional) Deploy {uuid}._id
-style UUIDRegistrar
.
Deploy an L2OnChainResolver
to L1
If thereās a node in the L2 registry and the metadata is in an L2 resolver, then you can set the L1 resolver to the singleton L2OnChainResolver
and then call setTarget(node, chain, rewriter)
.
Since ENSIP-10 doesnāt supply the node that has the wildcard contract, the contract needs to also find the base node by popping labels from the path until the registry gives a result.
// similar to UniversalResolver.findResolver()
// but loop untilresolver = address(this)
and return offset instead
uint256 pos = findBaseOffset(name);
bytes basename = name.slice(pos, name.length);
bytes prefix = name.slice(0, pos);
bytes32 basenode = namehashFromDNS(basename);
Use the basenode to query the target:
uint64 chain = ENS.ttl(basenode); // cheap hack
(uint64 chain, address rewriter) = L2OnChainResolver.getTarget(basenode);
rewriter
is a contract address. If it isnāt address(0)
then rewrite the name
:
name = INameRewriter(rewriter).rewrite(name)
.
interface INameRewriter {
// both names are dns-encoded
// pos is byte-offset of basename
function rewrite(bytes memory oldName, uint256 pos) external view returns (bytes memory newName);
}
This allows *[raffy.eth]
to become *[51050ec063d393217b436747617ad1c2285aeeee.addr.reverse]
before being resolved.
setTarget(namehash("raffy.eth"), 10, OwnedRewriter)
The following could be a helper contract to rewrite L1 names to owned L2 names:
contract OwnedRewriter implements INameRewriter {
function rewrite(bytes memory oldName, uint256 pos) external view returns (bytes memory) {
// compute the base node
bytes32 basenode = namehashFromDNS(oldName.slice(pos, oldName.length);
// find the owner
address owner = ENS.owner(basenode);
// replace *[base] with *[addr].addr.reverse
return bytes.concat(oldName.slice(0, pos), dnsEncodedAddr(owner), '\4addr\7reverse');
}
}
Example: addr("sub.raffy.eth", 60)
ENS.resolver(namehash("sub.raffy.eth"))
ānull
ENS.resolver(namehash("raffy.eth'))
āL2OnChainResolver
L2OnChainResolver.resolve(<sub.raffy.eth>, addr(..., 60))
pos = findBasenameOffset(name);
pos = 4; // only removed "\3sub"
basenode = namehashFromDNS(name.slice(pos, name.length)); // "raffy.eth"
(chain, rewriter) = getTarget(basenode);
chain = 10; // optimism
rewriter = ...; // eg. OwnedRewriter
name = INameRewriter(rewriter).rewrite(name, pos);
name = "sub.51050ec063d393217b436747617ad1c2285aeeee.addr.reverse";
node = namehash(node);
verifier = getVerifier(chain); // optimism verifier contract
- ā
EVMFetcher(verifier, registry)...fetch(<regCallback>, encode(calldata0, verifier, node))
- ā
regCallback(values[], calldata)
- Decode:
calldata
ā(calldata0, verifier, node)
- Decode:
values[]
āresolver = address(values[0]))
- ā
EVMFetcher(verifier, resolver)...fetch(<resCallback>, calldata)
- ā
resCallback(values[], calldata)
- Decode:
calldata
ā(calldata0)
- Decode original calldata:
calldata0
āaddr(60)
- Do any transforms necessary.
return values[1];
- This lets an L1 name use L2 for metadata.
- This lets you build out an L2 registry subtree and use L2 for metadata.
- Can reuse existing L1 tooling (L2 Registry and L2 PublicResolver would work as-is)
- Get native L2 resolution for free (when ENSIP-10 isnāt supported) ā only for names without rewrites