pay-lobster 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +401 -0
- package/README.md.bak +401 -0
- package/dist/agent.d.ts +132 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +224 -0
- package/dist/agent.js.map +1 -0
- package/dist/analytics.d.ts +120 -0
- package/dist/analytics.d.ts.map +1 -0
- package/dist/analytics.js +345 -0
- package/dist/analytics.js.map +1 -0
- package/dist/approvals.d.ts +168 -0
- package/dist/approvals.d.ts.map +1 -0
- package/dist/approvals.js +406 -0
- package/dist/approvals.js.map +1 -0
- package/dist/circle-client.d.ts +152 -0
- package/dist/circle-client.d.ts.map +1 -0
- package/dist/circle-client.js +266 -0
- package/dist/circle-client.js.map +1 -0
- package/dist/commission.d.ts +191 -0
- package/dist/commission.d.ts.map +1 -0
- package/dist/commission.js +475 -0
- package/dist/commission.js.map +1 -0
- package/dist/condition-builder.d.ts +98 -0
- package/dist/condition-builder.d.ts.map +1 -0
- package/dist/condition-builder.js +193 -0
- package/dist/condition-builder.js.map +1 -0
- package/dist/contacts.d.ts +179 -0
- package/dist/contacts.d.ts.map +1 -0
- package/dist/contacts.js +445 -0
- package/dist/contacts.js.map +1 -0
- package/dist/easy.d.ts +22 -0
- package/dist/easy.d.ts.map +1 -0
- package/dist/easy.js +40 -0
- package/dist/easy.js.map +1 -0
- package/dist/erc8004/constants.d.ts +152 -0
- package/dist/erc8004/constants.d.ts.map +1 -0
- package/dist/erc8004/constants.js +114 -0
- package/dist/erc8004/constants.js.map +1 -0
- package/dist/erc8004/discovery.d.ts +84 -0
- package/dist/erc8004/discovery.d.ts.map +1 -0
- package/dist/erc8004/discovery.js +217 -0
- package/dist/erc8004/discovery.js.map +1 -0
- package/dist/erc8004/identity.d.ts +91 -0
- package/dist/erc8004/identity.d.ts.map +1 -0
- package/dist/erc8004/identity.js +250 -0
- package/dist/erc8004/identity.js.map +1 -0
- package/dist/erc8004/index.d.ts +147 -0
- package/dist/erc8004/index.d.ts.map +1 -0
- package/dist/erc8004/index.js +225 -0
- package/dist/erc8004/index.js.map +1 -0
- package/dist/erc8004/reputation.d.ts +133 -0
- package/dist/erc8004/reputation.d.ts.map +1 -0
- package/dist/erc8004/reputation.js +277 -0
- package/dist/erc8004/reputation.js.map +1 -0
- package/dist/escrow-templates.d.ts +38 -0
- package/dist/escrow-templates.d.ts.map +1 -0
- package/dist/escrow-templates.js +419 -0
- package/dist/escrow-templates.js.map +1 -0
- package/dist/escrow.d.ts +320 -0
- package/dist/escrow.d.ts.map +1 -0
- package/dist/escrow.js +854 -0
- package/dist/escrow.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/index.js.map +1 -0
- package/dist/invoices.d.ts +212 -0
- package/dist/invoices.d.ts.map +1 -0
- package/dist/invoices.js +393 -0
- package/dist/invoices.js.map +1 -0
- package/dist/notifications.d.ts +141 -0
- package/dist/notifications.d.ts.map +1 -0
- package/dist/notifications.js +350 -0
- package/dist/notifications.js.map +1 -0
- package/dist/tips.d.ts +171 -0
- package/dist/tips.d.ts.map +1 -0
- package/dist/tips.js +390 -0
- package/dist/tips.js.map +1 -0
- package/dist/types.d.ts +100 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/x402-client.d.ts +127 -0
- package/dist/x402-client.d.ts.map +1 -0
- package/dist/x402-client.js +350 -0
- package/dist/x402-client.js.map +1 -0
- package/dist/x402-server.d.ts +133 -0
- package/dist/x402-server.d.ts.map +1 -0
- package/dist/x402-server.js +330 -0
- package/dist/x402-server.js.map +1 -0
- package/lib/agent.ts +273 -0
- package/lib/analytics.ts +474 -0
- package/lib/analytics.ts.bak +474 -0
- package/lib/approvals.ts +585 -0
- package/lib/approvals.ts.bak +585 -0
- package/lib/circle-client.ts +376 -0
- package/lib/circle-client.ts.bak +376 -0
- package/lib/commission.ts +680 -0
- package/lib/commission.ts.bak +680 -0
- package/lib/condition-builder.ts +223 -0
- package/lib/condition-builder.ts.bak +223 -0
- package/lib/contacts.ts +615 -0
- package/lib/contacts.ts.bak +615 -0
- package/lib/easy.ts +46 -0
- package/lib/easy.ts.bak +352 -0
- package/lib/erc8004/constants.ts +175 -0
- package/lib/erc8004/discovery.ts +299 -0
- package/lib/erc8004/identity.ts +327 -0
- package/lib/erc8004/index.ts +285 -0
- package/lib/erc8004/reputation.ts +368 -0
- package/lib/escrow-templates.ts +462 -0
- package/lib/escrow.ts +1216 -0
- package/lib/index.ts +13 -0
- package/lib/invoices.ts +588 -0
- package/lib/notifications.ts +484 -0
- package/lib/tips.ts +570 -0
- package/lib/types.ts +108 -0
- package/lib/x402-client.ts +471 -0
- package/lib/x402-server.ts +462 -0
- package/package.json +58 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ERC-8004 Trustless Agents - Main Export
|
|
3
|
+
*
|
|
4
|
+
* Complete integration for agent identity, reputation, and discovery.
|
|
5
|
+
* Designed for seamless integration with USDC payments and x402 protocol.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export * from './constants';
|
|
9
|
+
export * from './identity';
|
|
10
|
+
export * from './reputation';
|
|
11
|
+
export * from './discovery';
|
|
12
|
+
|
|
13
|
+
import { ethers } from 'ethers';
|
|
14
|
+
import {
|
|
15
|
+
SupportedChain,
|
|
16
|
+
AgentRegistration,
|
|
17
|
+
CHAIN_CONFIG,
|
|
18
|
+
} from './constants';
|
|
19
|
+
import { IdentityClient, createLobsterAgentRegistration, RegisteredAgent } from './identity';
|
|
20
|
+
import { ReputationClient, FeedbackTemplates, ReputationSummary } from './reputation';
|
|
21
|
+
import { DiscoveryService, DiscoveredAgent } from './discovery';
|
|
22
|
+
|
|
23
|
+
export interface ERC8004ClientConfig {
|
|
24
|
+
chain: SupportedChain;
|
|
25
|
+
privateKey?: string;
|
|
26
|
+
paymentAddress?: string;
|
|
27
|
+
x402Endpoint?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Unified ERC-8004 Client
|
|
32
|
+
*
|
|
33
|
+
* Provides high-level API for agent registration, discovery, and trust management.
|
|
34
|
+
*/
|
|
35
|
+
export class ERC8004Client {
|
|
36
|
+
readonly chain: SupportedChain;
|
|
37
|
+
readonly identity: IdentityClient;
|
|
38
|
+
readonly reputation: ReputationClient;
|
|
39
|
+
readonly discovery: DiscoveryService;
|
|
40
|
+
|
|
41
|
+
private config: ERC8004ClientConfig;
|
|
42
|
+
private myAgentId?: number;
|
|
43
|
+
|
|
44
|
+
constructor(config: ERC8004ClientConfig) {
|
|
45
|
+
this.chain = config.chain;
|
|
46
|
+
this.config = config;
|
|
47
|
+
|
|
48
|
+
this.identity = new IdentityClient({
|
|
49
|
+
chain: config.chain,
|
|
50
|
+
privateKey: config.privateKey,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
this.reputation = new ReputationClient({
|
|
54
|
+
chain: config.chain,
|
|
55
|
+
privateKey: config.privateKey,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
this.discovery = new DiscoveryService({
|
|
59
|
+
chain: config.chain,
|
|
60
|
+
privateKey: config.privateKey,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Register this agent with the ERC-8004 Identity Registry
|
|
66
|
+
*/
|
|
67
|
+
async registerAgent(options: {
|
|
68
|
+
name: string;
|
|
69
|
+
description: string;
|
|
70
|
+
image?: string;
|
|
71
|
+
capabilities: string[];
|
|
72
|
+
mcpEndpoint?: string;
|
|
73
|
+
a2aEndpoint?: string;
|
|
74
|
+
}): Promise<number> {
|
|
75
|
+
const registration = createLobsterAgentRegistration({
|
|
76
|
+
name: options.name,
|
|
77
|
+
description: options.description,
|
|
78
|
+
image: options.image,
|
|
79
|
+
chain: this.chain,
|
|
80
|
+
capabilities: options.capabilities,
|
|
81
|
+
paymentAddress: this.config.paymentAddress,
|
|
82
|
+
x402Endpoint: this.config.x402Endpoint,
|
|
83
|
+
escrowSupport: true,
|
|
84
|
+
mcpEndpoint: options.mcpEndpoint,
|
|
85
|
+
a2aEndpoint: options.a2aEndpoint,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
this.myAgentId = await this.identity.register(registration);
|
|
89
|
+
return this.myAgentId;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Get my agent ID (if registered)
|
|
94
|
+
*/
|
|
95
|
+
getMyAgentId(): number | undefined {
|
|
96
|
+
return this.myAgentId;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Set agent ID (if already registered)
|
|
101
|
+
*/
|
|
102
|
+
setMyAgentId(agentId: number): void {
|
|
103
|
+
this.myAgentId = agentId;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Verify an agent before transacting
|
|
108
|
+
*/
|
|
109
|
+
async verifyAgent(agentId: number) {
|
|
110
|
+
return this.discovery.verifyAgent(agentId);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Check if payment is safe for an agent
|
|
115
|
+
*/
|
|
116
|
+
async isPaymentSafe(agentId: number, amountUsdc: number) {
|
|
117
|
+
return this.discovery.checkPaymentSafety(agentId, amountUsdc);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Post feedback after a transaction
|
|
122
|
+
*/
|
|
123
|
+
async postFeedback(options: {
|
|
124
|
+
agentId: number;
|
|
125
|
+
score: number;
|
|
126
|
+
context: string;
|
|
127
|
+
txHash?: string;
|
|
128
|
+
}) {
|
|
129
|
+
return this.reputation.postFeedback({
|
|
130
|
+
agentId: options.agentId,
|
|
131
|
+
score: options.score,
|
|
132
|
+
context: options.context,
|
|
133
|
+
taskHash: options.txHash,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Post positive feedback after successful payment
|
|
139
|
+
*/
|
|
140
|
+
async postPaymentSuccess(agentId: number, txHash: string, amount: string) {
|
|
141
|
+
const { score, context } = FeedbackTemplates.paymentSuccessful(txHash, amount);
|
|
142
|
+
return this.postFeedback({ agentId, score, context, txHash });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Post negative feedback after failed payment
|
|
147
|
+
*/
|
|
148
|
+
async postPaymentFailure(agentId: number, reason: string) {
|
|
149
|
+
const { score, context } = FeedbackTemplates.paymentFailed(reason);
|
|
150
|
+
return this.postFeedback({ agentId, score, context });
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Post feedback after escrow completion
|
|
155
|
+
*/
|
|
156
|
+
async postEscrowFeedback(agentId: number, escrowId: string, outcome: 'released' | 'refunded' | 'disputed', reason?: string) {
|
|
157
|
+
if (outcome === 'disputed') {
|
|
158
|
+
const { score, context } = FeedbackTemplates.escrowDisputed(escrowId, reason || 'Unknown');
|
|
159
|
+
return this.postFeedback({ agentId, score, context });
|
|
160
|
+
}
|
|
161
|
+
const { score, context } = FeedbackTemplates.escrowCompleted(escrowId, outcome);
|
|
162
|
+
return this.postFeedback({ agentId, score, context });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Find agents for payment
|
|
167
|
+
*/
|
|
168
|
+
async findPaymentAgents(minTrustScore?: number) {
|
|
169
|
+
return this.discovery.findPaymentAgents({ minTrustScore });
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Find agents with escrow capability
|
|
174
|
+
*/
|
|
175
|
+
async findEscrowAgents(minTrustLevel?: ReputationSummary['trustLevel']) {
|
|
176
|
+
return this.discovery.findEscrowAgents({ minTrustLevel });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get my reputation summary
|
|
181
|
+
*/
|
|
182
|
+
async getMyReputation(): Promise<ReputationSummary | null> {
|
|
183
|
+
if (!this.myAgentId) return null;
|
|
184
|
+
return this.reputation.getReputationSummary(this.myAgentId);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Get contract addresses for this chain
|
|
189
|
+
*/
|
|
190
|
+
getContractAddresses() {
|
|
191
|
+
return CHAIN_CONFIG[this.chain].contracts;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Get the agent registry identifier
|
|
196
|
+
*/
|
|
197
|
+
getAgentRegistry(): string {
|
|
198
|
+
return this.identity.getAgentRegistry();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Integration helper: Wrap x402 payment with trust verification
|
|
204
|
+
*/
|
|
205
|
+
export async function createTrustedX402Payment(options: {
|
|
206
|
+
erc8004: ERC8004Client;
|
|
207
|
+
targetAgentId: number;
|
|
208
|
+
amountUsdc: number;
|
|
209
|
+
endpoint: string;
|
|
210
|
+
paymentFn: () => Promise<{ success: boolean; txHash?: string; error?: string }>;
|
|
211
|
+
}): Promise<{
|
|
212
|
+
success: boolean;
|
|
213
|
+
txHash?: string;
|
|
214
|
+
trustScore: number;
|
|
215
|
+
feedbackPosted: boolean;
|
|
216
|
+
error?: string;
|
|
217
|
+
}> {
|
|
218
|
+
// Verify agent first
|
|
219
|
+
const verification = await options.erc8004.verifyAgent(options.targetAgentId);
|
|
220
|
+
|
|
221
|
+
if (!verification.verified) {
|
|
222
|
+
return {
|
|
223
|
+
success: false,
|
|
224
|
+
trustScore: verification.trustScore,
|
|
225
|
+
feedbackPosted: false,
|
|
226
|
+
error: `Agent verification failed: ${verification.reasons.join(', ')}`,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Check payment safety
|
|
231
|
+
const safety = await options.erc8004.isPaymentSafe(options.targetAgentId, options.amountUsdc);
|
|
232
|
+
|
|
233
|
+
if (!safety.safe) {
|
|
234
|
+
console.warn(`Payment exceeds recommended limit: ${safety.reason}`);
|
|
235
|
+
// Continue anyway, but warn
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Execute payment
|
|
239
|
+
const result = await options.paymentFn();
|
|
240
|
+
|
|
241
|
+
// Post feedback
|
|
242
|
+
let feedbackPosted = false;
|
|
243
|
+
try {
|
|
244
|
+
if (result.success && result.txHash) {
|
|
245
|
+
await options.erc8004.postPaymentSuccess(
|
|
246
|
+
options.targetAgentId,
|
|
247
|
+
result.txHash,
|
|
248
|
+
options.amountUsdc.toString()
|
|
249
|
+
);
|
|
250
|
+
feedbackPosted = true;
|
|
251
|
+
} else if (!result.success) {
|
|
252
|
+
await options.erc8004.postPaymentFailure(
|
|
253
|
+
options.targetAgentId,
|
|
254
|
+
result.error || 'Unknown error'
|
|
255
|
+
);
|
|
256
|
+
feedbackPosted = true;
|
|
257
|
+
}
|
|
258
|
+
} catch (e) {
|
|
259
|
+
console.warn('Failed to post feedback:', e);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
success: result.success,
|
|
264
|
+
txHash: result.txHash,
|
|
265
|
+
trustScore: verification.trustScore,
|
|
266
|
+
feedbackPosted,
|
|
267
|
+
error: result.error,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Quick setup helper
|
|
273
|
+
*/
|
|
274
|
+
export function createERC8004Client(
|
|
275
|
+
chain: SupportedChain,
|
|
276
|
+
privateKey?: string,
|
|
277
|
+
options?: { paymentAddress?: string; x402Endpoint?: string }
|
|
278
|
+
): ERC8004Client {
|
|
279
|
+
return new ERC8004Client({
|
|
280
|
+
chain,
|
|
281
|
+
privateKey,
|
|
282
|
+
paymentAddress: options?.paymentAddress,
|
|
283
|
+
x402Endpoint: options?.x402Endpoint,
|
|
284
|
+
});
|
|
285
|
+
}
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ERC-8004 Reputation Registry Client
|
|
3
|
+
*
|
|
4
|
+
* Post feedback, query reputation, and manage trust levels.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { ethers } from 'ethers';
|
|
8
|
+
import {
|
|
9
|
+
CHAIN_CONFIG,
|
|
10
|
+
REPUTATION_REGISTRY_ABI,
|
|
11
|
+
SupportedChain,
|
|
12
|
+
Feedback,
|
|
13
|
+
ReputationSummary,
|
|
14
|
+
TRUST_LEVELS,
|
|
15
|
+
} from './constants';
|
|
16
|
+
|
|
17
|
+
export interface ReputationClientConfig {
|
|
18
|
+
chain: SupportedChain;
|
|
19
|
+
privateKey?: string;
|
|
20
|
+
provider?: ethers.Provider;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface FeedbackInput {
|
|
24
|
+
agentId: number;
|
|
25
|
+
score: number; // -100 to 100
|
|
26
|
+
context: string;
|
|
27
|
+
taskHash?: string; // Hash of the task/transaction being rated
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class ReputationClient {
|
|
31
|
+
private chain: SupportedChain;
|
|
32
|
+
private provider: ethers.Provider;
|
|
33
|
+
private signer?: ethers.Wallet;
|
|
34
|
+
private contract: ethers.Contract;
|
|
35
|
+
private readOnlyContract: ethers.Contract;
|
|
36
|
+
|
|
37
|
+
constructor(config: ReputationClientConfig) {
|
|
38
|
+
this.chain = config.chain;
|
|
39
|
+
const chainConfig = CHAIN_CONFIG[config.chain];
|
|
40
|
+
|
|
41
|
+
this.provider = config.provider || new ethers.JsonRpcProvider(chainConfig.rpcUrl);
|
|
42
|
+
|
|
43
|
+
if (config.privateKey) {
|
|
44
|
+
this.signer = new ethers.Wallet(config.privateKey, this.provider);
|
|
45
|
+
this.contract = new ethers.Contract(
|
|
46
|
+
chainConfig.contracts.reputationRegistry,
|
|
47
|
+
REPUTATION_REGISTRY_ABI,
|
|
48
|
+
this.signer
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.readOnlyContract = new ethers.Contract(
|
|
53
|
+
chainConfig.contracts.reputationRegistry,
|
|
54
|
+
REPUTATION_REGISTRY_ABI,
|
|
55
|
+
this.provider
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Post feedback for an agent
|
|
61
|
+
*
|
|
62
|
+
* @param feedback Feedback to post
|
|
63
|
+
* @returns The feedbackId
|
|
64
|
+
*/
|
|
65
|
+
async postFeedback(feedback: FeedbackInput): Promise<number> {
|
|
66
|
+
if (!this.contract || !this.signer) {
|
|
67
|
+
throw new Error('Signer required to post feedback');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Validate score range
|
|
71
|
+
if (feedback.score < -100 || feedback.score > 100) {
|
|
72
|
+
throw new Error('Score must be between -100 and 100');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Create task hash if not provided
|
|
76
|
+
const taskHash = feedback.taskHash
|
|
77
|
+
? ethers.id(feedback.taskHash)
|
|
78
|
+
: ethers.id(`feedback-${Date.now()}-${Math.random()}`);
|
|
79
|
+
|
|
80
|
+
console.log(`Posting feedback for agent ${feedback.agentId}: score=${feedback.score}`);
|
|
81
|
+
|
|
82
|
+
const tx = await this.contract.postFeedback(
|
|
83
|
+
feedback.agentId,
|
|
84
|
+
feedback.score,
|
|
85
|
+
feedback.context,
|
|
86
|
+
taskHash
|
|
87
|
+
);
|
|
88
|
+
const receipt = await tx.wait();
|
|
89
|
+
|
|
90
|
+
// Extract feedbackId from event
|
|
91
|
+
const event = receipt.logs.find((log: any) => {
|
|
92
|
+
try {
|
|
93
|
+
const parsed = this.contract.interface.parseLog(log);
|
|
94
|
+
return parsed?.name === 'FeedbackPosted';
|
|
95
|
+
} catch {
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (!event) {
|
|
101
|
+
throw new Error('FeedbackPosted event not found');
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const parsed = this.contract.interface.parseLog(event);
|
|
105
|
+
const feedbackId = Number(parsed?.args?.feedbackId);
|
|
106
|
+
|
|
107
|
+
console.log(`Feedback posted with ID: ${feedbackId}`);
|
|
108
|
+
|
|
109
|
+
return feedbackId;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Post feedback after a successful x402 payment
|
|
114
|
+
*
|
|
115
|
+
* Links the feedback to the payment transaction for verification
|
|
116
|
+
*/
|
|
117
|
+
async postPaymentFeedback(options: {
|
|
118
|
+
agentId: number;
|
|
119
|
+
score: number;
|
|
120
|
+
context: string;
|
|
121
|
+
paymentTxHash: string;
|
|
122
|
+
paymentAmount: string;
|
|
123
|
+
serviceUsed: string;
|
|
124
|
+
}): Promise<number> {
|
|
125
|
+
const enrichedContext = JSON.stringify({
|
|
126
|
+
type: 'x402-payment-feedback',
|
|
127
|
+
context: options.context,
|
|
128
|
+
payment: {
|
|
129
|
+
txHash: options.paymentTxHash,
|
|
130
|
+
amount: options.paymentAmount,
|
|
131
|
+
service: options.serviceUsed,
|
|
132
|
+
},
|
|
133
|
+
timestamp: new Date().toISOString(),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return this.postFeedback({
|
|
137
|
+
agentId: options.agentId,
|
|
138
|
+
score: options.score,
|
|
139
|
+
context: enrichedContext,
|
|
140
|
+
taskHash: options.paymentTxHash,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get a specific feedback entry
|
|
146
|
+
*/
|
|
147
|
+
async getFeedback(feedbackId: number): Promise<Feedback | null> {
|
|
148
|
+
try {
|
|
149
|
+
const result = await this.readOnlyContract.getFeedback(feedbackId);
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
feedbackId,
|
|
153
|
+
agentId: Number(result.agentId),
|
|
154
|
+
author: result.author,
|
|
155
|
+
score: Number(result.score),
|
|
156
|
+
context: result.context,
|
|
157
|
+
taskHash: result.taskHash,
|
|
158
|
+
timestamp: Number(result.timestamp),
|
|
159
|
+
};
|
|
160
|
+
} catch {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Get feedback count for an agent
|
|
167
|
+
*/
|
|
168
|
+
async getFeedbackCount(agentId: number): Promise<number> {
|
|
169
|
+
const count = await this.readOnlyContract.getFeedbackCount(agentId);
|
|
170
|
+
return Number(count);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Get recent feedback for an agent
|
|
175
|
+
*/
|
|
176
|
+
async getRecentFeedback(agentId: number, limit: number = 10): Promise<Feedback[]> {
|
|
177
|
+
const feedbackIds: bigint[] = await this.readOnlyContract.getFeedbackByAgent(
|
|
178
|
+
agentId,
|
|
179
|
+
0,
|
|
180
|
+
limit
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const feedbacks = await Promise.all(
|
|
184
|
+
feedbackIds.map(id => this.getFeedback(Number(id)))
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
return feedbacks.filter((f): f is Feedback => f !== null);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Get average score for an agent
|
|
192
|
+
*/
|
|
193
|
+
async getAverageScore(agentId: number): Promise<{ average: number; count: number }> {
|
|
194
|
+
const [average, count] = await this.readOnlyContract.getAverageScore(agentId);
|
|
195
|
+
return {
|
|
196
|
+
average: Number(average),
|
|
197
|
+
count: Number(count),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get comprehensive reputation summary for an agent
|
|
203
|
+
*/
|
|
204
|
+
async getReputationSummary(agentId: number): Promise<ReputationSummary> {
|
|
205
|
+
const [{ average, count }, recentFeedback] = await Promise.all([
|
|
206
|
+
this.getAverageScore(agentId),
|
|
207
|
+
this.getRecentFeedback(agentId, 5),
|
|
208
|
+
]);
|
|
209
|
+
|
|
210
|
+
// Determine trust level based on score and feedback count
|
|
211
|
+
let trustLevel: ReputationSummary['trustLevel'] = 'untrusted';
|
|
212
|
+
|
|
213
|
+
if (count >= TRUST_LEVELS.verified.minFeedback && average >= TRUST_LEVELS.verified.minScore) {
|
|
214
|
+
trustLevel = 'verified';
|
|
215
|
+
} else if (count >= TRUST_LEVELS.trusted.minFeedback && average >= TRUST_LEVELS.trusted.minScore) {
|
|
216
|
+
trustLevel = 'trusted';
|
|
217
|
+
} else if (count >= TRUST_LEVELS.established.minFeedback && average >= TRUST_LEVELS.established.minScore) {
|
|
218
|
+
trustLevel = 'established';
|
|
219
|
+
} else if (count >= TRUST_LEVELS.emerging.minFeedback && average >= TRUST_LEVELS.emerging.minScore) {
|
|
220
|
+
trustLevel = 'emerging';
|
|
221
|
+
} else if (count >= TRUST_LEVELS.new.minFeedback && average >= TRUST_LEVELS.new.minScore) {
|
|
222
|
+
trustLevel = 'new';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return {
|
|
226
|
+
agentId,
|
|
227
|
+
averageScore: average,
|
|
228
|
+
totalFeedback: count,
|
|
229
|
+
recentFeedback,
|
|
230
|
+
trustLevel,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Authorize an address to post feedback for your agent
|
|
236
|
+
*/
|
|
237
|
+
async authorizeFeedbackAuthor(agentId: number, author: string): Promise<void> {
|
|
238
|
+
if (!this.contract) {
|
|
239
|
+
throw new Error('Signer required');
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const tx = await this.contract.authorizeFeedback(agentId, author);
|
|
243
|
+
await tx.wait();
|
|
244
|
+
|
|
245
|
+
console.log(`Authorized ${author} to post feedback for agent ${agentId}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Revoke feedback authorization
|
|
250
|
+
*/
|
|
251
|
+
async revokeFeedbackAuthorization(agentId: number, author: string): Promise<void> {
|
|
252
|
+
if (!this.contract) {
|
|
253
|
+
throw new Error('Signer required');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const tx = await this.contract.revokeFeedbackAuthorization(agentId, author);
|
|
257
|
+
await tx.wait();
|
|
258
|
+
|
|
259
|
+
console.log(`Revoked feedback authorization for ${author} on agent ${agentId}`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Check if an address is authorized to post feedback
|
|
264
|
+
*/
|
|
265
|
+
async isAuthorizedFeedbackAuthor(agentId: number, author: string): Promise<boolean> {
|
|
266
|
+
return this.readOnlyContract.isAuthorizedFeedbackAuthor(agentId, author);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Check if an agent meets minimum trust requirements
|
|
271
|
+
*/
|
|
272
|
+
async meetsMinimumTrust(agentId: number, minLevel: ReputationSummary['trustLevel']): Promise<boolean> {
|
|
273
|
+
const summary = await this.getReputationSummary(agentId);
|
|
274
|
+
|
|
275
|
+
const levelOrder: ReputationSummary['trustLevel'][] = [
|
|
276
|
+
'untrusted', 'new', 'emerging', 'established', 'trusted', 'verified'
|
|
277
|
+
];
|
|
278
|
+
|
|
279
|
+
return levelOrder.indexOf(summary.trustLevel) >= levelOrder.indexOf(minLevel);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Calculate trust score for payment decisions
|
|
284
|
+
*
|
|
285
|
+
* Returns a 0-100 score combining reputation and feedback volume
|
|
286
|
+
*/
|
|
287
|
+
async calculateTrustScore(agentId: number): Promise<number> {
|
|
288
|
+
const summary = await this.getReputationSummary(agentId);
|
|
289
|
+
|
|
290
|
+
// Weight: 70% average score, 30% volume bonus
|
|
291
|
+
const scoreComponent = Math.max(0, (summary.averageScore + 100) / 2); // Normalize -100..100 to 0..100
|
|
292
|
+
const volumeBonus = Math.min(30, summary.totalFeedback * 0.3); // Max 30 points from volume
|
|
293
|
+
|
|
294
|
+
return Math.round(scoreComponent * 0.7 + volumeBonus);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Standard feedback templates for common scenarios
|
|
300
|
+
*/
|
|
301
|
+
export const FeedbackTemplates = {
|
|
302
|
+
// Positive feedback
|
|
303
|
+
excellentService: (details?: string) => ({
|
|
304
|
+
score: 95,
|
|
305
|
+
context: `Excellent service. ${details || 'Highly recommended.'}`,
|
|
306
|
+
}),
|
|
307
|
+
|
|
308
|
+
goodService: (details?: string) => ({
|
|
309
|
+
score: 75,
|
|
310
|
+
context: `Good service. ${details || 'Would use again.'}`,
|
|
311
|
+
}),
|
|
312
|
+
|
|
313
|
+
satisfactory: (details?: string) => ({
|
|
314
|
+
score: 50,
|
|
315
|
+
context: `Satisfactory service. ${details || 'Met expectations.'}`,
|
|
316
|
+
}),
|
|
317
|
+
|
|
318
|
+
// Negative feedback
|
|
319
|
+
poorService: (details?: string) => ({
|
|
320
|
+
score: -50,
|
|
321
|
+
context: `Poor service. ${details || 'Did not meet expectations.'}`,
|
|
322
|
+
}),
|
|
323
|
+
|
|
324
|
+
failed: (details?: string) => ({
|
|
325
|
+
score: -90,
|
|
326
|
+
context: `Service failed. ${details || 'Would not recommend.'}`,
|
|
327
|
+
}),
|
|
328
|
+
|
|
329
|
+
// Payment-specific
|
|
330
|
+
paymentSuccessful: (txHash: string, amount: string) => ({
|
|
331
|
+
score: 80,
|
|
332
|
+
context: JSON.stringify({
|
|
333
|
+
type: 'payment-feedback',
|
|
334
|
+
result: 'success',
|
|
335
|
+
txHash,
|
|
336
|
+
amount,
|
|
337
|
+
}),
|
|
338
|
+
}),
|
|
339
|
+
|
|
340
|
+
paymentFailed: (reason: string) => ({
|
|
341
|
+
score: -70,
|
|
342
|
+
context: JSON.stringify({
|
|
343
|
+
type: 'payment-feedback',
|
|
344
|
+
result: 'failed',
|
|
345
|
+
reason,
|
|
346
|
+
}),
|
|
347
|
+
}),
|
|
348
|
+
|
|
349
|
+
// Escrow-specific
|
|
350
|
+
escrowCompleted: (escrowId: string, outcome: 'released' | 'refunded') => ({
|
|
351
|
+
score: outcome === 'released' ? 85 : 40,
|
|
352
|
+
context: JSON.stringify({
|
|
353
|
+
type: 'escrow-feedback',
|
|
354
|
+
escrowId,
|
|
355
|
+
outcome,
|
|
356
|
+
}),
|
|
357
|
+
}),
|
|
358
|
+
|
|
359
|
+
escrowDisputed: (escrowId: string, reason: string) => ({
|
|
360
|
+
score: -30,
|
|
361
|
+
context: JSON.stringify({
|
|
362
|
+
type: 'escrow-feedback',
|
|
363
|
+
escrowId,
|
|
364
|
+
outcome: 'disputed',
|
|
365
|
+
reason,
|
|
366
|
+
}),
|
|
367
|
+
}),
|
|
368
|
+
};
|