@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/consumer-sdk.ts
ADDED
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
import { ethers } from 'ethers';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Service Consumer SDK
|
|
5
|
+
*
|
|
6
|
+
* For agents and applications to discover services, make payments,
|
|
7
|
+
* and consume services on the Relay Core platform.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Configuration for the SDK
|
|
11
|
+
export interface ConsumerSDKConfig {
|
|
12
|
+
apiUrl?: string;
|
|
13
|
+
network?: 'testnet' | 'mainnet';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Service query parameters
|
|
17
|
+
export interface ServiceQuery {
|
|
18
|
+
category?: string;
|
|
19
|
+
minReputation?: number;
|
|
20
|
+
maxLatency?: number;
|
|
21
|
+
maxPrice?: number;
|
|
22
|
+
inputType?: string;
|
|
23
|
+
outputType?: string;
|
|
24
|
+
tags?: string[];
|
|
25
|
+
capabilities?: string[];
|
|
26
|
+
sortBy?: 'reputation' | 'latency' | 'price' | 'volume';
|
|
27
|
+
limit?: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Discovered service
|
|
31
|
+
export interface DiscoveredService {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
description: string;
|
|
35
|
+
category: string;
|
|
36
|
+
endpointUrl: string;
|
|
37
|
+
pricePerCall: string;
|
|
38
|
+
ownerAddress: string;
|
|
39
|
+
reputationScore: number;
|
|
40
|
+
successRate: number;
|
|
41
|
+
avgLatencyMs: number;
|
|
42
|
+
schema?: {
|
|
43
|
+
inputType?: string;
|
|
44
|
+
outputType?: string;
|
|
45
|
+
tags?: string[];
|
|
46
|
+
capabilities?: string[];
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Payment result
|
|
51
|
+
export interface PaymentResult {
|
|
52
|
+
paymentId: string;
|
|
53
|
+
txHash: string;
|
|
54
|
+
settled: boolean;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Service call result
|
|
58
|
+
export interface ServiceCallResult<T = unknown> {
|
|
59
|
+
success: boolean;
|
|
60
|
+
data?: T;
|
|
61
|
+
error?: string;
|
|
62
|
+
paymentId?: string;
|
|
63
|
+
latencyMs: number;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Workflow step
|
|
67
|
+
export interface WorkflowStep {
|
|
68
|
+
serviceId: string;
|
|
69
|
+
serviceName: string;
|
|
70
|
+
inputType: string;
|
|
71
|
+
outputType: string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const DEFAULT_API_URL = 'https://api.relaycore.xyz';
|
|
75
|
+
const FACILITATOR_URL = 'https://facilitator.cronoslabs.org/v2/x402';
|
|
76
|
+
|
|
77
|
+
export class ServiceConsumerSDK {
|
|
78
|
+
private apiUrl: string;
|
|
79
|
+
private network: 'testnet' | 'mainnet';
|
|
80
|
+
private signer: ethers.Signer | null = null;
|
|
81
|
+
|
|
82
|
+
constructor(config: ConsumerSDKConfig = {}) {
|
|
83
|
+
this.apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
84
|
+
this.network = config.network || 'mainnet';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Connect a signer for payment operations
|
|
89
|
+
*/
|
|
90
|
+
connectSigner(signer: ethers.Signer): void {
|
|
91
|
+
this.signer = signer;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Discover services matching criteria
|
|
96
|
+
*/
|
|
97
|
+
async discoverServices(query: ServiceQuery = {}): Promise<DiscoveredService[]> {
|
|
98
|
+
const params = new URLSearchParams();
|
|
99
|
+
|
|
100
|
+
if (query.category) params.set('category', query.category);
|
|
101
|
+
if (query.minReputation) params.set('minReputation', query.minReputation.toString());
|
|
102
|
+
if (query.maxLatency) params.set('maxLatency', query.maxLatency.toString());
|
|
103
|
+
if (query.inputType) params.set('inputType', query.inputType);
|
|
104
|
+
if (query.outputType) params.set('outputType', query.outputType);
|
|
105
|
+
if (query.tags) params.set('tags', query.tags.join(','));
|
|
106
|
+
if (query.capabilities) params.set('capabilities', query.capabilities.join(','));
|
|
107
|
+
if (query.sortBy) params.set('sortBy', query.sortBy);
|
|
108
|
+
if (query.limit) params.set('limit', query.limit.toString());
|
|
109
|
+
|
|
110
|
+
const response = await fetch(`${this.apiUrl}/api/services?${params}`);
|
|
111
|
+
|
|
112
|
+
if (!response.ok) {
|
|
113
|
+
throw new Error('Failed to discover services');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const data = await response.json();
|
|
117
|
+
return (data.services || []).map(this.formatService);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get service details
|
|
122
|
+
*/
|
|
123
|
+
async getService(serviceId: string): Promise<DiscoveredService | null> {
|
|
124
|
+
const response = await fetch(`${this.apiUrl}/api/services/${serviceId}`);
|
|
125
|
+
|
|
126
|
+
if (!response.ok) {
|
|
127
|
+
if (response.status === 404) return null;
|
|
128
|
+
throw new Error('Failed to get service');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const data = await response.json();
|
|
132
|
+
return this.formatService(data);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Find services compatible with given input/output types
|
|
137
|
+
*/
|
|
138
|
+
async findCompatibleServices(params: {
|
|
139
|
+
inputType?: string;
|
|
140
|
+
outputType?: string;
|
|
141
|
+
tags?: string[];
|
|
142
|
+
capabilities?: string[];
|
|
143
|
+
}): Promise<DiscoveredService[]> {
|
|
144
|
+
const query = new URLSearchParams();
|
|
145
|
+
if (params.inputType) query.set('inputType', params.inputType);
|
|
146
|
+
if (params.outputType) query.set('outputType', params.outputType);
|
|
147
|
+
if (params.tags) query.set('tags', params.tags.join(','));
|
|
148
|
+
if (params.capabilities) query.set('capabilities', params.capabilities.join(','));
|
|
149
|
+
|
|
150
|
+
const response = await fetch(`${this.apiUrl}/api/schemas/compatible?${query}`);
|
|
151
|
+
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
throw new Error('Failed to find compatible services');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const data = await response.json();
|
|
157
|
+
return (data.services || []).map(this.formatService);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Suggest a workflow to transform input type to output type
|
|
162
|
+
*/
|
|
163
|
+
async suggestWorkflow(params: {
|
|
164
|
+
startInputType: string;
|
|
165
|
+
endOutputType: string;
|
|
166
|
+
maxSteps?: number;
|
|
167
|
+
}): Promise<WorkflowStep[][]> {
|
|
168
|
+
const query = new URLSearchParams({
|
|
169
|
+
startInputType: params.startInputType,
|
|
170
|
+
endOutputType: params.endOutputType,
|
|
171
|
+
});
|
|
172
|
+
if (params.maxSteps) query.set('maxSteps', params.maxSteps.toString());
|
|
173
|
+
|
|
174
|
+
const response = await fetch(`${this.apiUrl}/api/schemas/workflow?${query}`);
|
|
175
|
+
|
|
176
|
+
if (!response.ok) {
|
|
177
|
+
throw new Error('Failed to suggest workflow');
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const data = await response.json();
|
|
181
|
+
return data.workflows || [];
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Call a service with automatic payment handling
|
|
186
|
+
*/
|
|
187
|
+
async callService<T = unknown>(params: {
|
|
188
|
+
serviceId: string;
|
|
189
|
+
endpoint?: string;
|
|
190
|
+
method?: 'GET' | 'POST';
|
|
191
|
+
body?: unknown;
|
|
192
|
+
headers?: Record<string, string>;
|
|
193
|
+
}): Promise<ServiceCallResult<T>> {
|
|
194
|
+
const startTime = performance.now();
|
|
195
|
+
|
|
196
|
+
if (!this.signer) {
|
|
197
|
+
throw new Error('Signer not connected. Call connectSigner first.');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Get service details if needed
|
|
201
|
+
let endpoint = params.endpoint;
|
|
202
|
+
if (!endpoint) {
|
|
203
|
+
const service = await this.getService(params.serviceId);
|
|
204
|
+
if (!service) {
|
|
205
|
+
return {
|
|
206
|
+
success: false,
|
|
207
|
+
error: 'Service not found',
|
|
208
|
+
latencyMs: Math.round(performance.now() - startTime),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
endpoint = service.endpointUrl;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Initial request
|
|
215
|
+
let response = await fetch(endpoint, {
|
|
216
|
+
method: params.method || 'GET',
|
|
217
|
+
headers: {
|
|
218
|
+
'Content-Type': 'application/json',
|
|
219
|
+
...params.headers,
|
|
220
|
+
},
|
|
221
|
+
body: params.body ? JSON.stringify(params.body) : undefined,
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
let paymentId: string | undefined;
|
|
225
|
+
|
|
226
|
+
// Handle 402 Payment Required
|
|
227
|
+
if (response.status === 402) {
|
|
228
|
+
const paymentRequired = await response.json();
|
|
229
|
+
const requirements = paymentRequired.paymentRequirements;
|
|
230
|
+
|
|
231
|
+
if (!requirements) {
|
|
232
|
+
return {
|
|
233
|
+
success: false,
|
|
234
|
+
error: 'Invalid payment requirements',
|
|
235
|
+
latencyMs: Math.round(performance.now() - startTime),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Make payment via facilitator
|
|
240
|
+
const paymentResult = await this.makePayment({
|
|
241
|
+
to: requirements.payTo,
|
|
242
|
+
amount: requirements.maxAmountRequired,
|
|
243
|
+
asset: requirements.asset,
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
paymentId = paymentResult.paymentId;
|
|
247
|
+
|
|
248
|
+
// Retry with payment header
|
|
249
|
+
response = await fetch(endpoint, {
|
|
250
|
+
method: params.method || 'GET',
|
|
251
|
+
headers: {
|
|
252
|
+
'Content-Type': 'application/json',
|
|
253
|
+
'X-Payment': paymentResult.txHash,
|
|
254
|
+
'X-Payment-Id': paymentId,
|
|
255
|
+
...params.headers,
|
|
256
|
+
},
|
|
257
|
+
body: params.body ? JSON.stringify(params.body) : undefined,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
const latencyMs = Math.round(performance.now() - startTime);
|
|
262
|
+
|
|
263
|
+
if (!response.ok) {
|
|
264
|
+
return {
|
|
265
|
+
success: false,
|
|
266
|
+
error: `Service returned ${response.status}: ${response.statusText}`,
|
|
267
|
+
paymentId,
|
|
268
|
+
latencyMs,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
const data = await response.json();
|
|
273
|
+
return {
|
|
274
|
+
success: true,
|
|
275
|
+
data,
|
|
276
|
+
paymentId,
|
|
277
|
+
latencyMs,
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Make a direct payment to a service provider
|
|
283
|
+
*/
|
|
284
|
+
async makePayment(params: {
|
|
285
|
+
to: string;
|
|
286
|
+
amount: string;
|
|
287
|
+
asset?: string;
|
|
288
|
+
}): Promise<PaymentResult> {
|
|
289
|
+
if (!this.signer) {
|
|
290
|
+
throw new Error('Signer not connected');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const signerAddress = await this.signer.getAddress();
|
|
294
|
+
|
|
295
|
+
// EIP-3009 authorization for USDC transfer
|
|
296
|
+
const domain = {
|
|
297
|
+
name: 'USD Coin',
|
|
298
|
+
version: '2',
|
|
299
|
+
chainId: this.network === 'mainnet' ? 25 : 338,
|
|
300
|
+
verifyingContract: params.asset || '0xf951eC28187D9E5Ca673Da8FE6757E6f0Be5F77C',
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const types = {
|
|
304
|
+
TransferWithAuthorization: [
|
|
305
|
+
{ name: 'from', type: 'address' },
|
|
306
|
+
{ name: 'to', type: 'address' },
|
|
307
|
+
{ name: 'value', type: 'uint256' },
|
|
308
|
+
{ name: 'validAfter', type: 'uint256' },
|
|
309
|
+
{ name: 'validBefore', type: 'uint256' },
|
|
310
|
+
{ name: 'nonce', type: 'bytes32' },
|
|
311
|
+
],
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const nonce = ethers.hexlify(ethers.randomBytes(32));
|
|
315
|
+
const validAfter = 0;
|
|
316
|
+
const validBefore = Math.floor(Date.now() / 1000) + 3600;
|
|
317
|
+
|
|
318
|
+
const value = {
|
|
319
|
+
from: signerAddress,
|
|
320
|
+
to: params.to,
|
|
321
|
+
value: ethers.parseUnits(params.amount, 6),
|
|
322
|
+
validAfter,
|
|
323
|
+
validBefore,
|
|
324
|
+
nonce,
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// Sign authorization
|
|
328
|
+
const signature = await (this.signer as ethers.Signer & {
|
|
329
|
+
signTypedData: (
|
|
330
|
+
domain: typeof domain,
|
|
331
|
+
types: typeof types,
|
|
332
|
+
value: typeof value
|
|
333
|
+
) => Promise<string>;
|
|
334
|
+
}).signTypedData(domain, types, value);
|
|
335
|
+
|
|
336
|
+
// Submit to facilitator
|
|
337
|
+
const response = await fetch(`${FACILITATOR_URL}/settle`, {
|
|
338
|
+
method: 'POST',
|
|
339
|
+
headers: {
|
|
340
|
+
'Content-Type': 'application/json',
|
|
341
|
+
},
|
|
342
|
+
body: JSON.stringify({
|
|
343
|
+
from: signerAddress,
|
|
344
|
+
to: params.to,
|
|
345
|
+
value: params.amount,
|
|
346
|
+
validAfter,
|
|
347
|
+
validBefore,
|
|
348
|
+
nonce,
|
|
349
|
+
signature,
|
|
350
|
+
network: this.network === 'mainnet' ? 'cronos-mainnet' : 'cronos-testnet',
|
|
351
|
+
}),
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
if (!response.ok) {
|
|
355
|
+
throw new Error('Payment failed');
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const result = await response.json();
|
|
359
|
+
return {
|
|
360
|
+
paymentId: `pay_${result.txHash?.slice(2, 18) || Date.now()}`,
|
|
361
|
+
txHash: result.txHash || '',
|
|
362
|
+
settled: result.success || false,
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Get service dependencies graph
|
|
368
|
+
*/
|
|
369
|
+
async getServiceGraph(serviceId: string): Promise<{
|
|
370
|
+
dependencies: Array<{ serviceId: string; callCount: number }>;
|
|
371
|
+
dependents: Array<{ serviceId: string; callCount: number }>;
|
|
372
|
+
}> {
|
|
373
|
+
const [depsResponse, deptsResponse] = await Promise.all([
|
|
374
|
+
fetch(`${this.apiUrl}/api/services/${serviceId}/dependencies`),
|
|
375
|
+
fetch(`${this.apiUrl}/api/services/${serviceId}/dependents`),
|
|
376
|
+
]);
|
|
377
|
+
|
|
378
|
+
const dependencies = depsResponse.ok
|
|
379
|
+
? (await depsResponse.json()).dependencies
|
|
380
|
+
: [];
|
|
381
|
+
const dependents = deptsResponse.ok
|
|
382
|
+
? (await deptsResponse.json()).dependents
|
|
383
|
+
: [];
|
|
384
|
+
|
|
385
|
+
return { dependencies, dependents };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Find path between two services
|
|
390
|
+
*/
|
|
391
|
+
async findServicePath(
|
|
392
|
+
fromServiceId: string,
|
|
393
|
+
toServiceId: string
|
|
394
|
+
): Promise<{
|
|
395
|
+
path: string[];
|
|
396
|
+
totalLatency: number;
|
|
397
|
+
} | null> {
|
|
398
|
+
const response = await fetch(
|
|
399
|
+
`${this.apiUrl}/api/graph/path?from=${fromServiceId}&to=${toServiceId}`
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
if (!response.ok) {
|
|
403
|
+
return null;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
const data = await response.json();
|
|
407
|
+
return data.shortestPath || null;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private formatService(s: Record<string, unknown>): DiscoveredService {
|
|
411
|
+
return {
|
|
412
|
+
id: s.id as string,
|
|
413
|
+
name: s.name as string,
|
|
414
|
+
description: s.description as string,
|
|
415
|
+
category: s.category as string,
|
|
416
|
+
endpointUrl: s.endpointUrl as string,
|
|
417
|
+
pricePerCall: s.pricePerCall as string,
|
|
418
|
+
ownerAddress: s.ownerAddress as string,
|
|
419
|
+
reputationScore: (s.reputationScore as number) || 0,
|
|
420
|
+
successRate: (s.successRate as number) || 0,
|
|
421
|
+
avgLatencyMs: (s.avgLatencyMs as number) || 0,
|
|
422
|
+
schema: s.schema as DiscoveredService['schema'],
|
|
423
|
+
};
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Create a Service Consumer SDK instance
|
|
429
|
+
*/
|
|
430
|
+
export function createConsumerSDK(config?: ConsumerSDKConfig): ServiceConsumerSDK {
|
|
431
|
+
return new ServiceConsumerSDK(config);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
export default ServiceConsumerSDK;
|