A related topic is discoverability. I believe the official app is relying on a standard set of text keys and address types, and subgraph data from event-emitting resolvers. This means that CCIP names (gasless DNS, L2 stuff, cb.id, etc.) are disadvantaged with non-standard records and incorporating new “standard” records will always lag.
If it was efficient to multicall in the general case, you could just probe multiple keys but that still sucks and doesn’t let you discover new things—just easily test 3 different Farcaster permutations. Ideally you’d just query a directory of available records.
I’m just gonna throw an idea out: named prebuilt calldata. This combines discoverability and call-efficiency into one idea, that fits in an address record.
First, pick a set of records and encode them:
// pre-defined calldata
let calldata = abi.encode([
encode("text(bytes32,string)", 0x0, "avatar"),
encode("text(bytes32,string)", 0x1, "name"),
encode("addr(bytes32,uint256)", 0x2, 60),
]);
It can be accessed under a new coinType C
:
addr(node, C) = calldata
At some predictable ENS name, eg. 1.profile.ens.eth
, have write-once resolver, that responds with the same data, for all nodes, such that addr(_, C) = namehash("<keccak256(calldata)>.profile.reverse")
Store a copy on-chain under that same name <keccak256(calldata)>.profile.reverse
with a resolver where addr(node, C) = calldata
and with corresponding hash.
Elsewhere: if addr(node, C)
returns 32 bytes
it’s a node (and this resolution should repeat), otherwise stop, and decode it as an array of calls.
When we want to publish a new “standard”, 2.profile.ens.eth
, etc. Clients switch their default profile whenever.
Now when you resolve raffy.eth
, do the following:
// (A) get resolver
(resolver, version, offset) = getResolver("raffy.eth")
// offset = byte offset of first non-null resolver
// version 0 = og ens
// version 1 = ensip-10
// version 2 = ensip-10 + multicall support (backwards compat with v0 and v1)
// (B) get calldata from various sources:
// 1. "ens standard records, edition v1"
bytes32 profile1 = ENS.resolve("1.profile.ens.eth").addr("raffy.eth", C)
bytes[] calls = abi.decode(ENS.resolve(profile1).addr(profile1, C))
/// 2. "your records"
// this can be ccip-read
// this can be a node or encoded-calldata
bytes32 node = namehash("raffy.eth");
while let v = ens.resolver(node).addr(node, C) and v.length == 32 { node = v }
if (v.length > 32) calls.push(abi.decode(v, bytes[]))
// inject the queried node into the calldata:
// since all records of the form: Ć’(bytes32, ...)
for each call:
replace bytes 36,68 w/ namehash("raffy.eth")
// (C) make the reads
version == 2:
resolver.multi(["raffy.eth"], calls)
version == 1:
UniversalResolver("raffy.eth", calls)
version == 0:
calls.map(v => resolver.staticcall(v))
The new resolver function is:
// this is flattened outer product of names x calls
multi(bytes[] names, bytes[] records) returns (bytes[])
The resolver versions are related in the following ways:
addr(node, ct) := multi([], [abi.encode("addr", node, ct)])
resolve(name, call) := multi([name], [call])
multi(names[], calls[]) := names.flatMap(n => calls.map(c => resolver.call(c~n)))
multi([], calls[]) := calls.map(c => r.call(c))
If you only need a few records, you can construct the calldata yourself, eg. addr(node, 60)
.
If you want the standard records, you can look them up on-chain by version.
You also get your own set of records per name. You can use a shared list of records (eg. popular) by using the namehash of the reverse name, a namehash of another ENS, or the raw calldata itself. addr()
itself can be CCIP-read. It can also be null.
You can ignore/filter selectors you don’t understand.
Client-side, you invoke:
A+B(a) => [x,y,z]
→ “this is what this persona
is showing public”A+C([a], [x,y])
→ for usera
, give mex
andy
A+C([a], [addr(_, 60)]
→ for usera
give meaddr(x, 60)
C([], [addr(a, 60)])
→ give meaddr(a, 60)
Usage: at minimum, you do nothing and get the standard set of records when querying through the MultiResolver. You can also discover anyones full list of records.
You set a single slot on chain, to a namehash of a community name like “ethmoji-degens.eth”, which periodically updates a CCIP (or on-chain record) with their communities precomputed calldata of records/stats/attributes/data. tkn.eth
could set a calldata[]
record for all their supported chains and information.
This idea might be too half-baked and/or I might not of explained it enough, however I’ll leave this as a draft.