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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ A provider with an Azeth smart account is a **first-class participant** in the m

## Links

- [Azeth MCP Server](https://www.npmjs.com/package/@azeth/mcp-server) — 32 tools for AI agents
- [Azeth MCP Server](https://www.npmjs.com/package/@azeth/mcp-server) — 34 tools for AI agents
- [Azeth SDK](https://www.npmjs.com/package/@azeth/sdk) — TypeScript SDK
- [Agent Starter Template](https://github.com/azeth-protocol/agent-starter) — Fork and build
- [Website](https://azeth.ai)
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
"scripts": {
"demo": "tsx src/demo.ts",
"demo:provider": "tsx src/provider.ts",
"demo:consumer": "tsx src/consumer.ts"
"demo:consumer": "tsx src/consumer.ts",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@azeth/sdk": "^0.2.0",
"@azeth/provider": "^0.2.0",
"@azeth/sdk": "^0.2.22",
"@azeth/provider": "^0.2.21",
"hono": "^4.7.0",
"@hono/node-server": "^1.13.0",
"dotenv": "^16.4.0",
Expand Down
37 changes: 24 additions & 13 deletions src/consumer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,14 @@ export async function runConsumer(): Promise<void> {
// 4. Check reputation before trusting
log('\nChecking provider reputation...');
const reputation = await agent.getWeightedReputation(service.tokenId);
log(` Composite score: ${chalk.bold(String(reputation.compositeScore))}/100`);
log(` Total interactions: ${reputation.totalInteractions}`);

if (reputation.compositeScore < 20) {
// weightedValue is 18-decimal fixed point on a -100..100 scale, weighted by USD paid
const score = reputation.opinionCount > 0n ? Number(reputation.weightedValue) / 1e18 : 0;
log(` Weighted score: ${chalk.bold(score.toFixed(1))} (range -100..100)`);
log(` Opinions: ${reputation.opinionCount}`);

if (reputation.opinionCount === 0n) {
log(chalk.yellow(' No ratings yet — proceeding with caution (small payment)'));
} else if (score < 20) {
log(chalk.yellow(' Low reputation — proceeding with caution (small payment)'));
} else {
log(chalk.green(' Reputation acceptable — proceeding with payment'));
Expand All @@ -94,16 +98,23 @@ export async function runConsumer(): Promise<void> {
log(` Response time: ${responseTimeMs}ms`);

// 6. Rate the provider on-chain
// Opinions are payment-gated: the ReputationModule requires >= $1 net USD paid
// to the provider before accepting a rating (anti-Sybil protection).
log('\nSubmitting on-chain reputation feedback...');
const qualityScore = responseTimeMs < 2000 ? 90 : responseTimeMs < 5000 ? 70 : 50;
await agent.submitOpinion({
serviceTokenId: service.tokenId,
success: true,
responseTimeMs,
qualityScore,
});
log(` Opinion submitted! Quality: ${qualityScore}/100`);
log(` Provider's reputation has been updated on-chain.`);
const rating = responseTimeMs < 2000 ? 90 : responseTimeMs < 5000 ? 70 : 50;
try {
await agent.submitOpinion({
serviceTokenId: service.tokenId,
rating, // -100 to 100
tag1: 'quality',
endpoint: service.endpoint,
});
log(` Opinion submitted! Rating: ${rating}/100`);
log(` Provider's reputation has been updated on-chain.`);
} catch (err) {
log(chalk.yellow(` Opinion not accepted yet: ${err instanceof Error ? err.message : String(err)}`));
log(chalk.yellow(' Ratings unlock after >= $1 total net payment to the provider.'));
}

log(chalk.bold('\nComplete! The trust flywheel turns.'));
}
Expand Down
26 changes: 21 additions & 5 deletions src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,12 @@ import { Hono } from 'hono';
import { serve } from '@hono/node-server';
import chalk from 'chalk';
import { AzethKit } from '@azeth/sdk';
import { createX402StackFromEnv, paymentMiddlewareFromHTTPServer } from '@azeth/provider';
import {
CAIP2_NETWORKS,
createX402StackFromEnv,
paymentMiddlewareFromHTTPServer,
type RoutesConfig,
} from '@azeth/provider';

const PORT = 3402;

Expand Down Expand Up @@ -59,21 +64,32 @@ async function main() {
// 3. Register on trust registry
log('Publishing service on trust registry...');
const reg = await agent.publishService({
name: 'WeatherOracle',
description: 'Real-time weather data for AI agents',
entityType: 'service',
capabilities: ['weather-data'],
endpoint: `http://localhost:${PORT}`,
});
log(`Registered! TokenId: ${chalk.bold(String(reg.tokenId))}`);

// 4. Set up x402 paywall
const routes = {
// Payments go to the smart account (not the EOA) so consumers can rate this service.
const payTo = agent.smartAccount ?? (await agent.resolveSmartAccount());

const routes: RoutesConfig = {
'GET /api/weather/:city': {
price: '$0.001',
network: 'base-sepolia',
accepts: {
scheme: 'exact',
price: '$0.001',
network: CAIP2_NETWORKS[chain],
payTo,
},
description: 'Weather data for a city',
mimeType: 'application/json',
},
};

const x402 = createX402StackFromEnv(routes);
const x402 = await createX402StackFromEnv(routes, { payTo });

// 5. Build Hono app
const app = new Hono();
Expand Down