MANDATELaunch app →

ENS — central, not cosmetic

Delete the ENSv2 layer from this project and there is no product left. Every mandate is an ENS subname; every permission it carries comes from ENSv2’s Enhanced Access Control, not from a database this app happens to keep in sync with a name.

The role-omission table

A name’s properties are emergent from which roles are withheld at registration — never features implemented after the fact:

PropertyHow it's achieved
Non-transferableNo ROLE_CAN_TRANSFER_ADMIN in the roleBitmap passed to register().
Self-expiringNo ROLE_RENEW — only the registrar (org-controlled) can extend expiry.
Revocable by the orgMandateRegistrar always holds ROLE_UNREGISTER at the registry root.
Agent owns some recordsauthorizeTextRoles() grants ROLE_SET_TEXT scoped to exactly three keys.

Admin roles are settable only at a name’s registration — never again afterward. Get the bitmap right at issueMandate; there is no second chance, which is exactly the property that makes soulbound-by-omission trustworthy rather than merely convenient.

Agents as namespaces

On its first attenuate call, a mandate deploys its own UserRegistry via ENSv2’s VerifiableFactory and wires it as that name’s subregistry — making the agent a genuine namespace root for its own sub-agents, not a leaf with a flat list of children. This is ENSv2’s bonus criterion, and it’s load-bearing here: attenuation depth and per-child registries are how a research agent can safely hire a scraper agent without the org writing a single extra line of ENS state by hand.

Per-key resolver permissions

Each mandate gets its own dedicated PermissionedResolver instance. authorizeTextRoles(name, "agent.status", agentWallet, true) grants exactly that key — not the whole name, not a set of keys. The agent can report agent.status and agent.heartbeat; it is cryptographically incapable of writing mandate.budget.total, verified directly against the live ENSv2 Sepolia deployment.

A verified, real integration finding: the resolver’s initialize() skips permission checks for direct setters (setText et al.) but not for the authorize*Roles grant path — multicall’s delegatecall relay preserves the deploying factory as msg.sender throughout the whole batch. Agent grants run as separate calls immediately after deployment instead of inside the same multicall, once MandateRegistrar is genuinely the caller. Full writeup in /docs/architecture.

No hard-coded values

Every ENSv2 address — RootRegistry, ETHRegistry, VerifiableFactory, PermissionedResolverImpl, UserRegistryImpl — is loaded from environment configuration, and every mandate in the demo is authored live in the observatory, not seeded from a fixture.