// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; /** * @title AgentIdentity8004 * @notice ERC-8004 Agent Identity NFT for the Dynamic RWA Yield Router. * Each AI agent mints a soulbound identity token that tracks: * - Agent capabilities (yield optimization, risk management, etc.) * - Attestations from verifiers (auditors, performance validators) * - Reputation score based on historical performance * - On-chain decision history references * * @dev ERC-8004 extends ERC-721 with agent-specific interfaces: * - agentOf(tokenId): Returns the agent's controller address * - capabilities(tokenId): Returns list of registered capabilities * - attest(tokenId, attestation): Records a verifiable attestation * - reputation(tokenId): Returns computed reputation score * * Deployed on Mantle L2 (Chain ID 5000). */ contract AgentIdentity8004 is ERC721, Ownable { // ─────────────────── Types ────────────────────── struct AgentProfile { string name; string description; string version; address controller; // EOA/multisig controlling the agent uint256 createdAt; uint256 reputationScore; // 0-10000 (basis points) bool active; } struct Capability { string name; // e.g., "yield_optimization" string description; uint256 registeredAt; bool verified; } struct Attestation { address attester; // Who made the attestation string attestationType; // e.g., "performance_audit", "security_review" bytes32 contentHash; // IPFS CID or content hash uint256 timestamp; int256 scoreImpact; // Positive or negative reputation impact } struct DecisionReference { uint256 timestamp; bytes32 contentHash; // Hash of the strategy report / allocation record string decisionType; // "rebalance", "emergency_exit", "report" int256 outcomeScore; // Performance outcome (-10000 to 10000 bps) } // ─────────────────── State ────────────────────── uint256 private _nextTokenId; // Token ID → Agent Profile mapping(uint256 => AgentProfile) public agentProfiles; // Token ID → Capabilities mapping(uint256 => Capability[]) public agentCapabilities; // Token ID → Attestations mapping(uint256 => Attestation[]) public agentAttestations; // Token ID → Decision References mapping(uint256 => DecisionReference[]) public agentDecisions; // Authorized attesters mapping(address => bool) public authorizedAttesters; // ─────────────────── Events ───────────────────── event AgentRegistered(uint256 indexed tokenId, string name, address controller); event CapabilityAdded(uint256 indexed tokenId, string capability); event AttestationRecorded(uint256 indexed tokenId, address attester, string attestationType); event DecisionRecorded(uint256 indexed tokenId, string decisionType, bytes32 contentHash); event ReputationUpdated(uint256 indexed tokenId, uint256 newScore); event AttesterAuthorized(address indexed attester, bool authorized); // ─────────────────── Constructor ───────────────── constructor() ERC721("RWA Yield Router Agent Identity", "RWAID") Ownable(msg.sender) { _nextTokenId = 1; } // ─────────────────── ERC-8004 Core Interface ──── /** * @notice Register a new AI agent and mint its identity NFT. * @param controller Address that controls the agent (EOA or multisig) * @param name Human-readable agent name * @param description Agent description * @param version Semantic version string * @return tokenId The newly minted token ID */ function registerAgent( address controller, string calldata name, string calldata description, string calldata version ) external onlyOwner returns (uint256 tokenId) { tokenId = _nextTokenId++; _mint(controller, tokenId); agentProfiles[tokenId] = AgentProfile({ name: name, description: description, version: version, controller: controller, createdAt: block.timestamp, reputationScore: 5000, // Start neutral (50%) active: true }); emit AgentRegistered(tokenId, name, controller); } /** * @notice Get the controller (agent operator) for a token. * @dev ERC-8004 agentOf interface. */ function agentOf(uint256 tokenId) external view returns (address) { require(_ownerOf(tokenId) != address(0), "Agent does not exist"); return agentProfiles[tokenId].controller; } /** * @notice Get all registered capabilities for an agent. * @dev ERC-8004 capabilities interface. */ function capabilities(uint256 tokenId) external view returns (Capability[] memory) { return agentCapabilities[tokenId]; } /** * @notice Get the reputation score for an agent. * @dev ERC-8004 reputation interface. Score in basis points (0-10000). */ function reputation(uint256 tokenId) external view returns (uint256) { require(_ownerOf(tokenId) != address(0), "Agent does not exist"); return agentProfiles[tokenId].reputationScore; } // ─────────────────── Capability Management ────── /** * @notice Add a capability to the agent's profile. */ function addCapability( uint256 tokenId, string calldata name, string calldata description ) external { require( msg.sender == agentProfiles[tokenId].controller || msg.sender == owner(), "Not authorized" ); agentCapabilities[tokenId].push(Capability({ name: name, description: description, registeredAt: block.timestamp, verified: false })); emit CapabilityAdded(tokenId, name); } /** * @notice Verify a capability (attester confirms agent has this ability). */ function verifyCapability( uint256 tokenId, uint256 capabilityIndex ) external { require(authorizedAttesters[msg.sender], "Not an authorized attester"); require(capabilityIndex < agentCapabilities[tokenId].length, "Invalid index"); agentCapabilities[tokenId][capabilityIndex].verified = true; } // ─────────────────── Attestation System ───────── /** * @notice Record an attestation for an agent. * @dev ERC-8004 attest interface. * @param tokenId Agent token ID * @param attestationType Type of attestation * @param contentHash IPFS CID or SHA-256 of attestation document * @param scoreImpact Reputation impact (-1000 to +1000 bps) */ function attest( uint256 tokenId, string calldata attestationType, bytes32 contentHash, int256 scoreImpact ) external { require(authorizedAttesters[msg.sender], "Not an authorized attester"); require(_ownerOf(tokenId) != address(0), "Agent does not exist"); require(scoreImpact >= -1000 && scoreImpact <= 1000, "Impact out of range"); agentAttestations[tokenId].push(Attestation({ attester: msg.sender, attestationType: attestationType, contentHash: contentHash, timestamp: block.timestamp, scoreImpact: scoreImpact })); // Update reputation _updateReputation(tokenId, scoreImpact); emit AttestationRecorded(tokenId, msg.sender, attestationType); } // ─────────────────── Decision Tracking ────────── /** * @notice Record an agent decision reference on-chain. */ function recordDecision( uint256 tokenId, bytes32 contentHash, string calldata decisionType ) external { require( msg.sender == agentProfiles[tokenId].controller || msg.sender == owner(), "Not authorized" ); agentDecisions[tokenId].push(DecisionReference({ timestamp: block.timestamp, contentHash: contentHash, decisionType: decisionType, outcomeScore: 0 // Updated later via attestation })); emit DecisionRecorded(tokenId, decisionType, contentHash); } /** * @notice Score a past decision outcome (for reputation tracking). */ function scoreDecision( uint256 tokenId, uint256 decisionIndex, int256 outcomeScore ) external { require(authorizedAttesters[msg.sender], "Not an authorized attester"); require(decisionIndex < agentDecisions[tokenId].length, "Invalid index"); require(outcomeScore >= -10000 && outcomeScore <= 10000, "Score out of range"); agentDecisions[tokenId][decisionIndex].outcomeScore = outcomeScore; // Small reputation update based on decision outcome int256 repImpact = outcomeScore / 100; // 1% of decision score _updateReputation(tokenId, repImpact); } // ─────────────────── Reputation Engine ────────── function _updateReputation(uint256 tokenId, int256 impact) internal { AgentProfile storage profile = agentProfiles[tokenId]; int256 newScore = int256(profile.reputationScore) + impact; // Clamp to [0, 10000] if (newScore < 0) newScore = 0; if (newScore > 10000) newScore = 10000; profile.reputationScore = uint256(newScore); emit ReputationUpdated(tokenId, uint256(newScore)); } // ─────────────────── Admin ────────────────────── function authorizeAttester(address attester, bool authorized) external onlyOwner { authorizedAttesters[attester] = authorized; emit AttesterAuthorized(attester, authorized); } function setAgentActive(uint256 tokenId, bool active) external onlyOwner { agentProfiles[tokenId].active = active; } // ─────────────────── View Functions ───────────── function getAttestationCount(uint256 tokenId) external view returns (uint256) { return agentAttestations[tokenId].length; } function getDecisionCount(uint256 tokenId) external view returns (uint256) { return agentDecisions[tokenId].length; } function getCapabilityCount(uint256 tokenId) external view returns (uint256) { return agentCapabilities[tokenId].length; } /** * @notice Soulbound: Prevent transfers (agent identity is non-transferable). */ function _update( address to, uint256 tokenId, address auth ) internal override returns (address) { address from = _ownerOf(tokenId); // Allow minting (from == address(0)) but block transfers if (from != address(0) && to != address(0)) { revert("Agent identity is soulbound"); } return super._update(to, tokenId, auth); } }