ERC-8004 in production: per-collection factory vs chain singleton, which should be the reference architecture?

We’ve been running ERC-8004 in production since April 2026 and wanted to open a
discussion on an architectural decision that the current draft leaves unresolved: should
the reference deployment model be a per-chain singleton or a per-collection
factory
?

The draft currently describes registries as per-chain singletons. We shipped the
opposite, and want to explain why, what the tradeoffs are, and whether the spec should
accommodate both patterns explicitly.


What we built

AgentIdentityRegistryFactory deploys one AgentIdentityRegistry per NFT collection.
The collection address is set as an immutable in the registry constructor and never
changes. Three collections are live on mainnet, each with its own independently-owned
registry.

The full factory and registry source is readable on Etherscan (links at the bottom).
The key surface area:

// Factory — one deployment per collection, owner-controlled
contract AgentIdentityRegistryFactory is Ownable2Step {

    mapping(address => address) public registryOf;

    function deployRegistry(
        address sourceCollection,
        RegistryConfig calldata cfg   // name, symbol, mintPrice, treasury, royalties
    ) external onlyOwner returns (address registry);

    function lookup(address sourceCollection)
        external view
        returns (address registry, bool isDelisted);
}

// Registry — per-collection, bound at construction
contract AgentIdentityRegistry is ERC721, EIP712 {

    address public immutable boundCollection;  // set once, never changes

    // mints agent NFT after live ownership check of sourceTokenId in boundCollection
    function registerWithSource(uint256 sourceTokenId) external payable;

    // agent hot wallet — separate from NFT owner, EIP-712 + IERC1271
    function getAgentWallet(uint256 agentId) external view returns (address);

    // arbitrary key/value metadata per agent (capabilities, endpoints, services)
    function getMetadata(uint256 agentId, string calldata key)
        external view returns (bytes memory);
}


Consumer lookup (two hops, but cacheable):

factory.lookup(collectionAddress) → (registryAddress, isDelisted)
registry.bindingOf(agentId)       → (collectionAddress, tokenId)


The boundCollection immutable is the binding. There is no setBinding()
the architectural constraint is the guarantee.


The core architectural argument

Per-chain singleton pros:

  • Single canonical address - consumers always know where to look

  • Indexers watch one contract

  • Simpler cross-collection queries

Per-chain singleton cons:

  • A single registry owner controls agent identity for the entire chain

  • One vulnerability or admin key compromise affects every collection

  • Collections can’t have independent economics (mint price, royalties, treasury)

  • Delisting one bad collection requires touching the global state

Per-collection factory pros:

  • Binding is a structural invariant - there is no setBinding() because there’s nothing
    to set. If an agent exists in registry X, it is bound to collection X, always.

  • Registry ownership is scoped to the collection admin. A collection’s registry operator
    cannot affect another collection’s agents.

  • Independent economics per collection: mint price, treasury, royalty receiver, credits.

  • Isolation: a compromised or misbehaving collection can be delisted from the factory
    frontend without affecting other registries - on-chain agents remain valid.

  • tokenToAgentId reverse lookup is trivial - each registry already owns exactly one
    collection, so a mapping from sourceTokenId to agentId is straightforward without
    cross-collection ambiguity.

Per-collection factory cons:

  • No canonical single address - consumers need a factory lookup step

  • Indexers must watch the factory for RegistryDeployed events to discover all
    registries

  • Cross-collection agent queries require aggregation across multiple contracts


The ERC-8217 connection

This debate is directly relevant to ERC-8217,
which is standardising agent-NFT identity bindings. ERC-8217 currently assumes a
per-agent binding contract (one binding contract per agent). The factory pattern suggests
a third model: per-collection binding, where the registry itself is the binding contract.

In our implementation, ERC-8217’s IAgentBindings interface is satisfied by the
per-collection registry:

interface IAgentBindings {
    event AgentBound(uint256 indexed agentId, address indexed tokenContract, uint256 indexed tokenId);
    function bindingOf(uint256 agentId) external view returns (address tokenContract, uint256 tokenId);
    function tokenToAgentId(address tokenContract, uint256 tokenId) external view returns (uint256 agentId);
}


Binding is established atomically at registerWithSource(tokenId) and is irrevocable by
construction. No separate binding step, no separate binding contract per agent.


What we’re asking

  1. Should ERC-8004 explicitly define both deployment patterns (singleton and factory) as
    valid implementations, with a minimal interface requirement that both satisfy?

  2. Should the factory address be the canonical discovery mechanism — i.e.,
    factory.lookup(collection) as the standard consumer entrypoint rather than a
    hardcoded registry address?

  3. Is the isolation model (per-collection ownership, independent economics) worth the
    indexer complexity cost?

We’re not arguing the singleton model is wrong - there are valid use cases for a shared
registry. But the spec should acknowledge both patterns rather than implying singletons
are the only valid deployment.

