Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,9 +91,9 @@ The stack is EVM based, we support Solana with NeonEVM.
### v2 - Base Sepolia

```txt
SERVICE_REGISTRY_ADDRESS=0xC59D70954BFFf1aB687aB28E86324703B5D23dcC
AGENT_REGISTRY_ADDRESS=0xd5aD1B6c462C7cCF641Df8cdac356bc4a7C20400
TASK_REGISTRY_ADDRESS=0x7eaB59d9121c76eF44a101C2c1d2121cC8e871fd
AGENT_REGISTRY_ADDRESS=0xABC2AC53Aaf217B70825701c1a5aB750CD60DbaF
TASK_REGISTRY_ADDRESS=0x859bBE15EfbE62fD51DB5C24B01048A73839E141
SERVICE_REGISTRY_ADDRESS=0x68A88024060fD8Fe4dE848de1abB7F6d9225cCa8
```

### v1 - Neon Devnet
Expand Down
Binary file added packages/contracts/.DS_Store
Binary file not shown.
6 changes: 3 additions & 3 deletions packages/contracts/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ Contracts are deployed to the following networks, we support Solana via NeonEVM.
## Base Sepolia

```txt
SERVICE_REGISTRY_ADDRESS=0xC59D70954BFFf1aB687aB28E86324703B5D23dcC
AGENT_REGISTRY_ADDRESS=0xd5aD1B6c462C7cCF641Df8cdac356bc4a7C20400
TASK_REGISTRY_ADDRESS=0x7eaB59d9121c76eF44a101C2c1d2121cC8e871fd
AGENT_REGISTRY_ADDRESS=0xABC2AC53Aaf217B70825701c1a5aB750CD60DbaF
TASK_REGISTRY_ADDRESS=0x859bBE15EfbE62fD51DB5C24B01048A73839E141
SERVICE_REGISTRY_ADDRESS=0x68A88024060fD8Fe4dE848de1abB7F6d9225cCa8
```

## Neon Devnet
Expand Down
132 changes: 98 additions & 34 deletions packages/contracts/contracts/AgentsRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,50 @@ contract AgentsRegistry is Ownable, IProposalStruct {
address owner;
address agent;
uint256 reputation;
bool isRegistered;
Proposal[] proposals;
uint256 totalRatings;
}

ServiceRegistry public serviceRegistry;
address public taskRegistry;

mapping(address => AgentData) public agents;
Proposal[] public proposals;
mapping(uint256 => ServiceProposal) public proposals;
uint256 public nextProposalId;

