Skip to content

spec-change: ENSv2 (Sepolia) registrar and publishing flows replace setSubnodeRecord/setApprovalForAll; tx counts 4/2 publish, 2 registrar #17

Description

@cursor

Context

Jeff has made Sepolia ENSv2 the MVP live path (mainnet ENSv1 later). ENSv2 (ensdomains/contracts-v2, live on Sepolia behind UpgradableUniversalResolverProxy 0xeEeEEEeE14D718C2B47D9923Deab1335E144EeEe) replaces the single ENS Registry with per-name registries: a name's subnames live in a subregistry (UserRegistry proxy deployed through VerifiableFactory) that the parent points at with setSubregistry, and authority is a role bitmap (ROLE_REGISTRAR, ROLE_SET_RESOLVER, ROLE_SET_SUBREGISTRY, …) instead of owner() / setApprovalForAll.

SPEC.md is written against ENSv1 (Registry.setSubnodeRecord, setOwner, setApprovalForAll, "3 transactions for a new model, 2 for a new version"). Record keys, manifest, lockfile and the resolution algorithm are unaffected: contenthash, com.enspack.spec, com.enspack.magnet, com.enspack.hf stay as they are and resolve through the Universal Resolver exactly as before.

Proposed diff (SPEC.md, for enspack/0.2; implemented now behind a feature flag)

§7 step 5 (registrar) — replace the three v1 calls with:

- Registry.setSubnodeRecord(node(enspack.eth), labelhash(label), operator, PublicResolver, 0)
- PublicResolver.multicall([setText(node, "com.enspack.hf", …), setText(node, "com.enspack.spec", …)])
- Registry.setOwner(node, address)
+ ENSv2: the operator holds ROLE_REGISTRAR on the UserRegistry that enspack.eth points at:
+   Registry(enspack.eth).register(label, address, IRegistry(0), resolver, ROLE_SET_RESOLVER|ROLE_SET_RESOLVER_ADMIN|ROLE_SET_SUBREGISTRY|ROLE_SET_SUBREGISTRY_ADMIN|ROLE_CAN_TRANSFER_ADMIN, expiry(enspack.eth))
+   Resolver.multicall([setText(node, "com.enspack.hf", …), setText(node, "com.enspack.spec", …)])
+ That is 2 transactions (was 3). `resolver` is the project's PermissionedResolver, on which the operator holds ROLE_SET_TEXT; the claimant holds ROLE_SET_RESOLVER and may repoint the name to their own resolver later.

§7 step 5 preamble — "an ENS Registry operator approved by the root owner" → "an account granted ROLE_REGISTRAR (and ROLE_RENEW) on enspack.eth's UserRegistry by its owner via grantRootRoles".

§8 step 5 (publishing) — replace with:

- first publish of a model: Registry.setSubnodeRecord(publisherNode, labelhash(model), publisher, resolver, 0)
- every version:            Registry.setSubnodeRecord(modelNode, labelhash(vLabel), publisher, resolver, 0)
- one PublicResolver.multicall([...]) covering both nodes
- That is 3 transactions for a new model and 2 for a new version.
+ ENSv2: version names are subnames of the model name, so a model needs its own subregistry.
+ first publish of a model:
+   VerifiableFactory.deployProxy(UserRegistryImpl, salt, initialize(publisher, ROLE_REGISTRAR|ROLE_REGISTRAR_ADMIN|ROLE_RENEW|ROLE_RENEW_ADMIN|ROLE_SET_PARENT|ROLE_SET_PARENT_ADMIN|ROLE_SET_RESOLVER|ROLE_SET_RESOLVER_ADMIN|ROLE_SET_SUBREGISTRY|ROLE_SET_SUBREGISTRY_ADMIN|ROLE_UPGRADE|ROLE_UPGRADE_ADMIN)) → modelRegistry
+   Registry(publisher).register(modelLabel, publisher, modelRegistry, resolver, <owner roles>, expiry(publisher))
+ every version:
+   modelRegistry.register(vLabel, publisher, IRegistry(0), resolver, <owner roles>, expiry(model))
+ one Resolver.multicall([...]) covering both nodes (unchanged)
+ That is 4 transactions for a new model and 2 for a new version. The resolver used is the one already set on the publisher name (a PermissionedResolver the publisher controls).

§6.2 / §6.3 — "the owner can always change records and the parent can always reclaim a child" holds in v2 through roles (ROLE_REGISTRAR on the parent's registry can re-register an expired name; ROLE_UNREGISTER can remove it). Wording change only.

AGENTS.md — "Public resolver: read from the parent name at runtime" stays; add "ENSv2 registries are discovered from the Universal Resolver (ROOT_REGISTRY, findExactRegistry, findResolver); never hardcode registry addresses."

Feature flag

ENSPACK_ENS_VERSION=v1|v2 (default: v2 on Sepolia, v1 on mainnet). All v1 code paths and Anvil mainnet-fork tests stay intact; v2 is tested on an Anvil Sepolia fork.

Not changed

Record keys, manifest schema, lockfile schema, CLI exit codes, HTTP contracts (registrar returns txs: string[] — now 2 entries on v2).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions