Skip to content
Open
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
25 changes: 23 additions & 2 deletions contract/contracts/agent_registry/src/agent.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use soroban_sdk::{Address, Env, String, Vec};
use crate::errors::AgentError;
use crate::events;
use crate::storage::DataKey;
use crate::types::{AgentInfo, AgentTransaction, ContractState, PauseState};
use crate::types::{AgentInfo, AgentTransaction, ContractState, PauseState, Rating};

fn check_paused(env: &Env) -> Result<(), AgentError> {
if env
Expand Down Expand Up @@ -165,7 +165,13 @@ pub fn rate_agent(
return Err(AgentError::AlreadyRated);
}

env.storage().persistent().set(&rating_key, &true);
let rating = Rating {
rater: rater.clone(),
agent: agent.clone(),
score,
rated_at: env.ledger().timestamp(),
};
env.storage().persistent().set(&rating_key, &rating);
env.storage()
.persistent()
.extend_ttl(&rating_key, 500000, 500000);
Expand Down Expand Up @@ -195,6 +201,21 @@ pub fn get_agent_count(env: &Env) -> u32 {
.unwrap_or(0)
}

/// Returns the average rating scaled by 100 (e.g. 450 = 4.5), or None if the agent is not found.
pub fn get_agent_average_rating(env: &Env, agent: Address) -> Option<u32> {
let key = DataKey::Agent(agent);
env.storage()
.persistent()
.get::<DataKey, AgentInfo>(&key)
.map(|info| info.average_rating())
}

/// Returns the individual Rating left by a rater for an agent, if it exists.
pub fn get_rating(env: &Env, agent: Address, rater: Address) -> Option<Rating> {
let rating_key = DataKey::AgentRating(agent, rater);
env.storage().persistent().get(&rating_key)
}

pub fn register_transaction(
env: &Env,
transaction_id: String,
Expand Down
101 changes: 11 additions & 90 deletions contract/contracts/agent_registry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,25 +12,19 @@ mod types;
mod tests;

pub use agent::{
complete_transaction, get_agent_count, get_agent_info, rate_agent, register_agent,
register_transaction, verify_agent,
complete_transaction, get_agent_average_rating, get_agent_count, get_agent_info, get_rating,
rate_agent, register_agent, register_transaction, verify_agent,
};
pub use errors::AgentError;
pub use storage::DataKey;
pub use types::{AgentInfo, AgentTransaction, ContractState, PauseState};
pub use types::{AgentInfo, AgentTransaction, ContractState, PauseState, Rating};

#[contract]
pub struct AgentRegistryContract;

#[contractimpl]
impl AgentRegistryContract {
/// Initialize the contract with an admin address.
///
/// # Arguments
/// * `admin` - The address that will have admin privileges to verify agents
///
/// # Errors
/// * `AlreadyInitialized` - If the contract has already been initialized
pub fn initialize(env: Env, admin: Address) -> Result<(), AgentError> {
if env.storage().persistent().has(&DataKey::Initialized) {
return Err(AgentError::AlreadyInitialized);
Expand All @@ -56,24 +50,10 @@ impl AgentRegistryContract {
Ok(())
}

/// Get the current contract state.
///
/// # Returns
/// * `Option<ContractState>` - The contract state if initialized
pub fn get_state(env: Env) -> Option<ContractState> {
env.storage().instance().get(&DataKey::State)
}

/// Register a new agent on-chain.
///
/// # Arguments
/// * `agent` - The address of the agent registering
/// * `external_profile_hash` - Hash reference to agent's external profile (IPFS, etc.)
///
/// # Errors
/// * `NotInitialized` - If the contract hasn't been initialized
/// * `AgentAlreadyRegistered` - If the agent is already registered
/// * `InvalidProfileHash` - If the profile hash is empty
pub fn register_agent(
env: Env,
agent: Address,
Expand All @@ -82,38 +62,10 @@ impl AgentRegistryContract {
agent::register_agent(&env, agent, external_profile_hash)
}

/// Verify a registered agent (admin only).
///
/// # Arguments
/// * `admin` - The admin address performing the verification
/// * `agent` - The address of the agent to verify
///
/// # Errors
/// * `NotInitialized` - If the contract hasn't been initialized
/// * `Unauthorized` - If the caller is not the admin
/// * `AgentNotFound` - If the agent doesn't exist
/// * `AlreadyVerified` - If the agent is already verified
pub fn verify_agent(env: Env, admin: Address, agent: Address) -> Result<(), AgentError> {
agent::verify_agent(&env, admin, agent)
}

/// Rate an agent after completing a transaction (1-5 stars).
///
/// # Arguments
/// * `rater` - The address of the person rating (tenant or landlord)
/// * `agent` - The address of the agent being rated
/// * `score` - The rating score (1-5)
/// * `transaction_id` - The ID of the completed transaction
///
/// # Errors
/// * `NotInitialized` - If the contract hasn't been initialized
/// * `InvalidRatingScore` - If score is not between 1 and 5
/// * `AgentNotFound` - If the agent doesn't exist
/// * `AgentNotVerified` - If the agent is not verified
/// * `TransactionNotFound` - If the transaction doesn't exist
/// * `TransactionNotCompleted` - If the transaction is not marked as completed
/// * `NotTransactionParty` - If the rater wasn't part of the transaction
/// * `AlreadyRated` - If the rater has already rated this agent
pub fn rate_agent(
env: Env,
rater: Address,
Expand All @@ -124,36 +76,22 @@ impl AgentRegistryContract {
agent::rate_agent(&env, rater, agent, score, transaction_id)
}

/// Get information about a registered agent.
///
/// # Arguments
/// * `agent` - The address of the agent
///
/// # Returns
/// * `Option<AgentInfo>` - The agent information if they exist
pub fn get_agent_info(env: Env, agent: Address) -> Option<AgentInfo> {
agent::get_agent_info(&env, agent)
}

/// Get the total count of registered agents.
///
/// # Returns
/// * `u32` - The total number of agents registered
pub fn get_agent_count(env: Env) -> u32 {
agent::get_agent_count(&env)
}

/// Register a transaction involving an agent.
/// This is called when a rent agreement or property transaction is created.
///
/// # Arguments
/// * `transaction_id` - Unique identifier for the transaction
/// * `agent` - The agent involved in the transaction
/// * `parties` - Vector of addresses involved (tenant, landlord, etc.)
///
/// # Errors
/// * `NotInitialized` - If the contract hasn't been initialized
/// * `AgentNotFound` - If the agent doesn't exist
pub fn get_agent_average_rating(env: Env, agent: Address) -> Option<u32> {
agent::get_agent_average_rating(&env, agent)
}

pub fn get_rating(env: Env, agent: Address, rater: Address) -> Option<Rating> {
agent::get_rating(&env, agent, rater)
}

pub fn register_transaction(
env: Env,
transaction_id: String,
Expand All @@ -163,17 +101,6 @@ impl AgentRegistryContract {
agent::register_transaction(&env, transaction_id, agent, parties)
}

/// Mark a transaction as completed.
/// This enables the parties to rate the agent.
///
/// # Arguments
/// * `transaction_id` - The ID of the transaction to complete
/// * `agent` - The agent address (for verification)
///
/// # Errors
/// * `NotInitialized` - If the contract hasn't been initialized
/// * `TransactionNotFound` - If the transaction doesn't exist
/// * `Unauthorized` - If the caller is not the agent for this transaction
pub fn complete_transaction(
env: Env,
transaction_id: String,
Expand All @@ -182,7 +109,6 @@ impl AgentRegistryContract {
agent::complete_transaction(&env, transaction_id, agent)
}

/// Pause the contract (admin only).
pub fn pause(env: Env, admin: Address, reason: String) -> Result<(), AgentError> {
let state = Self::get_state(env.clone()).ok_or(AgentError::NotInitialized)?;

Expand Down Expand Up @@ -212,7 +138,6 @@ impl AgentRegistryContract {
Ok(())
}

/// Unpause the contract (admin only).
pub fn unpause(env: Env, admin: Address) -> Result<(), AgentError> {
let state = Self::get_state(env.clone()).ok_or(AgentError::NotInitialized)?;

Expand All @@ -232,7 +157,6 @@ impl AgentRegistryContract {
Ok(())
}

/// Check if the contract is paused.
pub fn is_paused(env: Env) -> bool {
env.storage()
.instance()
Expand All @@ -241,7 +165,6 @@ impl AgentRegistryContract {
.unwrap_or(false)
}

/// Propose a new admin (two-step transfer).
pub fn propose_admin(env: Env, admin: Address, new_admin: Address) -> Result<(), AgentError> {
let state = Self::get_state(env.clone()).ok_or(AgentError::NotInitialized)?;

Expand All @@ -260,7 +183,6 @@ impl AgentRegistryContract {
Ok(())
}

/// Accept admin role (pending admin only).
pub fn accept_admin(env: Env, new_admin: Address) -> Result<(), AgentError> {
let mut state = Self::get_state(env.clone()).ok_or(AgentError::NotInitialized)?;

Expand All @@ -287,7 +209,6 @@ impl AgentRegistryContract {
Ok(())
}

/// Get the pending admin address if any.
pub fn get_pending_admin(env: Env) -> Option<Address> {
env.storage().instance().get(&DataKey::PendingAdmin)
}
Expand Down
8 changes: 7 additions & 1 deletion contract/contracts/agent_registry/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,15 @@ pub struct AgentInfo {
}

impl AgentInfo {
/// Returns the average rating scaled by 100 (e.g. 450 = 4.5).
/// Returns 0 if there are no ratings.
pub fn average_rating(&self) -> u32 {
if self.total_ratings == 0 {
return 0;
}
self.total_score
.checked_div(self.total_ratings)
.checked_mul(100)
.and_then(|v| v.checked_div(self.total_ratings))
.unwrap_or(0)
}
}
Expand Down