@relaycore/sdk 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/agent-sdk.ts ADDED
@@ -0,0 +1,392 @@
1
+ /**
2
+ * Relay Core Agent SDK
3
+ *
4
+ * Enables AI agents to interact with Relay Core:
5
+ * - Register services (on-chain + off-chain)
6
+ * - Discover other agents
7
+ * - Pay for services (x402)
8
+ * - Submit feedback
9
+ */
10
+
11
+ import { createSupabaseClient } from './lib/supabase';
12
+ import { FacilitatorClient } from './lib/facilitator';
13
+ import type { PaymentRequirements, CronosNetwork } from '@crypto.com/facilitator-client';
14
+ import { uploadAgentMetadataToIPFS, buildAgentMetadata } from './lib/ipfs';
15
+ import { registerAgent as registerAgentOnChain } from './lib/erc8004';
16
+ import { ethers } from 'ethers';
17
+ import { SupabaseClient } from '@supabase/supabase-js';
18
+
19
+ // SDK Configuration
20
+ export interface SDKConfig {
21
+ apiKey?: string; // API key for authenticated access
22
+ walletAddress: string; // Agent's wallet address
23
+ rpcUrl?: string; // Cronos RPC URL
24
+ baseUrl?: string; // Relay Core API URL (default: localhost:4000)
25
+ supabaseUrl?: string;
26
+ supabaseKey?: string;
27
+ contracts?: {
28
+ identityRegistry?: string;
29
+ };
30
+ }
31
+
32
+ export interface AgentService {
33
+ serviceId?: string;
34
+ name: string;
35
+ description: string;
36
+ serviceType: string;
37
+ pricePerRequest: string; // in USDC
38
+ endpoint: string;
39
+ agentAddress?: string;
40
+ }
41
+
42
+ export interface AgentProfile {
43
+ address: string;
44
+ agentId?: number; // On-chain agent ID
45
+ name?: string;
46
+ services: AgentService[];
47
+ reputationScore: number;
48
+ successRate: number;
49
+ }
50
+
51
+ export interface RegisterAgentResult {
52
+ agentId: number;
53
+ txHash: string;
54
+ ipfsUri: string;
55
+ }
56
+
57
+ /**
58
+ * Relay Core Agent SDK
59
+ */
60
+ export class AgentSDK {
61
+ private config: SDKConfig;
62
+ private headers: Record<string, string>;
63
+ private supabase: SupabaseClient | null = null;
64
+ private facilitator: FacilitatorClient;
65
+
66
+ constructor(config: SDKConfig) {
67
+ this.config = {
68
+ rpcUrl: 'https://evm-t3.cronos.org',
69
+ baseUrl: 'http://localhost:4000',
70
+ ...config,
71
+ };
72
+
73
+ // Set up headers with API key if provided
74
+ this.headers = {
75
+ 'Content-Type': 'application/json',
76
+ };
77
+
78
+ if (config.apiKey) {
79
+ this.headers['x-api-key'] = config.apiKey;
80
+ }
81
+
82
+ // Initialize Supabase if config provided
83
+ if (config.supabaseUrl && config.supabaseKey) {
84
+ this.supabase = createSupabaseClient(config.supabaseUrl, config.supabaseKey);
85
+ }
86
+
87
+ // Initialize Facilitator
88
+ // Determining network from RPC URL is heuristics, might be better to explicitly ask for network
89
+ // For now defaulting to testnet
90
+ this.facilitator = new FacilitatorClient('cronos-testnet' as CronosNetwork);
91
+ }
92
+
93
+ /**
94
+ * Register an agent on-chain with IPFS metadata
95
+ *
96
+ * Requires a signer (wallet) to submit the transaction.
97
+ */
98
+ async registerAgentOnChain(
99
+ service: Omit<AgentService, 'agentAddress' | 'serviceId'>,
100
+ signer: ethers.Signer
101
+ ): Promise<RegisterAgentResult> {
102
+ // Build and upload metadata to IPFS
103
+ const metadata = buildAgentMetadata({
104
+ name: service.name,
105
+ description: service.description,
106
+ serviceType: service.serviceType,
107
+ endpoint: service.endpoint,
108
+ pricePerRequest: service.pricePerRequest,
109
+ });
110
+
111
+ // Use base URL for IPFS upload proxy
112
+ const ipfsUri = await uploadAgentMetadataToIPFS(metadata, this.config.baseUrl || 'http://localhost:4000');
113
+
114
+ // Register on-chain
115
+ const { agentId, txHash } = await registerAgentOnChain(
116
+ ipfsUri,
117
+ this.config.walletAddress,
118
+ signer,
119
+ this.config.contracts?.identityRegistry || '0x4b697D8ABC0e3dA0086011222755d9029DBB9C43'
120
+ );
121
+
122
+ // Also save to Supabase for discoverability
123
+ await this.registerServiceOffChain({
124
+ ...service,
125
+ serviceId: `agent-${agentId}`,
126
+ });
127
+
128
+ return { agentId, txHash, ipfsUri };
129
+ }
130
+
131
+ /**
132
+ * Register a service (off-chain only, for discovery)
133
+ */
134
+ async registerServiceOffChain(service: Omit<AgentService, 'agentAddress'>): Promise<void> {
135
+ // Use API if available
136
+ if (this.config.apiKey) {
137
+ const response = await fetch(`${this.config.baseUrl}/api/services`, {
138
+ method: 'POST',
139
+ headers: this.headers,
140
+ body: JSON.stringify({
141
+ ...service,
142
+ agentAddress: this.config.walletAddress,
143
+ }),
144
+ });
145
+
146
+ if (!response.ok) {
147
+ const error = await response.json();
148
+ throw new Error(`Failed to register service: ${error.error}`);
149
+ }
150
+
151
+ console.log(`Service registered: ${service.name}`);
152
+ return;
153
+ }
154
+
155
+ // Fallback to direct Supabase
156
+ if (!this.supabase) {
157
+ console.warn('Supabase not configured. Off-chain service registration skiped.');
158
+ return;
159
+ }
160
+
161
+ const { error } = await this.supabase.from('agent_activity').insert({
162
+ agent_address: this.config.walletAddress,
163
+ activity_type: 'service_registered',
164
+ metadata: {
165
+ serviceId: service.serviceId,
166
+ name: service.name,
167
+ description: service.description,
168
+ serviceType: service.serviceType,
169
+ pricePerRequest: service.pricePerRequest,
170
+ endpoint: service.endpoint,
171
+ },
172
+ timestamp: new Date().toISOString(),
173
+ });
174
+
175
+ if (error) throw new Error(`Failed to register service: ${error.message}`);
176
+
177
+ console.log(`Service registered: ${service.name}`);
178
+ }
179
+
180
+ /**
181
+ * Discover agents by service type
182
+ */
183
+ async discoverAgents(serviceType: string, minReputation: number = 0): Promise<AgentProfile[]> {
184
+ // Use API for discovery
185
+ if (this.config.apiKey) {
186
+ const response = await fetch(
187
+ `${this.config.baseUrl}/api/agents?serviceType=${serviceType}&minReputation=${minReputation}`,
188
+ { headers: this.headers }
189
+ );
190
+
191
+ if (!response.ok) {
192
+ throw new Error('Failed to discover agents');
193
+ }
194
+
195
+ return response.json();
196
+ }
197
+
198
+ // Fallback to direct Supabase query
199
+ if (!this.supabase) {
200
+ throw new Error('Supabase not configured. Cannot discover agents without API Key or Supabase credentials.');
201
+ }
202
+
203
+ const { data: activities, error: actError } = await this.supabase
204
+ .from('agent_activity')
205
+ .select('agent_address, metadata')
206
+ .eq('activity_type', 'service_registered')
207
+ .contains('metadata', { serviceType });
208
+
209
+ if (actError) throw new Error(`Failed to discover agents: ${actError.message}`);
210
+
211
+ if (!activities || activities.length === 0) return [];
212
+
213
+ const agentAddresses = [...new Set(activities.map((a: any) => a.agent_address))];
214
+
215
+ const { data: reputations, error: repError } = await this.supabase
216
+ .from('agent_reputation')
217
+ .select('*')
218
+ .in('agent_address', agentAddresses)
219
+ .gte('reputation_score', minReputation);
220
+
221
+ if (repError) throw new Error(`Failed to get reputations: ${repError.message}`);
222
+
223
+ const profiles: AgentProfile[] = [];
224
+
225
+ for (const address of agentAddresses) {
226
+ const agentActivities = activities.filter((a: any) => a.agent_address === address);
227
+ const reputation = reputations?.find((r: any) => r.agent_address === address);
228
+
229
+ const services: AgentService[] = agentActivities.map((a: any) => ({
230
+ serviceId: a.metadata.serviceId,
231
+ name: a.metadata.name,
232
+ description: a.metadata.description,
233
+ serviceType: a.metadata.serviceType,
234
+ pricePerRequest: a.metadata.pricePerRequest,
235
+ endpoint: a.metadata.endpoint,
236
+ agentAddress: address as string,
237
+ }));
238
+
239
+ const total = (reputation?.successful_transactions || 0) + (reputation?.failed_transactions || 0);
240
+ const successRate = total > 0 ? (reputation?.successful_transactions || 0) / total : 0;
241
+
242
+ profiles.push({
243
+ address: address as string,
244
+ services,
245
+ reputationScore: reputation?.reputation_score || 0,
246
+ successRate,
247
+ });
248
+ }
249
+
250
+ return profiles.sort((a, b) => b.reputationScore - a.reputationScore);
251
+ }
252
+
253
+ /**
254
+ * Get agent reputation
255
+ */
256
+ async getReputation(address: string): Promise<number> {
257
+ if (!this.supabase) {
258
+ return 0; // Or throw error
259
+ }
260
+
261
+ const { data, error } = await this.supabase
262
+ .from('agent_reputation')
263
+ .select('reputation_score')
264
+ .eq('agent_address', address)
265
+ .single();
266
+
267
+ if (error) {
268
+ if (error.code === 'PGRST116') return 0;
269
+ throw new Error(`Failed to get reputation: ${error.message}`);
270
+ }
271
+
272
+ return data.reputation_score;
273
+ }
274
+
275
+ /**
276
+ * Pay for a service using x402
277
+ */
278
+ async payForService(params: {
279
+ serviceId: string;
280
+ agentAddress: string;
281
+ amount: string;
282
+ signer: any;
283
+ }): Promise<{ paymentId: string; txHash: string }> {
284
+ const paymentRequirements = this.facilitator.generatePaymentRequirements({
285
+ merchantAddress: params.agentAddress,
286
+ amount: params.amount,
287
+ resourceUrl: `https://relaycore.xyz/api/services/${params.serviceId}`,
288
+ description: `Payment for service ${params.serviceId}`,
289
+ });
290
+
291
+ const facilitator = this.facilitator.getFacilitator();
292
+ const now = Math.floor(Date.now() / 1000);
293
+ const paymentHeader = await facilitator.generatePaymentHeader({
294
+ to: params.agentAddress,
295
+ value: paymentRequirements.maxAmountRequired,
296
+ asset: paymentRequirements.asset,
297
+ signer: params.signer,
298
+ validAfter: now - 60,
299
+ validBefore: now + 300,
300
+ });
301
+
302
+ const result = await this.facilitator.settlePayment({
303
+ paymentHeader,
304
+ paymentRequirements,
305
+ });
306
+
307
+ const txHash = result.txHash || '';
308
+ const paymentId = `pay_${txHash.slice(2, 18)}`;
309
+
310
+ console.log(`Payment settled: ${txHash}`);
311
+
312
+ return { paymentId, txHash };
313
+ }
314
+
315
+ /**
316
+ * Call a protected service endpoint with automatic payment
317
+ */
318
+ async callService(params: {
319
+ endpoint: string;
320
+ paymentRequirements: PaymentRequirements;
321
+ signer: any;
322
+ }): Promise<any> {
323
+ let response = await fetch(params.endpoint, { headers: this.headers });
324
+
325
+ if (response.status === 402) {
326
+ await response.json();
327
+
328
+ const facilitator = this.facilitator.getFacilitator();
329
+ const now = Math.floor(Date.now() / 1000);
330
+ const paymentHeader = await facilitator.generatePaymentHeader({
331
+ to: params.paymentRequirements.payTo,
332
+ value: params.paymentRequirements.maxAmountRequired,
333
+ asset: params.paymentRequirements.asset,
334
+ signer: params.signer,
335
+ validAfter: now - 60,
336
+ validBefore: now + (params.paymentRequirements.maxTimeoutSeconds || 300),
337
+ });
338
+
339
+ const result = await this.facilitator.settlePayment({
340
+ paymentHeader,
341
+ paymentRequirements: params.paymentRequirements,
342
+ });
343
+
344
+ const txHash = result.txHash || '';
345
+ const paymentId = `pay_${txHash.slice(2, 18)}`;
346
+
347
+ response = await fetch(params.endpoint, {
348
+ headers: {
349
+ ...this.headers,
350
+ 'x-payment-id': paymentId,
351
+ },
352
+ });
353
+ }
354
+
355
+ if (!response.ok) {
356
+ throw new Error(`Service call failed: ${response.statusText}`);
357
+ }
358
+
359
+ return response.json();
360
+ }
361
+
362
+ /**
363
+ * Validate the API key
364
+ */
365
+ async validateApiKey(): Promise<boolean> {
366
+ if (!this.config.apiKey) return false;
367
+
368
+ try {
369
+ const response = await fetch(`${this.config.baseUrl}/api/auth/validate`, {
370
+ headers: this.headers,
371
+ });
372
+ return response.ok;
373
+ } catch {
374
+ return false;
375
+ }
376
+ }
377
+ }
378
+
379
+ /**
380
+ * Create an Agent SDK instance
381
+ *
382
+ * @example
383
+ * ```typescript
384
+ * const sdk = createAgentSDK({
385
+ * apiKey: 'rc_xxxxx',
386
+ * walletAddress: '0x1234...',
387
+ * });
388
+ * ```
389
+ */
390
+ export function createAgentSDK(config: SDKConfig): AgentSDK {
391
+ return new AgentSDK(config);
392
+ }