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).
Context
Jeff has made Sepolia ENSv2 the MVP live path (mainnet ENSv1 later). ENSv2 (
ensdomains/contracts-v2, live on Sepolia behindUpgradableUniversalResolverProxy 0xeEeEEEeE14D718C2B47D9923Deab1335E144EeEe) replaces the single ENS Registry with per-name registries: a name's subnames live in a subregistry (UserRegistryproxy deployed throughVerifiableFactory) that the parent points at withsetSubregistry, and authority is a role bitmap (ROLE_REGISTRAR,ROLE_SET_RESOLVER,ROLE_SET_SUBREGISTRY, …) instead ofowner()/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.hfstay 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:
§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:
§6.2 / §6.3 — "the owner can always change records and the parent can always reclaim a child" holds in v2 through roles (
ROLE_REGISTRARon the parent's registry can re-register an expired name;ROLE_UNREGISTERcan 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:v2on Sepolia,v1on 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).