@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/PUBLISHING.md +37 -0
- package/README.md +434 -0
- package/agent-sdk.ts +392 -0
- package/consumer-sdk.ts +434 -0
- package/dist/index.js +14116 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +14057 -0
- package/dist/index.mjs.map +1 -0
- package/hooks.ts +250 -0
- package/index.ts +153 -0
- package/lib/erc8004.ts +51 -0
- package/lib/facilitator.ts +65 -0
- package/lib/ipfs.ts +71 -0
- package/lib/supabase.ts +5 -0
- package/lib/x402.ts +220 -0
- package/package.json +38 -0
- package/provider-sdk.ts +311 -0
- package/relay-agent.ts +1414 -0
- package/relay-rwa.ts +128 -0
- package/relay-service.ts +886 -0
- package/tsconfig.json +23 -0
- package/tsup.config.ts +19 -0
- package/types/chat.types.ts +146 -0
- package/types/x402.types.ts +114 -0
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
|
+
}
|