@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/lib/x402.ts
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X402 Payment Middleware - Cronos Standard Implementation
|
|
3
|
+
*
|
|
4
|
+
* Based on x402-examples reference from Cronos Labs.
|
|
5
|
+
* Provides middleware for protecting routes with x402 payment requirements.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import crypto from 'crypto';
|
|
9
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
10
|
+
import type { Facilitator, VerifyRequest } from '@crypto.com/facilitator-client';
|
|
11
|
+
import type {
|
|
12
|
+
X402Accepts,
|
|
13
|
+
X402Response,
|
|
14
|
+
X402PaidRecord,
|
|
15
|
+
X402PayResult,
|
|
16
|
+
X402ProtectionOptions,
|
|
17
|
+
} from '../types/x402.types';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* In-memory entitlement store keyed by payment ID.
|
|
21
|
+
*
|
|
22
|
+
* @remarks
|
|
23
|
+
* For production, replace with persistent storage (Redis, Supabase, etc.)
|
|
24
|
+
*/
|
|
25
|
+
const paidEntitlements = new Map<string, X402PaidRecord>();
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Generates a unique payment identifier per Cronos standard.
|
|
29
|
+
* @returns Payment ID in format `pay_<uuid>`
|
|
30
|
+
*/
|
|
31
|
+
export const generatePaymentId = (): string => `pay_${crypto.randomUUID()}`;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Check if a payment ID has been settled.
|
|
35
|
+
*/
|
|
36
|
+
export function isEntitled(paymentId: string): boolean {
|
|
37
|
+
return paidEntitlements.get(paymentId)?.settled === true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get entitlement record for a payment ID.
|
|
42
|
+
*/
|
|
43
|
+
export function getEntitlement(paymentId: string): X402PaidRecord | undefined {
|
|
44
|
+
return paidEntitlements.get(paymentId);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Store an entitlement record after successful payment.
|
|
49
|
+
*/
|
|
50
|
+
export function recordEntitlement(paymentId: string, record: X402PaidRecord): void {
|
|
51
|
+
paidEntitlements.set(paymentId, record);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Creates Express middleware that enforces x402 payment.
|
|
56
|
+
*
|
|
57
|
+
* If the request is already entitled (has valid x-payment-id), calls next().
|
|
58
|
+
* Otherwise, responds with HTTP 402 and x402 challenge.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* router.get('/protected', requireX402({
|
|
63
|
+
* network: 'cronos-testnet',
|
|
64
|
+
* payTo: '0x...',
|
|
65
|
+
* asset: '0x...',
|
|
66
|
+
* maxAmountRequired: '1000000',
|
|
67
|
+
* description: 'Access to premium data',
|
|
68
|
+
* resource: '/api/protected',
|
|
69
|
+
* }), handler);
|
|
70
|
+
* ```
|
|
71
|
+
*/
|
|
72
|
+
export function requireX402(options: X402ProtectionOptions) {
|
|
73
|
+
const {
|
|
74
|
+
network,
|
|
75
|
+
payTo,
|
|
76
|
+
asset,
|
|
77
|
+
maxAmountRequired,
|
|
78
|
+
maxTimeoutSeconds = 300,
|
|
79
|
+
description,
|
|
80
|
+
mimeType = 'application/json',
|
|
81
|
+
resource,
|
|
82
|
+
outputSchema,
|
|
83
|
+
getEntitlementKey,
|
|
84
|
+
} = options;
|
|
85
|
+
|
|
86
|
+
return (req: Request, res: Response, next: NextFunction): void => {
|
|
87
|
+
// Check for existing entitlement
|
|
88
|
+
const entitlementKey = (
|
|
89
|
+
getEntitlementKey?.(req) ??
|
|
90
|
+
req.header('x-payment-id') ??
|
|
91
|
+
''
|
|
92
|
+
).trim();
|
|
93
|
+
|
|
94
|
+
if (entitlementKey && isEntitled(entitlementKey)) {
|
|
95
|
+
next();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Generate new payment ID for challenge
|
|
100
|
+
const paymentId = generatePaymentId();
|
|
101
|
+
|
|
102
|
+
const accepts: X402Accepts = {
|
|
103
|
+
scheme: 'exact',
|
|
104
|
+
network,
|
|
105
|
+
asset,
|
|
106
|
+
payTo,
|
|
107
|
+
maxAmountRequired,
|
|
108
|
+
maxTimeoutSeconds,
|
|
109
|
+
description,
|
|
110
|
+
mimeType,
|
|
111
|
+
resource,
|
|
112
|
+
outputSchema,
|
|
113
|
+
extra: { paymentId },
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const response: X402Response = {
|
|
117
|
+
x402Version: 1,
|
|
118
|
+
error: 'payment_required',
|
|
119
|
+
accepts: [accepts],
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
res.status(402).json(response);
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Verifies and settles an x402 payment using the Facilitator SDK.
|
|
128
|
+
* Records entitlement on success.
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```ts
|
|
132
|
+
* const result = await handleX402Settlement({
|
|
133
|
+
* facilitator,
|
|
134
|
+
* paymentId: 'pay_...',
|
|
135
|
+
* paymentHeader: '...',
|
|
136
|
+
* paymentRequirements: {...},
|
|
137
|
+
* });
|
|
138
|
+
* ```
|
|
139
|
+
*/
|
|
140
|
+
export async function handleX402Settlement(params: {
|
|
141
|
+
facilitator: Facilitator;
|
|
142
|
+
paymentId: string;
|
|
143
|
+
paymentHeader: string;
|
|
144
|
+
paymentRequirements: VerifyRequest['paymentRequirements'];
|
|
145
|
+
}): Promise<X402PayResult> {
|
|
146
|
+
const { facilitator, paymentId, paymentHeader, paymentRequirements } = params;
|
|
147
|
+
|
|
148
|
+
const body: VerifyRequest = {
|
|
149
|
+
x402Version: 1,
|
|
150
|
+
paymentHeader,
|
|
151
|
+
paymentRequirements,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Step 1: Verify
|
|
155
|
+
const verify = await facilitator.verifyPayment(body);
|
|
156
|
+
if (!verify.isValid) {
|
|
157
|
+
return {
|
|
158
|
+
ok: false,
|
|
159
|
+
error: 'verify_failed',
|
|
160
|
+
details: verify,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Step 2: Settle
|
|
165
|
+
const settle = await facilitator.settlePayment(body);
|
|
166
|
+
if (settle.event !== 'payment.settled') {
|
|
167
|
+
return {
|
|
168
|
+
ok: false,
|
|
169
|
+
error: 'settle_failed',
|
|
170
|
+
details: settle,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Step 3: Record entitlement
|
|
175
|
+
recordEntitlement(paymentId, {
|
|
176
|
+
settled: true,
|
|
177
|
+
txHash: settle.txHash,
|
|
178
|
+
at: Date.now(),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
return {
|
|
182
|
+
ok: true,
|
|
183
|
+
txHash: settle.txHash,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Helper to create payment requirements for a resource.
|
|
189
|
+
*/
|
|
190
|
+
export function createPaymentRequirements(options: {
|
|
191
|
+
network: string;
|
|
192
|
+
payTo: string;
|
|
193
|
+
asset: string;
|
|
194
|
+
maxAmountRequired: string;
|
|
195
|
+
resource: string;
|
|
196
|
+
description: string;
|
|
197
|
+
}): X402Accepts {
|
|
198
|
+
return {
|
|
199
|
+
scheme: 'exact',
|
|
200
|
+
network: options.network as X402Accepts['network'],
|
|
201
|
+
payTo: options.payTo,
|
|
202
|
+
asset: options.asset,
|
|
203
|
+
maxAmountRequired: options.maxAmountRequired,
|
|
204
|
+
maxTimeoutSeconds: 300,
|
|
205
|
+
description: options.description,
|
|
206
|
+
mimeType: 'application/json',
|
|
207
|
+
resource: options.resource,
|
|
208
|
+
extra: { paymentId: generatePaymentId() },
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export default {
|
|
213
|
+
requireX402,
|
|
214
|
+
handleX402Settlement,
|
|
215
|
+
generatePaymentId,
|
|
216
|
+
isEntitled,
|
|
217
|
+
getEntitlement,
|
|
218
|
+
recordEntitlement,
|
|
219
|
+
createPaymentRequirements,
|
|
220
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@relaycore/sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "The official SDK for Relay Core - Build AI Agents and Services on Cronos",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsup",
|
|
10
|
+
"lint": "eslint .",
|
|
11
|
+
"test": "vitest"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"relaycore",
|
|
15
|
+
"cronos",
|
|
16
|
+
"ai",
|
|
17
|
+
"agents",
|
|
18
|
+
"sdk",
|
|
19
|
+
"x402"
|
|
20
|
+
],
|
|
21
|
+
"author": "Relay Core Team",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@crypto.com/facilitator-client": "^1.0.2",
|
|
25
|
+
"@reown/appkit": "^1.8.16",
|
|
26
|
+
"ethers": "^6.13.4",
|
|
27
|
+
"siwe": "^3.0.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/express": "^5.0.6",
|
|
31
|
+
"tsup": "^8.3.5",
|
|
32
|
+
"typescript": "^5.6.3",
|
|
33
|
+
"vitest": "^2.1.8"
|
|
34
|
+
},
|
|
35
|
+
"publishConfig": {
|
|
36
|
+
"access": "public"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/provider-sdk.ts
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* Service Provider SDK
|
|
4
|
+
*
|
|
5
|
+
* For service providers to register services, handle payments,
|
|
6
|
+
* and track their reputation on the Relay Core platform.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Configuration for the SDK
|
|
10
|
+
export interface ProviderSDKConfig {
|
|
11
|
+
apiUrl?: string;
|
|
12
|
+
supabaseUrl?: string;
|
|
13
|
+
supabaseKey?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Service registration parameters
|
|
17
|
+
export interface ServiceRegistration {
|
|
18
|
+
name: string;
|
|
19
|
+
description: string;
|
|
20
|
+
category: string;
|
|
21
|
+
endpointUrl: string;
|
|
22
|
+
pricePerCall: string;
|
|
23
|
+
inputSchema?: Record<string, unknown>;
|
|
24
|
+
outputSchema?: Record<string, unknown>;
|
|
25
|
+
inputType?: string;
|
|
26
|
+
outputType?: string;
|
|
27
|
+
tags?: string[];
|
|
28
|
+
capabilities?: string[];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Registered service response
|
|
32
|
+
export interface RegisteredService {
|
|
33
|
+
id: string;
|
|
34
|
+
name: string;
|
|
35
|
+
endpointUrl: string;
|
|
36
|
+
ownerAddress: string;
|
|
37
|
+
createdAt: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Reputation data
|
|
41
|
+
export interface ProviderReputation {
|
|
42
|
+
reputationScore: number;
|
|
43
|
+
successRate: number;
|
|
44
|
+
totalPayments: number;
|
|
45
|
+
avgLatencyMs: number;
|
|
46
|
+
trend: 'improving' | 'stable' | 'declining';
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Payment received event
|
|
50
|
+
export interface PaymentReceived {
|
|
51
|
+
paymentId: string;
|
|
52
|
+
txHash: string;
|
|
53
|
+
amount: string;
|
|
54
|
+
payerAddress: string;
|
|
55
|
+
timestamp: string;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Metrics snapshot
|
|
59
|
+
export interface ServiceMetrics {
|
|
60
|
+
timestamp: string;
|
|
61
|
+
successRate: number;
|
|
62
|
+
avgLatencyMs: number;
|
|
63
|
+
callVolume: number;
|
|
64
|
+
reputationScore: number;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const DEFAULT_API_URL = 'https://api.relaycore.xyz';
|
|
68
|
+
|
|
69
|
+
export class ServiceProviderSDK {
|
|
70
|
+
private apiUrl: string;
|
|
71
|
+
private walletAddress: string;
|
|
72
|
+
|
|
73
|
+
constructor(walletAddress: string, config: ProviderSDKConfig = {}) {
|
|
74
|
+
this.walletAddress = walletAddress.toLowerCase();
|
|
75
|
+
this.apiUrl = config.apiUrl || DEFAULT_API_URL;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Register a new service
|
|
80
|
+
*/
|
|
81
|
+
async registerService(service: ServiceRegistration): Promise<RegisteredService> {
|
|
82
|
+
const response = await fetch(`${this.apiUrl}/api/services`, {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: {
|
|
85
|
+
'Content-Type': 'application/json',
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
...service,
|
|
89
|
+
ownerAddress: this.walletAddress,
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
const error = await response.json();
|
|
95
|
+
throw new Error(`Failed to register service: ${error.error || response.statusText}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const data = await response.json();
|
|
99
|
+
return {
|
|
100
|
+
id: data.id,
|
|
101
|
+
name: data.name,
|
|
102
|
+
endpointUrl: data.endpointUrl,
|
|
103
|
+
ownerAddress: this.walletAddress,
|
|
104
|
+
createdAt: new Date().toISOString(),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Update an existing service
|
|
110
|
+
*/
|
|
111
|
+
async updateService(
|
|
112
|
+
serviceId: string,
|
|
113
|
+
updates: Partial<ServiceRegistration>
|
|
114
|
+
): Promise<void> {
|
|
115
|
+
const response = await fetch(`${this.apiUrl}/api/services/${serviceId}`, {
|
|
116
|
+
method: 'PUT',
|
|
117
|
+
headers: {
|
|
118
|
+
'Content-Type': 'application/json',
|
|
119
|
+
},
|
|
120
|
+
body: JSON.stringify(updates),
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
const error = await response.json();
|
|
125
|
+
throw new Error(`Failed to update service: ${error.error || response.statusText}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Deactivate a service
|
|
131
|
+
*/
|
|
132
|
+
async deactivateService(serviceId: string): Promise<void> {
|
|
133
|
+
await this.updateService(serviceId, { isActive: false } as any);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get current reputation
|
|
138
|
+
*/
|
|
139
|
+
async getReputation(): Promise<ProviderReputation> {
|
|
140
|
+
const response = await fetch(
|
|
141
|
+
`${this.apiUrl}/api/services?ownerAddress=${this.walletAddress}&limit=1`
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
throw new Error('Failed to fetch reputation');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const data = await response.json();
|
|
149
|
+
const service = data.services?.[0];
|
|
150
|
+
|
|
151
|
+
if (!service) {
|
|
152
|
+
return {
|
|
153
|
+
reputationScore: 0,
|
|
154
|
+
successRate: 0,
|
|
155
|
+
totalPayments: 0,
|
|
156
|
+
avgLatencyMs: 0,
|
|
157
|
+
trend: 'stable',
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return {
|
|
162
|
+
reputationScore: service.reputationScore || 0,
|
|
163
|
+
successRate: service.successRate || 0,
|
|
164
|
+
totalPayments: service.totalPayments || 0,
|
|
165
|
+
avgLatencyMs: service.avgLatencyMs || 0,
|
|
166
|
+
trend: service.trend || 'stable',
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Get service metrics history
|
|
172
|
+
*/
|
|
173
|
+
async getMetricsHistory(
|
|
174
|
+
serviceId: string,
|
|
175
|
+
options: { from?: Date; to?: Date } = {}
|
|
176
|
+
): Promise<ServiceMetrics[]> {
|
|
177
|
+
const params = new URLSearchParams();
|
|
178
|
+
if (options.from) params.set('from', options.from.toISOString());
|
|
179
|
+
if (options.to) params.set('to', options.to.toISOString());
|
|
180
|
+
|
|
181
|
+
const response = await fetch(
|
|
182
|
+
`${this.apiUrl}/api/services/${serviceId}/metrics?${params}`
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
if (!response.ok) {
|
|
186
|
+
throw new Error('Failed to fetch metrics');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const data = await response.json();
|
|
190
|
+
return data.data || [];
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Get all registered services for this provider
|
|
195
|
+
*/
|
|
196
|
+
async getMyServices(): Promise<RegisteredService[]> {
|
|
197
|
+
const response = await fetch(
|
|
198
|
+
`${this.apiUrl}/api/services?ownerAddress=${this.walletAddress}`
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (!response.ok) {
|
|
202
|
+
throw new Error('Failed to fetch services');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const data = await response.json();
|
|
206
|
+
return (data.services || []).map((s: Record<string, unknown>) => ({
|
|
207
|
+
id: s.id,
|
|
208
|
+
name: s.name,
|
|
209
|
+
endpointUrl: s.endpointUrl,
|
|
210
|
+
ownerAddress: s.ownerAddress,
|
|
211
|
+
createdAt: s.createdAt,
|
|
212
|
+
}));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Generate x402 payment requirements for an endpoint
|
|
217
|
+
* Use this when building your service's 402 response
|
|
218
|
+
*/
|
|
219
|
+
generatePaymentRequirements(params: {
|
|
220
|
+
amount: string;
|
|
221
|
+
resourceUrl: string;
|
|
222
|
+
description?: string;
|
|
223
|
+
timeoutSeconds?: number;
|
|
224
|
+
}): {
|
|
225
|
+
x402Version: number;
|
|
226
|
+
paymentRequirements: {
|
|
227
|
+
scheme: 'exact';
|
|
228
|
+
network: string;
|
|
229
|
+
payTo: string;
|
|
230
|
+
asset: string;
|
|
231
|
+
maxAmountRequired: string;
|
|
232
|
+
maxTimeoutSeconds: number;
|
|
233
|
+
description?: string;
|
|
234
|
+
};
|
|
235
|
+
} {
|
|
236
|
+
return {
|
|
237
|
+
x402Version: 1,
|
|
238
|
+
paymentRequirements: {
|
|
239
|
+
scheme: 'exact',
|
|
240
|
+
network: 'cronos-mainnet',
|
|
241
|
+
payTo: this.walletAddress,
|
|
242
|
+
asset: '0xf951eC28187D9E5Ca673Da8FE6757E6f0Be5F77C', // USDC.e on Cronos
|
|
243
|
+
maxAmountRequired: params.amount,
|
|
244
|
+
maxTimeoutSeconds: params.timeoutSeconds || 60,
|
|
245
|
+
description: params.description,
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Verify a payment was made (for use in your service)
|
|
252
|
+
*/
|
|
253
|
+
async verifyPayment(paymentId: string): Promise<{
|
|
254
|
+
verified: boolean;
|
|
255
|
+
amount?: string;
|
|
256
|
+
payerAddress?: string;
|
|
257
|
+
}> {
|
|
258
|
+
const response = await fetch(`${this.apiUrl}/api/payments/${paymentId}`);
|
|
259
|
+
|
|
260
|
+
if (!response.ok) {
|
|
261
|
+
return { verified: false };
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const data = await response.json();
|
|
265
|
+
return {
|
|
266
|
+
verified: data.payment?.status === 'settled',
|
|
267
|
+
amount: data.payment?.amount,
|
|
268
|
+
payerAddress: data.payment?.payerAddress,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Record a service outcome (success or failure)
|
|
274
|
+
* Call after each service invocation
|
|
275
|
+
*/
|
|
276
|
+
async recordOutcome(params: {
|
|
277
|
+
paymentId: string;
|
|
278
|
+
outcomeType: 'delivered' | 'failed' | 'partial';
|
|
279
|
+
latencyMs: number;
|
|
280
|
+
evidence?: Record<string, unknown>;
|
|
281
|
+
}): Promise<void> {
|
|
282
|
+
const response = await fetch(`${this.apiUrl}/api/outcomes`, {
|
|
283
|
+
method: 'POST',
|
|
284
|
+
headers: {
|
|
285
|
+
'Content-Type': 'application/json',
|
|
286
|
+
},
|
|
287
|
+
body: JSON.stringify({
|
|
288
|
+
paymentId: params.paymentId,
|
|
289
|
+
outcomeType: params.outcomeType,
|
|
290
|
+
latencyMs: params.latencyMs,
|
|
291
|
+
evidence: params.evidence,
|
|
292
|
+
}),
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
if (!response.ok) {
|
|
296
|
+
console.error('Failed to record outcome');
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Create a Service Provider SDK instance
|
|
303
|
+
*/
|
|
304
|
+
export function createProviderSDK(
|
|
305
|
+
walletAddress: string,
|
|
306
|
+
config?: ProviderSDKConfig
|
|
307
|
+
): ServiceProviderSDK {
|
|
308
|
+
return new ServiceProviderSDK(walletAddress, config);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
export default ServiceProviderSDK;
|