mantle-rwa-yield-router / contracts /AgentIdentity8004.sol
muthuk1's picture
Add contracts/AgentIdentity8004.sol
33db02c verified
Raw
History Blame
12.1 kB
// 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);
}
}