Overview
The rating system in IOTA Repstation allows parties to rate each other after completing a deal. Ratings are permanent, blockchain-recorded assessments that build into reputation profiles over time.
Rating Requirements
Deal-Bound Ratings
All ratings must be tied to a specific deal:
- Deal must be in
CLOSED state
- Only deal participants can rate each other
- Each participant can rate the other party once per deal
- Ratings reference the specific deal and subject
Score Range
Ratings use a standardized 1-100 score system:
- 1-20: Very Poor
- 21-40: Poor
- 41-60: Average
- 61-80: Good
- 81-100: Excellent
Rating Categories
Categories help organize different aspects of performance:
Common Categories
reliability - Did they deliver as promised?
communication - How well did they communicate?
quality - Was the work/product high quality?
speed - How quickly did they complete the work?
professionalism - Were they professional throughout?
Custom Categories
Applications can define their own categories:
// NFT Marketplace
const categories = [
'artwork_quality',
'authenticity',
'shipping_speed',
'packaging'
];
// Service Platform
const categories = [
'technical_skill',
'deadline_adherence',
'code_quality',
'project_management'
];
Creating Ratings
Basic Rating
const { endorsement_id } = await client.rate({
signer: raterWallet,
deal_id: completedDealId,
score: 85,
category: 'reliability'
});
Multiple Category Ratings
Rate different aspects separately:
// Rate communication
await client.rate({
signer: buyerWallet,
deal_id: dealId,
score: 92,
category: 'communication'
});
// Rate delivery speed
await client.rate({
signer: buyerWallet,
deal_id: dealId,
score: 78,
category: 'speed'
});
// Rate product quality
await client.rate({
signer: buyerWallet,
deal_id: dealId,
score: 95,
category: 'quality'
});
Rating Management
Updating Ratings
Ratings can be updated using the endorsement capability:
await client.updateRating({
signer: raterWallet,
endorsement_id: endorsementId,
endorsement_cap_id: endorsementCapId,
new_score: 90, // Updated score
});
Revoking Ratings
Ratings can be completely revoked:
await client.revokeRating({
signer: raterWallet,
endorsement_id: endorsementId,
endorsement_cap_id: endorsementCapId
});
Revoked ratings are permanently removed and cannot be restored. Use updates instead of revocation when possible.
Rating Aggregation
Global Reputation
All ratings for a user are aggregated globally:
const profile = await client.getReputationProfile({
walletAddress: userWallet
});
console.log(`Global average: ${profile.global.average}/100`);
console.log(`Total ratings: ${profile.global.count}`);
App-Scoped Reputation
Ratings are also tracked per application:
// Each app namespace maintains separate stats
profile.appScoped.forEach(app => {
console.log(`App: ${app.appOwner}`);
console.log(`Average: ${app.stats.average}/100`);
console.log(`Count: ${app.stats.count}`);
});
Rating Data Structure
Endorsement Object
interface Endorsement {
id: string;
rater: string; // Wallet address of rater
deal_id: string; // Associated deal
score: number; // 1-100 rating score
category: string; // Rating category
timestamp: string; // When rating was created
last_updated?: string; // When rating was last modified
}
Rating Statistics
interface RatingStats {
average: number; // Average of all ratings
count: number; // Total number of ratings
distribution?: { // Optional score distribution
excellent: number; // 81-100 scores
good: number; // 61-80 scores
average: number; // 41-60 scores
poor: number; // 21-40 scores
very_poor: number; // 1-20 scores
};
}
Best Practices
1. Meaningful Categories
Use categories that matter to your use case:
// Good for marketplaces
const marketplaceCategories = [
'product_quality',
'shipping_speed',
'customer_service',
'value_for_money'
];
// Good for freelancing
const freelanceCategories = [
'skill_level',
'communication',
'deadline_compliance',
'work_quality'
];
2. Encourage Honest Ratings
Design your UI to promote fair ratings:
function RatingComponent({ onSubmit }) {
const [scores, setScores] = useState({});
return (
<div className="rating-form">
<h3>Rate Your Experience</h3>
<RatingSlider
label="Communication (How well did they communicate?)"
value={scores.communication || 50}
onChange={(score) => setScores({...scores, communication: score})}
helpText="Consider responsiveness, clarity, and professionalism"
/>
<RatingSlider
label="Quality (How was the work quality?)"
value={scores.quality || 50}
onChange={(score) => setScores({...scores, quality: score})}
helpText="Consider attention to detail and meeting requirements"
/>
<button onClick={() => submitRatings(scores)}>
Submit Ratings
</button>
</div>
);
}
3. Rating Timing
Prompt for ratings at the right time:
class OrderService {
async markOrderDelivered(orderId: string) {
const order = await this.getOrder(orderId);
// Close the deal first
await this.reputation.closeDeal({
signer: this.adminWallet,
deal_id: order.reputationDealId
});
// Send rating reminders after short delay
setTimeout(() => {
this.sendRatingReminder(order.buyerId, order.id);
this.sendRatingReminder(order.sellerId, order.id);
}, 24 * 60 * 60 * 1000); // 24 hours
}
}
Error Handling
Common rating errors:
try {
await client.rate({
signer: wallet,
deal_id: dealId,
score: score,
category: category
});
} catch (error) {
switch (error.code) {
case 'DEAL_NOT_CLOSED':
showError('Deal must be completed before rating');
break;
case 'ALREADY_RATED':
showError('You have already rated this deal. Use update instead.');
break;
case 'NOT_DEAL_PARTICIPANT':
showError('Only deal participants can rate each other');
break;
case 'INVALID_SCORE':
showError('Rating must be between 1 and 100');
break;
case 'CANNOT_RATE_SELF':
showError('You cannot rate yourself');
break;
}
}
Integration Examples
E-commerce Rating Flow
class EcommerceRatings {
async handleOrderCompletion(orderId: string) {
const order = await this.db.orders.findById(orderId);
// Close reputation deal
await this.reputation.closeDeal({
signer: this.adminWallet,
deal_id: order.reputationDealId
});
// Enable rating UI for both parties
await this.enableRatings(order.buyerId, order.sellerId, order.id);
}
async submitBuyerRating(buyerId: string, orderId: string, ratings: any) {
const order = await this.db.orders.findById(orderId);
// Rate seller on multiple categories
for (const [category, score] of Object.entries(ratings)) {
await this.reputation.rate({
signer: buyerId,
deal_id: order.reputationDealId,
score: score as number,
category
});
}
}
}
class ServiceRatings {
async completeProject(projectId: string, clientId: string, providerId: string) {
const project = await this.getProject(projectId);
// Close deal
await this.reputation.closeDeal({
signer: clientId,
deal_id: project.dealId
});
// Client rates provider
await this.reputation.rate({
signer: clientId,
deal_id: project.dealId,
score: project.clientRating.skill,
category: 'technical_skill'
});
// Provider rates client
await this.reputation.rate({
signer: providerId,
deal_id: project.dealId,
score: project.providerRating.clarity,
category: 'requirements_clarity'
});
}
}
Next Steps