// SPDX-License-Identifier: MIT pragma solidity 0.8.23; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; /** * @title AereValidationRegistry8004V2 — ERC-8004 Validation Registry, to the letter of the draft. * * @notice Transcribed from the "Validation Registry" section of the ERC-8004 draft (read * 2026-09-02): validationRequest (owner or operator of the agent), validationResponse (only * the validator named in the request; response 0..100; may be called repeatedly, e.g. soft * then hard finality via `tag`), and the four read functions, with the exact argument * orders and event indexing. Stores requestHash, validatorAddress, agentId, response, * responseHash, lastUpdate and tag, as the draft lists. * * @dev `initialize(address)` follows the draft's wording and can run once; a constructor * argument is offered for non-proxy deployments. `requestCount` is kept for the operational * check POP-1 of the conformance suite. No owner, no admin, no upgrade. */ contract AereValidationRegistry8004V2 { struct Request { address validatorAddress; uint256 agentId; uint8 response; bytes32 responseHash; string tag; uint256 lastUpdate; bool exists; bool responded; } event ValidationRequest(address indexed validatorAddress, uint256 indexed agentId, string requestURI, bytes32 indexed requestHash); event ValidationResponse(address indexed validatorAddress, uint256 indexed agentId, bytes32 indexed requestHash, uint8 response, string responseURI, bytes32 responseHash, string tag); error AlreadyInitialized(); error NotInitialized(); error ZeroAddress(); error NotAgentOwnerOrOperator(uint256 agentId, address caller); error AgentNotRegistered(uint256 agentId); error RequestExists(bytes32 requestHash); error NoSuchRequest(bytes32 requestHash); error NotTheValidator(bytes32 requestHash, address caller); error BadResponse(uint8 response); address private _identityRegistry; uint256 public requestCount; mapping(bytes32 => Request) private _requests; mapping(uint256 => bytes32[]) private _byAgent; mapping(address => bytes32[]) private _byValidator; constructor(address identityRegistry_) { if (identityRegistry_ != address(0)) _identityRegistry = identityRegistry_; } function initialize(address identityRegistry_) external { if (_identityRegistry != address(0)) revert AlreadyInitialized(); if (identityRegistry_ == address(0)) revert ZeroAddress(); _identityRegistry = identityRegistry_; } function getIdentityRegistry() external view returns (address identityRegistry) { return _identityRegistry; } // ------------------------------------------------------------------ writes function validationRequest(address validatorAddress, uint256 agentId, string calldata requestURI, bytes32 requestHash) external { if (validatorAddress == address(0)) revert ZeroAddress(); _requireOwnerOrOperator(agentId); if (_requests[requestHash].exists) revert RequestExists(requestHash); _requests[requestHash] = Request({ validatorAddress: validatorAddress, agentId: agentId, response: 0, responseHash: bytes32(0), tag: "", lastUpdate: block.timestamp, exists: true, responded: false }); _byAgent[agentId].push(requestHash); _byValidator[validatorAddress].push(requestHash); requestCount += 1; emit ValidationRequest(validatorAddress, agentId, requestURI, requestHash); } function validationResponse(bytes32 requestHash, uint8 response, string calldata responseURI, bytes32 responseHash, string calldata tag) external { Request storage r = _requests[requestHash]; if (!r.exists) revert NoSuchRequest(requestHash); if (msg.sender != r.validatorAddress) revert NotTheValidator(requestHash, msg.sender); if (response > 100) revert BadResponse(response); r.response = response; r.responseHash = responseHash; r.tag = tag; r.lastUpdate = block.timestamp; r.responded = true; emit ValidationResponse(r.validatorAddress, r.agentId, requestHash, response, responseURI, responseHash, tag); } // ------------------------------------------------------------------ reads function getValidationStatus(bytes32 requestHash) external view returns (address validatorAddress, uint256 agentId, uint8 response, bytes32 responseHash, string memory tag, uint256 lastUpdate) { Request storage r = _requests[requestHash]; if (!r.exists) revert NoSuchRequest(requestHash); return (r.validatorAddress, r.agentId, r.response, r.responseHash, r.tag, r.lastUpdate); } function getSummary(uint256 agentId, address[] calldata validatorAddresses, string calldata tag) external view returns (uint64 count, uint8 averageResponse) { bytes32[] storage hs = _byAgent[agentId]; bytes32 ht = keccak256(bytes(tag)); bool ft = bytes(tag).length != 0; uint256 sum; for (uint256 i = 0; i < hs.length; i++) { Request storage r = _requests[hs[i]]; if (!r.responded) continue; if (validatorAddresses.length != 0 && !_contains(validatorAddresses, r.validatorAddress)) continue; if (ft && keccak256(bytes(r.tag)) != ht) continue; sum += r.response; count += 1; } if (count != 0) averageResponse = uint8(sum / count); } function getAgentValidations(uint256 agentId) external view returns (bytes32[] memory requestHashes) { return _byAgent[agentId]; } function getValidatorRequests(address validatorAddress) external view returns (bytes32[] memory requestHashes) { return _byValidator[validatorAddress]; } // ------------------------------------------------------------------ internals function _contains(address[] calldata xs, address x) internal pure returns (bool) { for (uint256 i = 0; i < xs.length; i++) if (xs[i] == x) return true; return false; } function _requireOwnerOrOperator(uint256 agentId) internal view { if (_identityRegistry == address(0)) revert NotInitialized(); IERC721 idr = IERC721(_identityRegistry); address owner; try idr.ownerOf(agentId) returns (address o) { owner = o; } catch { revert AgentNotRegistered(agentId); } if (msg.sender == owner || idr.isApprovedForAll(owner, msg.sender) || idr.getApproved(agentId) == msg.sender) return; revert NotAgentOwnerOrOperator(agentId, msg.sender); } }