Open for pushback.


Contracts (all verified on Etherscan — source readable there):

Live gateway: https://gateway.ensub.org
Live attestations: https://gateway.ensub.org/admin/attestations
Agent manifest (example): https://gateway.ensub.org/agent/0xe61f5a6783ae09949b9a1b6821b68f89c0d7bb2d/5/.well-known/agent.json

Related threads:

3 Likes

The metadata is saved per agent, but the contract can be the same for many agents.


There is also ERC-8041, if you want to create a verifiable fixed collection of ERC-8004 agents.

The collection contract could also be an “adapter” contract, where ERC-8041 and ERC-8217 are both supported in the same contract. This would make it impossible for a collection NFT to be registered twice and be a part of the verified collection, because the implementation could only allow for one collection NFT to one 8004 record NFT.

So it is not mandated by the standard, but instead by the ERC-8041 collection implementation.

1 Like

Appreciate the ERC-8041 pointer, worth reading. Two quick observations:

The adapter approach (ERC-8041 + ERC-8048 + ERC-8217 adapter) achieves 1:1 binding through implementation convention. Our factory achieves it structurally, boundCollection is an immutable set at construction, there is no code path that allows a second collection to be bound. The constraint isn’t enforced by the implementation following a rule, it’s enforced by the contract having no function to violate it.

On complexity: three standards composing to produce one guarantee vs. one factory contract producing the same guarantee. Both are valid designs. The spec question we originally posed is whether ERC-8004 should acknowledge both patterns explicitly, singleton and factory, with a minimal shared interface. That remains open regardless of which adapter approach is used on top.

Running three collections on mainnet with this model if anyone wants to inspect the actual binding behaviour on-chain.

1 Like

ERC-8041 and ERC-8217 were both written to solve the same problem. How do we have fixed supply agent NFTs using ERC-8004.

In the case of ERC-8041, the only NFT is the ERC-8004 NFT token, and the collection is a verifiable property of the agent NFTs. This could work if it was widely supported, but it doesn’t allow for legacy collections to register.

ERC-8217 handles the case where the collection NFT is already deployed. It allows for the collection NFT to be “bound” to the ERC-8004 NFT.

My perspective is that ERC-8004 is already a thing, so therefore a solution that works with it has the best chance of getting adopted, and therefore solving the problem.

It’s a bit of a hack, but we could add into ERC-8041 a codehash value that represents a specific contract type. This would be verifiable. Then you could verify that the 8004 agent is part of the collection and verify the contract code of the collection, which would be your design.

1 Like

The codehash idea is actually a smart pragmatic move, it gets you verifiable collection membership without touching existing NFTs, which is the hard part of adoption. Appreciate you thinking through how the two approaches could align rather than just staying in opposing corners.

Worth mentioning: in the per-collection factory model you get this structurally, boundCollection is immutable at deploy time, so the factory address already IS the collection verifier, no lookup needed. But if ERC-8041 incorporated codehash it would express the same guarantee from the singleton side. The semantics could converge even if the architecture doesn’t.

Good direction, would love to see this make it into the spec.

1 Like

I think there is actually an issue: codehash is only the runtime code and doesn’t know about any values that were set in the constructor. I think it would be possible to poison values in the contract using assembly in the constructor and still pass the codehash test.

I think there are a bunch of issues with ERC-8004 in terms of the contract being upgradable and also not having any fixed supply built into the contract. That said, I think it already has mindshare, and I see it as difficult to get people using a different standard.

1 Like

The codehash concern is well taken, and it’s exactly why ERC-8004 doesn’t use it. The per-collection factory gives you collection membership structurally: boundCollection is set at deploy time and immutable, so the factory address IS the verifier. No runtime code inspection, no constructor exploit surface.

On upgradeability, immutability here is the design intent, not an oversight. An agent identity binding that can be upgraded is one that can be reassigned, which defeats the purpose of the registry. Fixed supply is the collection’s concern, not the registry’s; ERC-8004 inherits whatever supply constraints the bound collection enforces.

Appreciate the honest read at the end. The codehash idea was a good-faith attempt to bridge the two models, it just turns out the structural approach already covers the requirement more cleanly.

Tiago / Dinamic.eth

2 Likes

Worth noting from the OCP side of this stack — the L3 commitment layer is indifferent to the factory vs. singleton question entirely.

Whether ERC-8004 identity comes from a per-collection factory or a chain singleton, record(input_hash) on the OCP contract works identically. The verification primitive doesn’t inspect how the agent identity was registered — it only sees the input_hash passed to it. The proof envelope that comes out is the same either way.

That means this architectural decision can be resolved purely on its own merits — collection membership semantics, upgradeability, supply constraints — without any downstream verification compatibility concern. L3 will compose cleanly with whichever pattern wins.

1 Like