// SPDX-License-Identifier: MIT pragma solidity 0.8.23; import "./IERC8004.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; /// @dev The subset of the LIVE, deployed AereAIReputation (chain 2800, 0x781e…feBF) this adapter /// uses. Reputation there is keyed by (operator address, bytes32 agentId): `attest` is gated /// to a registered attestor set fixed at deploy, and `scoreOf` / `statsOf` are public reads. interface IAereAIReputationLive { function attest(address operator, bytes32 agentId, int8 delta, bytes32 evidenceHash, string calldata evidenceUri) external; function scoreOf(address operator, bytes32 agentId) external view returns (uint256); function statsOf(address operator, bytes32 agentId) external view returns (uint256 positiveCount, uint256 disputeCount, uint256 lifetimeSlashed); function isAttestor(address who) external view returns (bool); } /// @dev The one Identity-adapter read used to resolve an ERC-8004 agentId to its Aere operator /// (the agent's controller), which is the operator address the live reputation is keyed by. interface IAereIdentity8004 { function isRegistered(uint256 agentId) external view returns (bool); function getAgent(uint256 agentId) external view returns (IERC8004Identity.AgentInfo memory); } /** * @title AereReputationRegistry8004, an ERC-8004 Reputation adapter over the deployed AereAIReputation * * @notice Exposes the ERC-8004 Reputation surface (attest / read feedback) over Aere Network's * ALREADY-DEPLOYED composable reputation primitive, AereAIReputation. It does NOT redeploy * or modify the live reputation contract. * * READ (adapts the deployed contract directly). `getScore` and `getStats` map an ERC-8004 * agentId to the live reputation's (operator, agentId) key and read `scoreOf` / `statsOf` * straight from the deployed AereAIReputation. This works against the currently-live * instance as-is, with no change to it. * * WRITE (forwards feedback to the live contract). `giveFeedback` maps an ERC-8004 feedback * signal to the live `attest(operator, agentId, delta, evidenceHash, uri)` and forwards it. * * @dev KEY MAPPING. The live reputation is keyed by (operator address, bytes32 agentId). This * adapter derives: * operator = the agent's current controller, resolved from the Identity adapter. * agentKey = bytes32(agentId) (the ERC-8004 / DID agent id, left-padded). * So an ERC-8004 read/write addresses exactly one live reputation slot, deterministically. * * @dev HONEST WRITE CONSTRAINT. AereAIReputation's attestor set is fixed at its deploy and has * no add path. For `giveFeedback` to land in the live score, THIS adapter must be a * registered attestor on the reputation instance it is constructed with. The currently-live * AereAIReputation lists the Foundation attestor only (immutable), so pairing this adapter * for WRITE-THROUGH is a Foundation governance decision, external to this contract and * founder-gated. [VERIFY: on mainnet, register this adapter as an attestor before relying on * giveFeedback, or have the Foundation attestor call the live attest directly.] The READ * path needs no such authorization and works against the live instance today. * * @dev [VERIFY: confirm against the finalized ERC-8004 interface] ERC-8004 reputation feedback in * later drafts carries a richer payload (score, tag, response URI). Here the feedback signal * is normalized to the live model's delta in {-1, 0, +1} (+1 positive, -1 dispute, 0 * neutral) so no information is invented beyond what the deployed contract records. * * HONEST SCOPE. Application-layer reputation only. Does NOT change AERE consensus, holds no * funds. * * @dev CALLER GATE (feedback authorization). Because every caller collapses into ONE on-chain * attestor identity (this adapter) on the live AereAIReputation, an ungated write path would * let ANY address inflate or tank any agent's reputation, defeating the live contract's * curated-attestor model and its (attestor, evidenceHash) dedup. To mirror that curated model, * `giveFeedback` is gated to an owner-managed allowlist of authorized feedback authors * (`isAuthorizedFeedbackAuthor`). The owner curates WHO may submit feedback; it CANNOT set a * reputation value directly, cannot bypass the {-1, 0, +1} delta clamp every author is held to, * and holds no funds. This is the least-surprising design that closes the "anyone can write" * hole while keeping the score honest and the read path fully permissionless. */ contract AereReputationRegistry8004 is IERC8004Reputation, Ownable { /// @notice The LIVE, deployed AereAIReputation this adapter presents an ERC-8004 face over. IAereAIReputationLive public immutable REPUTATION; /// @notice The Aere ERC-8004 Identity adapter, used to resolve agentId -> operator (controller). IAereIdentity8004 public immutable IDENTITY; /// @notice Owner-curated set of addresses allowed to submit feedback through this adapter. Mirrors /// the live AereAIReputation curated-attestor model at the adapter boundary: only an /// authorized author may drive an agent's live reputation via `giveFeedback`. mapping(address => bool) public isAuthorizedFeedbackAuthor; error ZeroAddress(); error AgentNotRegistered(uint256 agentId); error InvalidDelta(int8 delta); error AdapterNotAttestor(); error NotAuthorizedFeedbackAuthor(address caller); /// @notice Emitted when the owner adds or removes an authorized feedback author. event FeedbackAuthorSet(address indexed author, bool authorized); constructor(address reputation, address identity) { if (reputation == address(0) || identity == address(0)) revert ZeroAddress(); REPUTATION = IAereAIReputationLive(reputation); IDENTITY = IAereIdentity8004(identity); } // ========================================================================== // Feedback-author administration // ========================================================================== /// @notice Authorize `author` to submit feedback through this adapter. Owner-only. This power is /// scoped to feedback AUTHORIZATION only: it cannot set a reputation value, cannot exceed /// the {-1, 0, +1} delta clamp, and moves no funds. function addFeedbackAuthor(address author) external onlyOwner { if (author == address(0)) revert ZeroAddress(); isAuthorizedFeedbackAuthor[author] = true; emit FeedbackAuthorSet(author, true); } /// @notice Revoke `author`'s authorization to submit feedback through this adapter. Owner-only. function removeFeedbackAuthor(address author) external onlyOwner { isAuthorizedFeedbackAuthor[author] = false; emit FeedbackAuthorSet(author, false); } // ========================================================================== // Write // ========================================================================== /** * @notice Record ERC-8004 feedback about an agent, forwarding it to the live AereAIReputation. * The feedback signal is `delta` in {-1, 0, +1}. Resolves the live operator from the * Identity adapter and calls the deployed `attest`. Reverts fail-closed if this adapter * is not an authorized attestor on the live reputation (see contract NatSpec). * @dev Gated to the owner-curated `isAuthorizedFeedbackAuthor` set: an unauthorized caller * reverts NotAuthorizedFeedbackAuthor, so this adapter cannot act as an open reputation * firehose once it is a live attestor. Every authorized author is still bound by the * {-1, 0, +1} delta clamp, so authorization cannot forge an arbitrary score. * @param agentId the ERC-8004 / DID agent id. * @param delta +1 positive, -1 dispute, 0 neutral (audit-trail only). * @param evidenceHash a 32-byte commitment to the off-chain evidence (non-zero on the live path). * @param evidenceURI an optional public URI for the evidence. */ function giveFeedback(uint256 agentId, int8 delta, bytes32 evidenceHash, string calldata evidenceURI) external { if (!isAuthorizedFeedbackAuthor[msg.sender]) revert NotAuthorizedFeedbackAuthor(msg.sender); if (!IDENTITY.isRegistered(agentId)) revert AgentNotRegistered(agentId); if (delta < -1 || delta > 1) revert InvalidDelta(delta); // Surface the missing-authorization case with a clear adapter-level error rather than an // opaque revert bubbling up from the live contract. if (!REPUTATION.isAttestor(address(this))) revert AdapterNotAttestor(); address operator = _operatorOf(agentId); REPUTATION.attest(operator, _agentKey(agentId), delta, evidenceHash, evidenceURI); emit NewFeedback(agentId, msg.sender, delta, evidenceHash, evidenceURI); } // ========================================================================== // Read // ========================================================================== /// @inheritdoc IERC8004Reputation /// @notice The agent's live reputation score (0..10000), read straight from AereAIReputation. function getScore(uint256 agentId) external view returns (uint256) { return REPUTATION.scoreOf(_operatorOf(agentId), _agentKey(agentId)); } /// @notice The raw live reputation counters for an agent (positive / dispute / lifetime slashed). function getStats(uint256 agentId) external view returns (uint256 positiveCount, uint256 disputeCount, uint256 lifetimeSlashed) { return REPUTATION.statsOf(_operatorOf(agentId), _agentKey(agentId)); } /// @notice True iff this adapter is authorized to write feedback into the live reputation. function canWriteThrough() external view returns (bool) { return REPUTATION.isAttestor(address(this)); } /// @notice The (operator, agentKey) pair this adapter maps an ERC-8004 agentId to on the live contract. function liveKeyOf(uint256 agentId) external view returns (address operator, bytes32 agentKey) { return (_operatorOf(agentId), _agentKey(agentId)); } // ========================================================================== // Internal // ========================================================================== function _operatorOf(uint256 agentId) internal view returns (address) { return IDENTITY.getAgent(agentId).agentAddress; // current controller } function _agentKey(uint256 agentId) internal pure returns (bytes32) { return bytes32(agentId); } }