// SPDX-License-Identifier: MIT pragma solidity ^0.8.25; /** * @title VaultfireCapabilityCredentials * @author Vaultfire Protocol * @notice On-chain proof that an agent can actually do what it claims. Agents earn * verified capability credentials through proven task completions in specific * skill areas. When routing tasks, agents with verified credentials get * priority over self-reported capabilities. * @dev Self-contained — no OpenZeppelin imports. Manual ReentrancyGuard included. * Designed to be deployed on Base, Avalanche, Arbitrum, and Polygon. * * Credential issuers are verified addresses (the deployer/governance or * approved issuers) who confirm an agent demonstrated a capability. */ // --------------------------------------------------------------------------- // Minimal interfaces // --------------------------------------------------------------------------- interface IIdentityRegistry_CC { function isAgentActive(address agent) external view returns (bool); } // --------------------------------------------------------------------------- // Contract // --------------------------------------------------------------------------- contract VaultfireCapabilityCredentials { // ----------------------------------------------------------------------- // Types // ----------------------------------------------------------------------- struct Credential { uint256 id; address agent; // Agent who holds the credential bytes32 capabilityHash; // keccak256 of capability name address issuedBy; // Who issued the credential uint256 issuedAt; uint256 expiresAt; // 0 = never expires uint256 taskProofCount; // Number of tasks proving this capability bool active; } struct AgentCredentialProfile { uint256 totalCredentials; uint256 activeCredentials; uint256 totalTaskProofs; } // ----------------------------------------------------------------------- // Immutables // ----------------------------------------------------------------------- IIdentityRegistry_CC public immutable identityRegistry; address public immutable owner; // ----------------------------------------------------------------------- // State // ----------------------------------------------------------------------- uint256 private _credentialCounter; mapping(uint256 => Credential) private _credentials; mapping(address => AgentCredentialProfile) public agentProfiles; /// @dev Agent => capability hash => credential ID (0 = none) mapping(address => mapping(bytes32 => uint256)) public agentCapability; /// @dev Agent => list of credential IDs mapping(address => uint256[]) private _agentCredentials; /// @dev Approved credential issuers mapping(address => bool) public approvedIssuers; /// @dev Capability hash => list of agents with that credential mapping(bytes32 => address[]) private _capabilityHolders; // ----------------------------------------------------------------------- // Reentrancy guard // ----------------------------------------------------------------------- uint256 private constant _NOT_ENTERED = 1; uint256 private constant _ENTERED = 2; uint256 private _reentrancyStatus = _NOT_ENTERED; modifier nonReentrant() { require(_reentrancyStatus != _ENTERED, "VaultfireCapabilityCredentials: reentrant call"); _reentrancyStatus = _ENTERED; _; _reentrancyStatus = _NOT_ENTERED; } // ----------------------------------------------------------------------- // Events // ----------------------------------------------------------------------- event CredentialIssued( uint256 indexed credentialId, address indexed agent, bytes32 indexed capabilityHash, address issuedBy, uint256 expiresAt, uint256 timestamp ); event CredentialRevoked( uint256 indexed credentialId, address indexed agent, bytes32 indexed capabilityHash, uint256 timestamp ); event TaskProofAdded( uint256 indexed credentialId, address indexed agent, bytes32 indexed capabilityHash, uint256 newProofCount, uint256 timestamp ); event IssuerApproved(address indexed issuer, uint256 timestamp); event IssuerRemoved(address indexed issuer, uint256 timestamp); // ----------------------------------------------------------------------- // Constructor // ----------------------------------------------------------------------- constructor(address identityRegistry_) { require(identityRegistry_ != address(0), "VaultfireCapabilityCredentials: zero identityRegistry"); identityRegistry = IIdentityRegistry_CC(identityRegistry_); owner = msg.sender; // Owner is always an approved issuer approvedIssuers[msg.sender] = true; } // ----------------------------------------------------------------------- // Modifiers // ----------------------------------------------------------------------- modifier onlyOwner() { require(msg.sender == owner, "VaultfireCapabilityCredentials: only owner"); _; } modifier onlyIssuer() { require( approvedIssuers[msg.sender], "VaultfireCapabilityCredentials: not an approved issuer" ); _; } // ----------------------------------------------------------------------- // Admin functions // ----------------------------------------------------------------------- function approveIssuer(address issuer) external onlyOwner { require(issuer != address(0), "VaultfireCapabilityCredentials: zero address"); approvedIssuers[issuer] = true; emit IssuerApproved(issuer, block.timestamp); } function removeIssuer(address issuer) external onlyOwner { approvedIssuers[issuer] = false; emit IssuerRemoved(issuer, block.timestamp); } // ----------------------------------------------------------------------- // Core functions // ----------------------------------------------------------------------- /** * @notice Issue a capability credential to an agent. * @param agent The agent receiving the credential. * @param capabilityHash keccak256 of the capability name (e.g., "data-analysis"). * @param expiresAt Expiration timestamp (0 for permanent). * @return credentialId The newly created credential ID. */ function issueCredential( address agent, bytes32 capabilityHash, uint256 expiresAt ) external nonReentrant onlyIssuer returns (uint256 credentialId) { require(agent != address(0), "VaultfireCapabilityCredentials: zero agent"); require(capabilityHash != bytes32(0), "VaultfireCapabilityCredentials: zero capability"); require( identityRegistry.isAgentActive(agent), "VaultfireCapabilityCredentials: agent not registered" ); require( expiresAt == 0 || expiresAt > block.timestamp, "VaultfireCapabilityCredentials: expiry in the past" ); // Check no active credential for this capability exists uint256 existingId = agentCapability[agent][capabilityHash]; if (existingId != 0) { Credential storage existing = _credentials[existingId]; require( !existing.active || (existing.expiresAt > 0 && existing.expiresAt <= block.timestamp), "VaultfireCapabilityCredentials: active credential already exists" ); // Deactivate the expired one if (existing.active) { existing.active = false; agentProfiles[agent].activeCredentials -= 1; } } unchecked { credentialId = ++_credentialCounter; } _credentials[credentialId] = Credential({ id: credentialId, agent: agent, capabilityHash: capabilityHash, issuedBy: msg.sender, issuedAt: block.timestamp, expiresAt: expiresAt, taskProofCount: 0, active: true }); agentCapability[agent][capabilityHash] = credentialId; _agentCredentials[agent].push(credentialId); _capabilityHolders[capabilityHash].push(agent); AgentCredentialProfile storage profile = agentProfiles[agent]; profile.totalCredentials += 1; profile.activeCredentials += 1; emit CredentialIssued( credentialId, agent, capabilityHash, msg.sender, expiresAt, block.timestamp ); } /** * @notice Add a task completion proof to an existing credential. * @dev Only approved issuers can add proofs. * @param credentialId The credential to add proof to. */ function addTaskProof(uint256 credentialId) external nonReentrant onlyIssuer { require( credentialId > 0 && credentialId <= _credentialCounter, "VaultfireCapabilityCredentials: credential does not exist" ); Credential storage cred = _credentials[credentialId]; require(cred.active, "VaultfireCapabilityCredentials: credential not active"); require( cred.expiresAt == 0 || cred.expiresAt > block.timestamp, "VaultfireCapabilityCredentials: credential expired" ); cred.taskProofCount += 1; agentProfiles[cred.agent].totalTaskProofs += 1; emit TaskProofAdded( credentialId, cred.agent, cred.capabilityHash, cred.taskProofCount, block.timestamp ); } /** * @notice Revoke a credential. * @dev Only the issuer or owner can revoke. * @param credentialId The credential to revoke. */ function revokeCredential(uint256 credentialId) external nonReentrant { require( credentialId > 0 && credentialId <= _credentialCounter, "VaultfireCapabilityCredentials: credential does not exist" ); Credential storage cred = _credentials[credentialId]; require(cred.active, "VaultfireCapabilityCredentials: credential not active"); require( msg.sender == cred.issuedBy || msg.sender == owner, "VaultfireCapabilityCredentials: only issuer or owner can revoke" ); cred.active = false; agentCapability[cred.agent][cred.capabilityHash] = 0; agentProfiles[cred.agent].activeCredentials -= 1; emit CredentialRevoked(credentialId, cred.agent, cred.capabilityHash, block.timestamp); } // ----------------------------------------------------------------------- // View functions // ----------------------------------------------------------------------- function getCredential(uint256 credentialId) external view returns (Credential memory) { require( credentialId > 0 && credentialId <= _credentialCounter, "VaultfireCapabilityCredentials: credential does not exist" ); return _credentials[credentialId]; } function getAgentCredentials(address agent) external view returns (uint256[] memory) { return _agentCredentials[agent]; } function getCapabilityHolders(bytes32 capabilityHash) external view returns (address[] memory) { return _capabilityHolders[capabilityHash]; } function hasActiveCredential(address agent, bytes32 capabilityHash) external view returns (bool) { uint256 credId = agentCapability[agent][capabilityHash]; if (credId == 0) return false; Credential memory cred = _credentials[credId]; return cred.active && (cred.expiresAt == 0 || cred.expiresAt > block.timestamp); } function getAgentProfile(address agent) external view returns ( uint256 totalCredentials, uint256 activeCredentials, uint256 totalTaskProofs ) { AgentCredentialProfile memory p = agentProfiles[agent]; return (p.totalCredentials, p.activeCredentials, p.totalTaskProofs); } function credentialCount() external view returns (uint256) { return _credentialCounter; } }