modifier onlyRegistered(address agent) {
require(agents[agent].isRegistered, "Agent not registered");
modifier onlyAgentOwner(address agent) {
require(agents[agent].owner == msg.sender, "Not the owner of the agent");
_;
}

constructor(ServiceRegistry _serviceRegistry) Ownable(msg.sender) {
serviceRegistry = _serviceRegistry;
nextProposalId = 1;
}

event AgentRegistered(address indexed agent, address indexed owner, string name, string agentUri);
event ReputationUpdated(address indexed agent, uint256 newReputation);
event ServiceAdded(address indexed agent, uint256 name);

event ProposalAdded(address indexed agent, uint256 proposalId, string name, uint256 price);
event ProposalRemoved(address indexed agent, uint256 proposalId);
event ProposalUpdated(address indexed agent, uint256 proposalId, uint256 price);

/**
* @dev Sets the address of the TaskRegistry contract.
* @param _taskRegistry The address of the TaskRegistry contract.
*/
function setTaskRegistry(address _taskRegistry) external onlyOwner {
require(_taskRegistry != address(0), "Invalid address");
taskRegistry = _taskRegistry;
}

/**
* @dev Sets the address of the ServiceRegistry contract.
* @param _serviceRegistry The address of the ServiceRegistry contract.
*/
function setServiceRegistry(address _serviceRegistry) external onlyOwner {
require(_serviceRegistry != address(0), "Invalid address");
serviceRegistry = ServiceRegistry(_serviceRegistry);
}

/**
* @dev Registers a new agent with the given details.
Expand All @@ -64,8 +86,8 @@ contract AgentsRegistry is Ownable, IProposalStruct {
string memory agentUri,
string memory serviceName,
uint256 servicePrice
) external returns (bool) {
require(!agents[agent].isRegistered, "Agent already registered");
) external returns (uint256) {
require(agents[agent].agent == address(0), "Agent already registered");
require(serviceRegistry.isServiceRegistered(serviceName), "Service not registered");

AgentData storage agentData = agents[agent];
Expand All @@ -74,53 +96,95 @@ contract AgentsRegistry is Ownable, IProposalStruct {
agentData.owner = msg.sender;
agentData.agent = agent;
agentData.reputation = 0;
agentData.isRegistered = true;
Proposal memory proposal = Proposal(agent, serviceName, servicePrice, nextProposalId);
agentData.proposals.push(proposal);
proposals.push(proposal);

ServiceProposal memory proposal = ServiceProposal(agent, serviceName, servicePrice, nextProposalId, false);
proposals[nextProposalId] = proposal;

nextProposalId++;
emit AgentRegistered(agent, msg.sender, name, agentUri);
emit ProposalAdded(agent, proposal.proposalId, serviceName, servicePrice);

return true;
return nextProposalId - 1;
}

function updateReputation(address agent, uint256 _reputation) external onlyOwner onlyRegistered(agent) {
agents[agent].reputation = _reputation;
emit ReputationUpdated(agent, _reputation);

/**
* @dev Adds a new proposal for an agent.
* @param agent The address of the agent.
* @param serviceName The name of the service.
* @param servicePrice The price of the service.
* @return true if the proposal was added successfully, false otherwise.
*
* Requirements:
*
* - The caller must be the owner of the agent.
* - The agent must be registered.
* - The service must be registered.
*
* Emits a {ProposalAdded} event.
*/
function addProposal(address agent, string memory serviceName, uint256 servicePrice) external onlyAgentOwner(agent) returns (uint256) {
require(serviceRegistry.isServiceRegistered(serviceName), "Service not registered");

ServiceProposal memory proposal = ServiceProposal(agent, serviceName, servicePrice, nextProposalId, true);
proposals[nextProposalId] = proposal;

nextProposalId++;
emit ProposalAdded(agent, proposal.proposalId, serviceName, servicePrice);

return nextProposalId - 1;
}

/**
* @dev Removes a proposal for an agent.
* @param agent The address of the agent.
* @param proposalId The ID of the proposal to remove.
* @return true if the proposal was removed successfully, false otherwise.
*
* Requirements:
*
* - The caller must be the owner of the agent.
* - The agent must be registered.
* - The proposal must exist.
*
* Emits a {ProposalRemoved} event.
*/
function removeProposal(address agent, uint256 proposalId) external onlyAgentOwner(agent) returns (bool) {
require(proposals[proposalId].issuer == agent, "ServiceProposal not found");

delete proposals[proposalId];
emit ProposalRemoved(agent, proposalId);

return true;
}

function getReputation(address agent) external view onlyRegistered(agent) returns (uint256) {
function addRating(address agent, uint256 _rating) public returns (uint256) {
require(msg.sender == taskRegistry, "Not the TaskRegistry contract");
require(_rating >= 0 && _rating <= 100, "Rating must be between 0 and 100");
agents[agent].totalRatings += 1;
agents[agent].reputation = (agents[agent].reputation * (agents[agent].totalRatings - 1) + _rating) / agents[agent].totalRatings;
emit ReputationUpdated(agent, agents[agent].reputation);

return agents[agent].reputation;
}

function isRegistered(address agent) external view returns (bool) {
return agents[agent].isRegistered;
function getReputation(address agent) external view returns (uint256) {
return agents[agent].reputation;
}

/**
* @dev get agent data
* @param _agent The address of the agent
* @return name The name of the agent
* @return agentUri The URI pointing to the agent's metadata
* @return owner The owner address of the agent
* @return agent The agent contract address
* @return reputation The reputation score of the agent
* @dev Returns the data of an agent.
* @param _agent The address of the agent.
* @return AgentData The data of the agent.
*/
function getAgentData(address _agent) external view returns (
string memory name,
string memory agentUri,
address owner,
address agent,
uint256 reputation
function getAgentData(address _agent) external view returns (AgentData memory
) {
AgentData storage data = agents[_agent];
return (data.name, data.agentUri, data.owner, data.agent, data.reputation);
return data;
}


function getProposal(uint256 proposalId) external view returns (Proposal memory) {
function getProposal(uint256 proposalId) external view returns (ServiceProposal memory) {
return proposals[proposalId];
}
}
2 changes: 1 addition & 1 deletion packages/contracts/contracts/ServiceRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ contract ServiceRegistry is Ownable {
* @param name The name of the service.
* @return The ID of the registered service.
*/
function registerService(string memory name, string memory category, string memory description) external onlyOwner returns (Service memory) {
function registerService(string memory name, string memory category, string memory description) external returns (Service memory) {
require(!this.isServiceRegistered(name), "Service already registered");

Service memory service = Service({
Expand Down
59 changes: 52 additions & 7 deletions packages/contracts/contracts/TaskRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import "./lib/TransferHelper.sol";
*/
contract TaskRegistry is Ownable, IProposalStruct {

enum TaskStatus { CREATED, ASSIGNED, COMPLETED, FAILED }
enum TaskStatus { CREATED, ASSIGNED, COMPLETED, CANCELED }

struct TaskData {
uint256 id;
Expand All @@ -23,22 +23,30 @@ contract TaskRegistry is Ownable, IProposalStruct {
address assignee;
uint256 proposalId;
string result;
uint8 rating;
}

mapping(uint256 => TaskData) public tasks;
mapping(address => uint256[]) public issuerTasks;
uint256 private nextTaskId;
AgentsRegistry public agentRegistry;

modifier onlyTaskIssuer(uint256 taskId) {
require(msg.sender == tasks[taskId].issuer, "Not the issuer of the task");
_;
}
constructor(AgentsRegistry _agentRegistry) Ownable(msg.sender) {
agentRegistry = _agentRegistry;
nextTaskId = 1;
}

event TaskCreated(address indexed issuer, address indexed assignee, uint256 taskId, uint256 proposalId, string prompt);
event TaskStatusChanged(uint256 indexed taskId, TaskStatus status);
event TaskAssigned(uint256 indexed taskId, address indexed agent);
event ProposalApproved(uint256 indexed taskId, Proposal proposal);
event TaskCanceled(uint256 indexed taskId);
event ProposalApproved(uint256 indexed taskId, ServiceProposal proposal);
event TaskCompleted(uint256 indexed taskId, string result);

event TaskRated(uint256 indexed taskId, uint8 rating);
/**
* @dev Creates a new task with the given prompt and task type.
* @param prompt The description or prompt of the task.
Expand All @@ -48,8 +56,8 @@ contract TaskRegistry is Ownable, IProposalStruct {
string memory prompt,
uint256 proposalId
) external payable returns (TaskData memory) {
Proposal memory proposal = agentRegistry.getProposal(proposalId);
require(proposal.issuer != address(0), "Proposal not found");
ServiceProposal memory proposal = agentRegistry.getProposal(proposalId);
require(proposal.issuer != address(0), "ServiceProposal not found");
require(proposal.price == msg.value, "Invalid price");

TaskData storage task = tasks[nextTaskId];
Expand All @@ -73,19 +81,56 @@ contract TaskRegistry is Ownable, IProposalStruct {
*/
function completeTask(uint256 taskId, string memory result) external {
TaskData storage task = tasks[taskId];
require(msg.sender == task.assignee || msg.sender == owner(), "Not authorized");
require(msg.sender == task.assignee, "Not authorized");
require(task.status == TaskStatus.ASSIGNED, "Invalid task status");

task.status = TaskStatus.COMPLETED;
task.result = result;
Proposal memory proposal = agentRegistry.getProposal(task.proposalId);
ServiceProposal memory proposal = agentRegistry.getProposal(task.proposalId);

TransferHelper.safeTransferETH(proposal.issuer, proposal.price);

emit TaskStatusChanged(taskId, task.status);
emit TaskCompleted(taskId, result);
}

/**
* @dev Rated a completed task, called by the task issuer
* @param taskId The ID of the task.
* @param rating task rating from 0 to 100.
*/
function rateTask(uint256 taskId, uint8 rating) onlyTaskIssuer(taskId) external {
TaskData storage task = tasks[taskId];

require(task.status == TaskStatus.COMPLETED, "Task is not completed");
require(task.rating == 0, "Task got rating already");
require(rating >= 0 && rating <= 100, "Rating must be between 0 and 100");

task.rating = rating;
ServiceProposal memory proposal = agentRegistry.getProposal(task.proposalId);

agentRegistry.addRating(proposal.issuer, rating);

emit TaskRated(taskId, rating);
}

/**
* @dev Cancels a task that is in ASSIGNED status and refunds the payment.
* @param taskId The ID of the task to cancel.
*/
function cancelTask(uint256 taskId) external onlyTaskIssuer(taskId) {
TaskData storage task = tasks[taskId];
require(task.status != TaskStatus.COMPLETED && task.status != TaskStatus.CANCELED, "Task cannot be canceled");

task.status = TaskStatus.CANCELED;
ServiceProposal memory proposal = agentRegistry.getProposal(task.proposalId);

// Refund the payment to the issuer
TransferHelper.safeTransferETH(task.issuer, proposal.price);

emit TaskStatusChanged(taskId, task.status);
emit TaskCanceled(taskId);
}

function getTasksByIssuer(address issuer) external view returns (uint256[] memory) {
return issuerTasks[issuer];
Expand Down
3 changes: 2 additions & 1 deletion packages/contracts/contracts/interfaces/IProposalStruct.sol
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
interface IProposalStruct {
struct Proposal {
struct ServiceProposal {
address issuer;
string serviceName;
uint256 price;
uint256 proposalId;
bool isActive;
}
}
1 change: 1 addition & 0 deletions packages/contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import "@nomicfoundation/hardhat-toolbox";
import "@nomicfoundation/hardhat-ignition-ethers";
import "@nomicfoundation/hardhat-verify";
import * as dotenv from "dotenv";
import 'solidity-coverage';

dotenv.config();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"_format": "hh-sol-dbg-1",
"buildInfo": "../build-info/4f85bbb070e464b57411e80df715dabb.json"
"buildInfo": "../build-info/c531b525831bad0f98bf93a9d045f697.json"
}
Loading