An interface for creating fixed-supply collections of Agent NFTs that are registered in an ERC-8004 Agent Registry. While ERC-8004 provides an unlimited mint registry for agent identities, many use cases require limited collections. Collections can be created with fixed supply limits while maintaining the association between agents and their collection through onchain metadata and mint number tracking.
ERC-8004 establishes a singleton registry for AI Agent identity tokens with unlimited minting capability. This open registration model serves many use cases, but there is a clear need for:
This standard enables these use cases while leveraging the existing ERC-8004 registry infrastructure.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174.
Every compliant collection contract MUST maintain the following collection properties:
maxSupply: Maximum number of agents that can exist in the collectioncurrentSupply: Current number of agents minted in the collectionstartBlock: Block number when minting becomes availableopen: Boolean indicating if the collection is currently accepting mintsCompliant collection contracts MUST implement the IERC8041Collection interface:
pragma solidity ^0.8.25;
interface IERC8041Collection {
/// @notice Emitted when the collection is created or updated
/// @param maxSupply Maximum number of agents in the collection
/// @param startBlock Block number when minting becomes available
/// @param open Whether the collection is open for minting
event CollectionUpdated(uint256 maxSupply, uint256 startBlock, bool open);
/// @notice Emitted when an agent is minted in the collection
/// @param agentId The ERC-8004 token ID of the agent in the registry
/// @param mintNumber The agent's position in the collection (1-indexed)
/// @param owner The address that received the agent
event AgentMinted(uint256 indexed agentId, uint256 mintNumber, address indexed owner);
/// @notice Get the mint number for a specific agent
/// @param agentId The ERC-8004 token ID of the agent
/// @return mintNumber The agent's position in the collection (1-indexed, e.g., "5 of 1000")
/// Returns 0 if the agent was not minted through this collection
function getAgentMintNumber(uint256 agentId) external view returns (uint256 mintNumber);
/// @notice Get the current supply of the collection
/// @return currentSupply Current number of agents minted
function getCollectionSupply() external view returns (uint256 currentSupply);
}
Every compliant collection contract MUST implement the following functions:
function getAgentMintNumber(uint256 agentId) external view returns (uint256 mintNumber) - Returns the mint number for a given agent ID (0 if not in collection)function getCollectionSupply() external view returns (uint256 currentSupply) - Returns the current supply of the collectionEvery compliant collection contract MUST emit the following events:
event CollectionUpdated(uint256 maxSupply, uint256 startBlock, bool open) - MUST be emitted when the collection is created and whenever collection details are changedevent AgentMinted(uint256 indexed agentId, uint256 mintNumber, address indexed owner) - MUST be emitted when an agent is added to the collectionWhen an agent is minted through a collection contract, the contract MUST:
"agent-collection"AgentMinted eventThe collection metadata format SHOULD be:
abi.encodePacked(
address collectionAddress, // 20 bytes: the collection contract address
uint8 mintNumberLength, // 1 byte: length of mint number bytes
bytes mintNumberBytes // variable: the mint number (compact encoding)
)
This enables any party to:
Mint numbers MUST:
getAgentMintNumber()Collection contracts MUST:
maxSupply limits (prevent minting beyond maximum)currentSupply accuratelyAgentMinted events for each successful mintstartBlock timing constraintThe following features are OPTIONAL and left to implementation choice:
Collections SHOULD implement ERC-7572 contract metadata to provide collection-level information such as name, description, image, and external links. This is done by implementing:
function contractURI() external view returns (string memory);
The URI should point to a JSON file following the ERC-7572 metadata schema.
ERC-8004 agents are unlimited mint NFTs; this ERC was created to enable verifiable fixed-supply collections of agents. This standard attempts to make minting fixed-supply ERC-8004 agents as convenient as possible. The getCollectionSupply() function allows clients to query current supply without relying on an indexer. Collection configuration (maxSupply, startBlock, open) is communicated via the CollectionUpdated event, which is emitted at creation and whenever these values change.
This standard is fully backwards compatible with:
A minimal reference implementation demonstrating the core requirements:
pragma solidity ^0.8.25;
import {IERC8004AgentRegistry} from "./interfaces/IERC8004AgentRegistry.sol";
import {IERC8041Collection} from "./interfaces/IERC8041Collection.sol";
/**
* @title ERC8041MinimalCollection
* @notice Minimal reference implementation
* @dev Users supply pre-registered agent IDs to add to collection
*/
contract ERC8041MinimalCollection is IERC8041Collection {
IERC8004AgentRegistry public immutable agentRegistry;
uint256 public maxSupply;
uint256 public currentSupply;
uint256 public startBlock;
bool public open;
mapping(uint256 agentId => uint256 mintNumber) private _agentMintNumber;
constructor(
IERC8004AgentRegistry _agentRegistry,
uint256 _maxSupply
) {
agentRegistry = _agentRegistry;
maxSupply = _maxSupply;
startBlock = block.number;
open = true;
emit CollectionUpdated(_maxSupply, block.number, true);
}
/**
* @notice Add an existing agent to the collection
* @param agentId The ID of an agent already registered in ERC-8004
*/
function mint(uint256 agentId) external {
require(currentSupply < maxSupply, "Supply exceeded");
require(agentRegistry.ownerOf(agentId) == msg.sender, "Not agent owner");
require(_agentMintNumber[agentId] == 0, "Already in collection");
currentSupply++;
uint256 mintNumber = currentSupply;
_agentMintNumber[agentId] = mintNumber;
// Store collection metadata in the agent's onchain metadata
bytes memory mintNumberBytes = abi.encode(mintNumber);
uint8 mintNumberLength = uint8(mintNumberBytes.length);
bytes memory collectionMetadata = abi.encodePacked(
address(this), // 20 bytes: collection contract address
mintNumberLength, // 1 byte: length of mint number bytes
mintNumberBytes // variable: the mint number (compact encoding)
);
IERC8004AgentRegistry(address(agentRegistry)).setMetadata(
agentId,
"agent-collection",
collectionMetadata
);
emit AgentMinted(agentId, mintNumber, msg.sender);
}
function getAgentMintNumber(uint256 agentId) external view returns (uint256) {
return _agentMintNumber[agentId];
}
function getCollectionSupply() external view returns (uint256) {
return currentSupply;
}
}
This implementation demonstrates:
The owner of an ERC-8004 agent can modify or remove the "agent-collection" metadata stored on their agent at any time. Client applications MUST NOT rely solely on this metadata to verify collection membership. Instead, clients SHOULD:
getAgentMintNumber(agentId) to verify membership0 from the collection contract as definitive proof that an agent is not part of that collectionCopyright and related rights waived via CC0.