// SPDX-License-Identifier: MIT pragma solidity 0.8.23; import "@openzeppelin/contracts/utils/Strings.sol"; import "./IERC8004.sol"; /// @dev The subset of the LIVE, deployed AereAgentDID (chain 2800, 0xce64…22C5) this adapter reads. /// The DID is the Aere-native agent identity: an agent whose ROOT authority is a Falcon key in /// AerePQCKeyRegistry. This adapter never writes to the DID and never redeploys it. interface IAereAgentDIDIdentity { function agentExists(uint256 rootKeyId) external view returns (bool); function getAgent(uint256 rootKeyId) external view returns (address controller, uint64 createdBlock, uint64 sessionNonce, uint256 sessionCountForAgent); function keyRegistry() external view returns (address); } /// @dev The one AerePQCKeyRegistry read used to resolve an agent's CURRENT controller. interface IAerePQCKeyRegistryOwner { function ownerOf(uint256 keyId) external view returns (address); } /** * @title AereIdentityRegistry8004, an ERC-8004 Identity adapter over the deployed AereAgentDID * * @notice Exposes the ERC-8004 Identity registry surface (register / resolve an agent with a DID * plus an AgentCard metadata URI) over Aere Network's ALREADY-DEPLOYED agent identity * primitive, AereAgentDID. It does NOT redeploy or modify the live DID: it reads the live * DID for agent existence and control, and stores only the ERC-8004 metadata (domain + * AgentCard URI) that the DID itself does not carry. * * IDENTITY ANCHOR. The ERC-8004 agentId IS the AereAgentDID agent id, which is the Falcon * ROOT key id in AerePQCKeyRegistry (agentId == rootKeyId). So an ERC-8004 resolve is a * direct, honest pass-through to the live DID: the agent's on-chain root of authority is a * quantum-durable Falcon key, not a bare secp256k1 address. The DID string is * "did:aere::". * * REGISTER = BIND METADATA TO AN EXISTING DID AGENT. `register` does not mint a new * identity (the identity already exists in the live DID). It binds an ERC-8004 AgentCard * URI + domain to an existing DID agent, and is gated to that agent's CURRENT controller * (the registry owner of the Falcon root key), so only the party who proved Falcon * possession can publish the agent's ERC-8004 metadata. * * @dev [VERIFY: confirm against the finalized ERC-8004 interface] The newer ERC-8004 Identity * draft leans on an ERC-721 registration (register(tokenURI) minting a sequential id). Aere * anchors the ERC-8004 identity to the pre-existing Falcon-rooted DID id instead of minting * a fresh sequential id, because the trust root is the on-chain Falcon key, not a token. * The resolve / getAgent / AgentCard-URI SEMANTICS match ERC-8004; the id-minting selector * differs by design and is documented here. * * HONEST SCOPE. Application-layer identity only. This does NOT change AERE consensus (blocks * are still Besu QBFT with classical secp256k1 ECDSA). It holds no funds and has no admin. */ contract AereIdentityRegistry8004 is IERC8004Identity { using Strings for uint256; /// @notice The LIVE, deployed AereAgentDID this adapter presents an ERC-8004 face over. IAereAgentDIDIdentity public immutable DID; /// @notice The AerePQCKeyRegistry the DID roots into (read from the DID at construction). Used /// only to resolve an agent's current controller (the registry owner of the root key). IAerePQCKeyRegistryOwner public immutable KEY_REGISTRY; struct Record { bool registered; string agentDomain; string agentCardURI; address boundController; // controller captured at registration (immutable per Falcon root key) } mapping(uint256 => Record) private _records; // agentId (== rootKeyId) => ERC-8004 metadata mapping(bytes32 => uint256) private _domainToAgentPlus1; // keccak(domain) => agentId + 1 (0 = unset) mapping(address => uint256[]) private _agentsOfAddress; // controller => agentIds it registered uint256[] private _registeredAgentIds; // append-only list of all registered agentIds error ZeroAddress(); error UnknownAgent(uint256 agentId); error NotController(uint256 agentId); error AlreadyRegistered(uint256 agentId); error NotRegistered(uint256 agentId); error EmptyDomain(); error DomainTaken(string agentDomain); constructor(address did) { if (did == address(0)) revert ZeroAddress(); DID = IAereAgentDIDIdentity(did); address reg = IAereAgentDIDIdentity(did).keyRegistry(); if (reg == address(0)) revert ZeroAddress(); KEY_REGISTRY = IAerePQCKeyRegistryOwner(reg); } // ========================================================================== // Registration (metadata binding) // ========================================================================== /** * @notice Bind ERC-8004 metadata (domain + AgentCard URI) to an existing AereAgentDID agent. * Gated to the agent's CURRENT controller (the registry owner of the Falcon root key). * Does not mint a new identity: the identity already lives in the live DID. * @param agentId the AereAgentDID agent id (== the Falcon root key id). * @param agentDomain the agent's domain (non-empty, unique across this adapter). * @param agentCardURI the AgentCard / registration metadata document URI. */ function register(uint256 agentId, string calldata agentDomain, string calldata agentCardURI) external { if (!DID.agentExists(agentId)) revert UnknownAgent(agentId); address controller = KEY_REGISTRY.ownerOf(agentId); if (controller == address(0) || msg.sender != controller) revert NotController(agentId); if (_records[agentId].registered) revert AlreadyRegistered(agentId); if (bytes(agentDomain).length == 0) revert EmptyDomain(); bytes32 dk = keccak256(bytes(agentDomain)); if (_domainToAgentPlus1[dk] != 0) revert DomainTaken(agentDomain); _records[agentId] = Record({registered: true, agentDomain: agentDomain, agentCardURI: agentCardURI, boundController: controller}); _domainToAgentPlus1[dk] = agentId + 1; _agentsOfAddress[controller].push(agentId); _registeredAgentIds.push(agentId); emit AgentRegistered(agentId, agentDomain, agentCardURI, controller); } /** * @notice Update the ERC-8004 metadata for an already-registered agent. Controller-gated. * Re-indexes the domain (the new domain must be free unless unchanged). */ function updateAgent(uint256 agentId, string calldata newDomain, string calldata newCardURI) external { Record storage r = _records[agentId]; if (!r.registered) revert NotRegistered(agentId); address controller = KEY_REGISTRY.ownerOf(agentId); if (controller == address(0) || msg.sender != controller) revert NotController(agentId); if (bytes(newDomain).length == 0) revert EmptyDomain(); bytes32 oldKey = keccak256(bytes(r.agentDomain)); bytes32 newKey = keccak256(bytes(newDomain)); if (newKey != oldKey) { if (_domainToAgentPlus1[newKey] != 0) revert DomainTaken(newDomain); delete _domainToAgentPlus1[oldKey]; _domainToAgentPlus1[newKey] = agentId + 1; r.agentDomain = newDomain; } r.agentCardURI = newCardURI; emit AgentUpdated(agentId, newDomain, newCardURI, controller); } // ========================================================================== // Resolve // ========================================================================== /// @inheritdoc IERC8004Identity function getAgent(uint256 agentId) public view returns (AgentInfo memory) { Record storage r = _records[agentId]; if (!r.registered) revert NotRegistered(agentId); return AgentInfo({ agentId: agentId, did: didString(agentId), agentDomain: r.agentDomain, agentCardURI: r.agentCardURI, agentAddress: KEY_REGISTRY.ownerOf(agentId), // current controller, read live registered: true }); } /// @inheritdoc IERC8004Identity function resolveByDomain(string calldata agentDomain) external view returns (AgentInfo memory) { uint256 plus1 = _domainToAgentPlus1[keccak256(bytes(agentDomain))]; if (plus1 == 0) revert NotRegistered(0); return getAgent(plus1 - 1); } /// @inheritdoc IERC8004Identity /// @dev Returns the MOST RECENT agent registered by `agentAddress`. Use agentsOf() for the full set. function resolveByAddress(address agentAddress) external view returns (AgentInfo memory) { uint256[] storage ids = _agentsOfAddress[agentAddress]; if (ids.length == 0) revert NotRegistered(0); return getAgent(ids[ids.length - 1]); } /// @inheritdoc IERC8004Identity function agentCount() external view returns (uint256) { return _registeredAgentIds.length; } // ========================================================================== // Views // ========================================================================== /// @notice True iff ERC-8004 metadata has been bound for `agentId` (and the DID agent exists). function isRegistered(uint256 agentId) external view returns (bool) { return _records[agentId].registered && DID.agentExists(agentId); } /// @notice All agentIds registered by a controller (append-only; ids are stable Falcon roots). function agentsOf(address controller) external view returns (uint256[] memory) { return _agentsOfAddress[controller]; } /// @notice The full list of registered agentIds (append-only). function registeredAgentIds() external view returns (uint256[] memory) { return _registeredAgentIds; } /// @notice The DID string for an agent: "did:aere::". function didString(uint256 agentId) public view returns (string memory) { return string.concat("did:aere:", block.chainid.toString(), ":", agentId.toString()); } }