@halot/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/README.md +243 -0
- package/dist/auth/actor.d.ts +17 -0
- package/dist/auth/actor.js +33 -0
- package/dist/auth/attestation.d.ts +24 -0
- package/dist/auth/attestation.js +47 -0
- package/dist/client/halot-client.d.ts +37 -0
- package/dist/client/halot-client.js +89 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +5 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.js +11 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +43 -0
- package/dist/middleware/aggregator-client.d.ts +8 -0
- package/dist/middleware/aggregator-client.js +49 -0
- package/dist/middleware/halot.d.ts +4 -0
- package/dist/middleware/halot.js +142 -0
- package/dist/middleware/index.d.ts +2 -0
- package/dist/middleware/index.js +5 -0
- package/dist/middleware/types.d.ts +45 -0
- package/dist/middleware/types.js +2 -0
- package/dist/types/common.d.ts +90 -0
- package/dist/types/common.js +66 -0
- package/dist/types/job.d.ts +232 -0
- package/dist/types/job.js +125 -0
- package/dist/types/provider.d.ts +160 -0
- package/dist/types/provider.js +45 -0
- package/dist/types/quote.d.ts +43 -0
- package/dist/types/quote.js +31 -0
- package/dist/types/requester.d.ts +15 -0
- package/dist/types/requester.js +15 -0
- package/dist/types/service.d.ts +140 -0
- package/dist/types/service.js +48 -0
- package/dist/types/verifier.d.ts +144 -0
- package/dist/types/verifier.js +78 -0
- package/dist/types/x402.d.ts +31 -0
- package/dist/types/x402.js +27 -0
- package/dist/utils/amount.d.ts +2 -0
- package/dist/utils/amount.js +11 -0
- package/dist/utils/artifacts.d.ts +42 -0
- package/dist/utils/artifacts.js +378 -0
- package/dist/utils/category.d.ts +4 -0
- package/dist/utils/category.js +25 -0
- package/dist/utils/date.d.ts +2 -0
- package/dist/utils/date.js +10 -0
- package/dist/utils/hash.d.ts +4 -0
- package/dist/utils/hash.js +27 -0
- package/dist/utils/id.d.ts +1 -0
- package/dist/utils/id.js +7 -0
- package/dist/utils/json-file.d.ts +3 -0
- package/dist/utils/json-file.js +20 -0
- package/dist/utils/schema.d.ts +5 -0
- package/dist/utils/schema.js +66 -0
- package/dist/x402/payment.d.ts +11 -0
- package/dist/x402/payment.js +68 -0
- package/dist/zero-g/compute.d.ts +53 -0
- package/dist/zero-g/compute.js +249 -0
- package/dist/zero-g/storage.d.ts +28 -0
- package/dist/zero-g/storage.js +93 -0
- package/package.json +66 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveImageArtifacts = resolveImageArtifacts;
|
|
4
|
+
exports.buildArtifactSummary = buildArtifactSummary;
|
|
5
|
+
const hash_1 = require("./hash");
|
|
6
|
+
const imagePathPattern = /(image|images|artifact|artifacts|thumbnail|preview|mask|reference)/i;
|
|
7
|
+
const directImageFieldPattern = /(image|imageUrl|imageUri|dataUrl|dataURI|uri|url|src|href)$/i;
|
|
8
|
+
const imageMimePattern = /^image\//i;
|
|
9
|
+
const imageExtensionPattern = /\.(png|jpe?g|gif|webp|bmp|svg)(?:[?#].*)?$/i;
|
|
10
|
+
const defaultMaxImageBytes = 5 * 1024 * 1024;
|
|
11
|
+
const defaultMaxImagesPerOrigin = 4;
|
|
12
|
+
async function resolveImageArtifacts(input, result, options = {}) {
|
|
13
|
+
const fetchImplementation = options.fetchImplementation ?? fetch;
|
|
14
|
+
const maxImageBytes = options.maxImageBytes ?? defaultMaxImageBytes;
|
|
15
|
+
const maxImagesPerOrigin = options.maxImagesPerOrigin ?? defaultMaxImagesPerOrigin;
|
|
16
|
+
const inputCandidates = collectImageArtifactCandidates(input, 'input');
|
|
17
|
+
const resultCandidates = collectImageArtifactCandidates(result, 'result');
|
|
18
|
+
if (resultCandidates.length === 0
|
|
19
|
+
&& typeof result === 'string'
|
|
20
|
+
&& isSupportedImageReference(result)
|
|
21
|
+
&& (options.outputFormat ?? '').toLowerCase().includes('image')) {
|
|
22
|
+
resultCandidates.push({
|
|
23
|
+
origin: 'result',
|
|
24
|
+
path: 'result',
|
|
25
|
+
url: result,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const issues = [];
|
|
29
|
+
if ((options.outputFormat ?? '').toLowerCase().includes('image') && resultCandidates.length === 0) {
|
|
30
|
+
issues.push('Service output format indicates image output, but no image artifacts were found in the provider result.');
|
|
31
|
+
}
|
|
32
|
+
const inputResolution = await resolveCandidates(inputCandidates.slice(0, maxImagesPerOrigin), fetchImplementation, maxImageBytes);
|
|
33
|
+
const resultResolution = await resolveCandidates(resultCandidates.slice(0, maxImagesPerOrigin), fetchImplementation, maxImageBytes);
|
|
34
|
+
issues.push(...inputResolution.failures.flatMap(formatFailure));
|
|
35
|
+
issues.push(...resultResolution.failures.flatMap(formatFailure));
|
|
36
|
+
if (inputCandidates.length > maxImagesPerOrigin) {
|
|
37
|
+
issues.push(`Only the first ${maxImagesPerOrigin} input image artifacts were evaluated.`);
|
|
38
|
+
}
|
|
39
|
+
if (resultCandidates.length > maxImagesPerOrigin) {
|
|
40
|
+
issues.push(`Only the first ${maxImagesPerOrigin} result image artifacts were evaluated.`);
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
inputImages: inputResolution.images,
|
|
44
|
+
resultImages: resultResolution.images,
|
|
45
|
+
issues,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function buildArtifactSummary(artifacts) {
|
|
49
|
+
if (artifacts.length === 0) {
|
|
50
|
+
return 'None';
|
|
51
|
+
}
|
|
52
|
+
return artifacts.map((artifact) => {
|
|
53
|
+
const dimensions = artifact.width && artifact.height ? `, ${artifact.width}x${artifact.height}` : '';
|
|
54
|
+
return `- ${artifact.path}: ${artifact.mimeType}${dimensions}, sha256=${artifact.sha256}, bytes=${artifact.byteLength}`;
|
|
55
|
+
}).join('\n');
|
|
56
|
+
}
|
|
57
|
+
function formatFailure(failure) {
|
|
58
|
+
return failure.errors.map((error) => `${failure.path}: ${error}`);
|
|
59
|
+
}
|
|
60
|
+
function collectImageArtifactCandidates(value, origin) {
|
|
61
|
+
const seen = new WeakSet();
|
|
62
|
+
const results = [];
|
|
63
|
+
const dedupe = new Set();
|
|
64
|
+
const visit = (current, path, parentKey) => {
|
|
65
|
+
if (typeof current === 'string') {
|
|
66
|
+
const candidate = buildStringCandidate(current, origin, path, parentKey);
|
|
67
|
+
if (candidate) {
|
|
68
|
+
const key = `${candidate.origin}:${candidate.url}`;
|
|
69
|
+
if (!dedupe.has(key)) {
|
|
70
|
+
dedupe.add(key);
|
|
71
|
+
results.push(candidate);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (!current || typeof current !== 'object') {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (seen.has(current)) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
seen.add(current);
|
|
83
|
+
if (Array.isArray(current)) {
|
|
84
|
+
current.forEach((item, index) => visit(item, `${path}[${index}]`, parentKey));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
const candidate = buildObjectCandidate(current, origin, path, parentKey);
|
|
88
|
+
if (candidate) {
|
|
89
|
+
const key = `${candidate.origin}:${candidate.url}`;
|
|
90
|
+
if (!dedupe.has(key)) {
|
|
91
|
+
dedupe.add(key);
|
|
92
|
+
results.push(candidate);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
for (const [key, item] of Object.entries(current)) {
|
|
96
|
+
visit(item, `${path}.${key}`, key);
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
visit(value, origin);
|
|
100
|
+
return results;
|
|
101
|
+
}
|
|
102
|
+
function buildStringCandidate(value, origin, path, parentKey) {
|
|
103
|
+
if (!isSupportedImageReference(value)) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
if (!imagePathPattern.test(path) && !directImageFieldPattern.test(parentKey ?? '')) {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
origin,
|
|
111
|
+
path,
|
|
112
|
+
url: value,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
function buildObjectCandidate(value, origin, path, parentKey) {
|
|
116
|
+
const url = firstString(value, ['dataUrl', 'dataURI', 'imageUrl', 'imageURI', 'url', 'uri', 'src', 'href']);
|
|
117
|
+
if (!url || !isSupportedImageReference(url)) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const mimeType = firstString(value, ['mimeType', 'contentType', 'mediaType', 'type']);
|
|
121
|
+
const isImageObject = Boolean((mimeType && imageMimePattern.test(mimeType))
|
|
122
|
+
|| value.kind === 'image'
|
|
123
|
+
|| value.artifactType === 'image'
|
|
124
|
+
|| imagePathPattern.test(path)
|
|
125
|
+
|| imagePathPattern.test(parentKey ?? '')
|
|
126
|
+
|| imageExtensionPattern.test(url)
|
|
127
|
+
|| isDataImageUri(url));
|
|
128
|
+
if (!isImageObject) {
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
const sha256 = firstString(value, ['sha256', 'sha256Hash', 'contentSha256', 'checksumSha256', 'digest']);
|
|
132
|
+
const width = firstNumber(value, ['width']);
|
|
133
|
+
const height = firstNumber(value, ['height']);
|
|
134
|
+
return {
|
|
135
|
+
origin,
|
|
136
|
+
path,
|
|
137
|
+
url,
|
|
138
|
+
mimeType,
|
|
139
|
+
sha256,
|
|
140
|
+
width,
|
|
141
|
+
height,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
async function resolveCandidates(candidates, fetchImplementation, maxImageBytes) {
|
|
145
|
+
const images = [];
|
|
146
|
+
const failures = [];
|
|
147
|
+
for (const candidate of candidates) {
|
|
148
|
+
try {
|
|
149
|
+
const resolved = await resolveCandidate(candidate, fetchImplementation, maxImageBytes);
|
|
150
|
+
images.push(resolved.image);
|
|
151
|
+
if (resolved.errors.length > 0) {
|
|
152
|
+
failures.push({
|
|
153
|
+
origin: candidate.origin,
|
|
154
|
+
path: candidate.path,
|
|
155
|
+
url: candidate.url,
|
|
156
|
+
errors: resolved.errors,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
catch (error) {
|
|
161
|
+
failures.push({
|
|
162
|
+
origin: candidate.origin,
|
|
163
|
+
path: candidate.path,
|
|
164
|
+
url: candidate.url,
|
|
165
|
+
errors: [error instanceof Error ? error.message : String(error)],
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return { images, failures };
|
|
170
|
+
}
|
|
171
|
+
async function resolveCandidate(candidate, fetchImplementation, maxImageBytes) {
|
|
172
|
+
const { bytes, mimeType: detectedMimeType } = await loadImageBytes(candidate.url, fetchImplementation, maxImageBytes);
|
|
173
|
+
const metadata = inferImageMetadata(bytes, candidate.mimeType ?? detectedMimeType);
|
|
174
|
+
const mimeType = candidate.mimeType ?? metadata.mimeType ?? detectedMimeType;
|
|
175
|
+
if (!imageMimePattern.test(mimeType)) {
|
|
176
|
+
throw new Error(`Resolved artifact is not an image (${mimeType})`);
|
|
177
|
+
}
|
|
178
|
+
const sha256 = (0, hash_1.hashBytes)(bytes);
|
|
179
|
+
const errors = [];
|
|
180
|
+
if (candidate.mimeType && candidate.mimeType !== mimeType) {
|
|
181
|
+
errors.push(`declared mimeType ${candidate.mimeType} does not match fetched mimeType ${mimeType}`);
|
|
182
|
+
}
|
|
183
|
+
if (candidate.sha256 && candidate.sha256 !== sha256) {
|
|
184
|
+
errors.push(`declared sha256 ${candidate.sha256} does not match fetched content hash ${sha256}`);
|
|
185
|
+
}
|
|
186
|
+
if (candidate.width && metadata.width && candidate.width !== metadata.width) {
|
|
187
|
+
errors.push(`declared width ${candidate.width} does not match fetched width ${metadata.width}`);
|
|
188
|
+
}
|
|
189
|
+
if (candidate.height && metadata.height && candidate.height !== metadata.height) {
|
|
190
|
+
errors.push(`declared height ${candidate.height} does not match fetched height ${metadata.height}`);
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
image: {
|
|
194
|
+
origin: candidate.origin,
|
|
195
|
+
path: candidate.path,
|
|
196
|
+
url: candidate.url,
|
|
197
|
+
mimeType,
|
|
198
|
+
sha256,
|
|
199
|
+
byteLength: bytes.byteLength,
|
|
200
|
+
width: metadata.width ?? candidate.width,
|
|
201
|
+
height: metadata.height ?? candidate.height,
|
|
202
|
+
dataUrl: `data:${mimeType};base64,${Buffer.from(bytes).toString('base64')}`,
|
|
203
|
+
},
|
|
204
|
+
errors,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
async function loadImageBytes(url, fetchImplementation, maxImageBytes) {
|
|
208
|
+
if (isDataImageUri(url)) {
|
|
209
|
+
const match = url.match(/^data:([^;,]+);base64,(.+)$/);
|
|
210
|
+
if (!match) {
|
|
211
|
+
throw new Error('Unsupported data URI image format');
|
|
212
|
+
}
|
|
213
|
+
const bytes = Buffer.from(match[2], 'base64');
|
|
214
|
+
if (bytes.byteLength > maxImageBytes) {
|
|
215
|
+
throw new Error(`image exceeds maximum size of ${maxImageBytes} bytes`);
|
|
216
|
+
}
|
|
217
|
+
return {
|
|
218
|
+
bytes,
|
|
219
|
+
mimeType: match[1],
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (!isHttpUrl(url)) {
|
|
223
|
+
throw new Error('Only http(s) URLs and data:image URIs are supported for image verification');
|
|
224
|
+
}
|
|
225
|
+
const response = await fetchImplementation(url);
|
|
226
|
+
if (!response.ok) {
|
|
227
|
+
throw new Error(`failed to fetch image artifact (${response.status})`);
|
|
228
|
+
}
|
|
229
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
230
|
+
const bytes = new Uint8Array(arrayBuffer);
|
|
231
|
+
if (bytes.byteLength > maxImageBytes) {
|
|
232
|
+
throw new Error(`image exceeds maximum size of ${maxImageBytes} bytes`);
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
bytes,
|
|
236
|
+
mimeType: response.headers.get('content-type')?.split(';')[0]?.trim() || inferMimeTypeFromUrl(url) || 'application/octet-stream',
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function inferImageMetadata(bytes, fallbackMimeType) {
|
|
240
|
+
if (bytes.byteLength >= 24 && bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47) {
|
|
241
|
+
return {
|
|
242
|
+
mimeType: 'image/png',
|
|
243
|
+
width: readUInt32BE(bytes, 16),
|
|
244
|
+
height: readUInt32BE(bytes, 20),
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
if (bytes.byteLength >= 10 && bytes[0] === 0x47 && bytes[1] === 0x49 && bytes[2] === 0x46) {
|
|
248
|
+
return {
|
|
249
|
+
mimeType: 'image/gif',
|
|
250
|
+
width: readUInt16LE(bytes, 6),
|
|
251
|
+
height: readUInt16LE(bytes, 8),
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
if (bytes.byteLength >= 12 && String.fromCharCode(...bytes.slice(0, 4)) === 'RIFF' && String.fromCharCode(...bytes.slice(8, 12)) === 'WEBP') {
|
|
255
|
+
return {
|
|
256
|
+
mimeType: 'image/webp',
|
|
257
|
+
...readWebpDimensions(bytes),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
if (bytes.byteLength >= 4 && bytes[0] === 0xff && bytes[1] === 0xd8) {
|
|
261
|
+
return {
|
|
262
|
+
mimeType: 'image/jpeg',
|
|
263
|
+
...readJpegDimensions(bytes),
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
const asciiPrefix = Buffer.from(bytes.slice(0, 256)).toString('utf8').trimStart();
|
|
267
|
+
if (asciiPrefix.startsWith('<svg') || asciiPrefix.startsWith('<?xml')) {
|
|
268
|
+
return {
|
|
269
|
+
mimeType: 'image/svg+xml',
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
mimeType: fallbackMimeType ?? 'application/octet-stream',
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
function readJpegDimensions(bytes) {
|
|
277
|
+
let offset = 2;
|
|
278
|
+
while (offset + 9 < bytes.byteLength) {
|
|
279
|
+
if (bytes[offset] !== 0xff) {
|
|
280
|
+
offset += 1;
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
const marker = bytes[offset + 1];
|
|
284
|
+
const length = readUInt16BE(bytes, offset + 2);
|
|
285
|
+
if ([0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf].includes(marker)) {
|
|
286
|
+
return {
|
|
287
|
+
height: readUInt16BE(bytes, offset + 5),
|
|
288
|
+
width: readUInt16BE(bytes, offset + 7),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
if (length < 2) {
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
offset += 2 + length;
|
|
295
|
+
}
|
|
296
|
+
return {};
|
|
297
|
+
}
|
|
298
|
+
function readWebpDimensions(bytes) {
|
|
299
|
+
const chunkType = Buffer.from(bytes.slice(12, 16)).toString('ascii');
|
|
300
|
+
if (chunkType === 'VP8X' && bytes.byteLength >= 30) {
|
|
301
|
+
return {
|
|
302
|
+
width: 1 + readUInt24LE(bytes, 24),
|
|
303
|
+
height: 1 + readUInt24LE(bytes, 27),
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
if (chunkType === 'VP8 ' && bytes.byteLength >= 30) {
|
|
307
|
+
return {
|
|
308
|
+
width: readUInt16LE(bytes, 26) & 0x3fff,
|
|
309
|
+
height: readUInt16LE(bytes, 28) & 0x3fff,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
if (chunkType === 'VP8L' && bytes.byteLength >= 25) {
|
|
313
|
+
const bits = readUInt32LE(bytes, 21);
|
|
314
|
+
return {
|
|
315
|
+
width: (bits & 0x3fff) + 1,
|
|
316
|
+
height: ((bits >> 14) & 0x3fff) + 1,
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
return {};
|
|
320
|
+
}
|
|
321
|
+
function readUInt16BE(bytes, offset) {
|
|
322
|
+
return (bytes[offset] << 8) | bytes[offset + 1];
|
|
323
|
+
}
|
|
324
|
+
function readUInt16LE(bytes, offset) {
|
|
325
|
+
return bytes[offset] | (bytes[offset + 1] << 8);
|
|
326
|
+
}
|
|
327
|
+
function readUInt24LE(bytes, offset) {
|
|
328
|
+
return bytes[offset] | (bytes[offset + 1] << 8) | (bytes[offset + 2] << 16);
|
|
329
|
+
}
|
|
330
|
+
function readUInt32BE(bytes, offset) {
|
|
331
|
+
return (bytes[offset] * 2 ** 24) + (bytes[offset + 1] << 16) + (bytes[offset + 2] << 8) + bytes[offset + 3];
|
|
332
|
+
}
|
|
333
|
+
function readUInt32LE(bytes, offset) {
|
|
334
|
+
return bytes[offset] + (bytes[offset + 1] << 8) + (bytes[offset + 2] << 16) + (bytes[offset + 3] * 2 ** 24);
|
|
335
|
+
}
|
|
336
|
+
function firstString(value, keys) {
|
|
337
|
+
for (const key of keys) {
|
|
338
|
+
const item = value[key];
|
|
339
|
+
if (typeof item === 'string' && item.length > 0) {
|
|
340
|
+
return item;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return undefined;
|
|
344
|
+
}
|
|
345
|
+
function firstNumber(value, keys) {
|
|
346
|
+
for (const key of keys) {
|
|
347
|
+
const item = value[key];
|
|
348
|
+
if (typeof item === 'number' && Number.isFinite(item)) {
|
|
349
|
+
return item;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
return undefined;
|
|
353
|
+
}
|
|
354
|
+
function isSupportedImageReference(value) {
|
|
355
|
+
return isDataImageUri(value) || (isHttpUrl(value) && imageExtensionPattern.test(value)) || isHttpUrl(value);
|
|
356
|
+
}
|
|
357
|
+
function isDataImageUri(value) {
|
|
358
|
+
return /^data:image\/[^;]+;base64,/i.test(value);
|
|
359
|
+
}
|
|
360
|
+
function isHttpUrl(value) {
|
|
361
|
+
return /^https?:\/\//i.test(value);
|
|
362
|
+
}
|
|
363
|
+
function inferMimeTypeFromUrl(url) {
|
|
364
|
+
const normalized = url.split('?')[0]?.toLowerCase() ?? url.toLowerCase();
|
|
365
|
+
if (normalized.endsWith('.png'))
|
|
366
|
+
return 'image/png';
|
|
367
|
+
if (normalized.endsWith('.jpg') || normalized.endsWith('.jpeg'))
|
|
368
|
+
return 'image/jpeg';
|
|
369
|
+
if (normalized.endsWith('.gif'))
|
|
370
|
+
return 'image/gif';
|
|
371
|
+
if (normalized.endsWith('.webp'))
|
|
372
|
+
return 'image/webp';
|
|
373
|
+
if (normalized.endsWith('.bmp'))
|
|
374
|
+
return 'image/bmp';
|
|
375
|
+
if (normalized.endsWith('.svg'))
|
|
376
|
+
return 'image/svg+xml';
|
|
377
|
+
return undefined;
|
|
378
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { ServiceCategory } from '../types/common';
|
|
2
|
+
export declare function serviceCategoryToMask(category: ServiceCategory): bigint;
|
|
3
|
+
export declare function serviceCategoriesToMask(categories: ServiceCategory[]): bigint;
|
|
4
|
+
export declare function isKnownServiceCategory(value: string): value is ServiceCategory;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.serviceCategoryToMask = serviceCategoryToMask;
|
|
4
|
+
exports.serviceCategoriesToMask = serviceCategoriesToMask;
|
|
5
|
+
exports.isKnownServiceCategory = isKnownServiceCategory;
|
|
6
|
+
const common_1 = require("../types/common");
|
|
7
|
+
const categoryBitIndex = {
|
|
8
|
+
text: 0,
|
|
9
|
+
code: 1,
|
|
10
|
+
image: 2,
|
|
11
|
+
audio: 3,
|
|
12
|
+
video: 4,
|
|
13
|
+
document: 5,
|
|
14
|
+
tool: 6,
|
|
15
|
+
workflow: 7,
|
|
16
|
+
};
|
|
17
|
+
function serviceCategoryToMask(category) {
|
|
18
|
+
return 1n << BigInt(categoryBitIndex[category]);
|
|
19
|
+
}
|
|
20
|
+
function serviceCategoriesToMask(categories) {
|
|
21
|
+
return categories.reduce((mask, category) => mask | serviceCategoryToMask(category), 0n);
|
|
22
|
+
}
|
|
23
|
+
function isKnownServiceCategory(value) {
|
|
24
|
+
return common_1.serviceCategoryValues.includes(value);
|
|
25
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.nowIso = nowIso;
|
|
4
|
+
exports.addMinutes = addMinutes;
|
|
5
|
+
function nowIso() {
|
|
6
|
+
return new Date().toISOString();
|
|
7
|
+
}
|
|
8
|
+
function addMinutes(date, minutes) {
|
|
9
|
+
return new Date(date.getTime() + minutes * 60_000).toISOString();
|
|
10
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.stableStringify = stableStringify;
|
|
4
|
+
exports.hashValue = hashValue;
|
|
5
|
+
exports.hashText = hashText;
|
|
6
|
+
exports.hashBytes = hashBytes;
|
|
7
|
+
const node_crypto_1 = require("node:crypto");
|
|
8
|
+
function stableStringify(value) {
|
|
9
|
+
if (value === null || typeof value !== 'object') {
|
|
10
|
+
return JSON.stringify(value);
|
|
11
|
+
}
|
|
12
|
+
if (Array.isArray(value)) {
|
|
13
|
+
return `[${value.map((item) => stableStringify(item)).join(',')}]`;
|
|
14
|
+
}
|
|
15
|
+
const entries = Object.entries(value).sort(([left], [right]) => left.localeCompare(right));
|
|
16
|
+
return `{${entries.map(([key, item]) => `${JSON.stringify(key)}:${stableStringify(item)}`).join(',')}}`;
|
|
17
|
+
}
|
|
18
|
+
function hashValue(value) {
|
|
19
|
+
return `0x${(0, node_crypto_1.createHash)('sha256').update(stableStringify(value)).digest('hex')}`;
|
|
20
|
+
}
|
|
21
|
+
function hashText(value) {
|
|
22
|
+
return `0x${(0, node_crypto_1.createHash)('sha256').update(value).digest('hex')}`;
|
|
23
|
+
}
|
|
24
|
+
function hashBytes(value) {
|
|
25
|
+
const bytes = value instanceof Uint8Array ? value : new Uint8Array(value);
|
|
26
|
+
return `0x${(0, node_crypto_1.createHash)('sha256').update(bytes).digest('hex')}`;
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function createPrefixedId(prefix: string): string;
|
package/dist/utils/id.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createPrefixedId = createPrefixedId;
|
|
4
|
+
const node_crypto_1 = require("node:crypto");
|
|
5
|
+
function createPrefixedId(prefix) {
|
|
6
|
+
return `${prefix}_${(0, node_crypto_1.randomUUID)().replaceAll('-', '').slice(0, 16)}`;
|
|
7
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ensureDirectory = ensureDirectory;
|
|
7
|
+
exports.readJsonFile = readJsonFile;
|
|
8
|
+
exports.writeJsonFile = writeJsonFile;
|
|
9
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
10
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
+
function ensureDirectory(directoryPath) {
|
|
12
|
+
node_fs_1.default.mkdirSync(directoryPath, { recursive: true });
|
|
13
|
+
}
|
|
14
|
+
function readJsonFile(filePath) {
|
|
15
|
+
return JSON.parse(node_fs_1.default.readFileSync(filePath, 'utf8'));
|
|
16
|
+
}
|
|
17
|
+
function writeJsonFile(filePath, data) {
|
|
18
|
+
ensureDirectory(node_path_1.default.dirname(filePath));
|
|
19
|
+
node_fs_1.default.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
20
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateSchema = validateSchema;
|
|
4
|
+
function validateValue(value, schema, path, errors) {
|
|
5
|
+
if (!schema || typeof schema !== 'object') {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
if (schema.type === 'object') {
|
|
9
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
10
|
+
errors.push(`${path} must be an object`);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
const record = value;
|
|
14
|
+
const required = Array.isArray(schema.required) ? schema.required : [];
|
|
15
|
+
for (const key of required) {
|
|
16
|
+
if (!(key in record)) {
|
|
17
|
+
errors.push(`${path}.${key} is required`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const properties = schema.properties ?? {};
|
|
21
|
+
for (const [key, nestedSchema] of Object.entries(properties)) {
|
|
22
|
+
if (key in record) {
|
|
23
|
+
validateValue(record[key], nestedSchema, `${path}.${key}`, errors);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (schema.type === 'array') {
|
|
29
|
+
if (!Array.isArray(value)) {
|
|
30
|
+
errors.push(`${path} must be an array`);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (typeof schema.minItems === 'number' && value.length < schema.minItems) {
|
|
34
|
+
errors.push(`${path} must contain at least ${schema.minItems} items`);
|
|
35
|
+
}
|
|
36
|
+
if (schema.items) {
|
|
37
|
+
value.forEach((item, index) => validateValue(item, schema.items, `${path}[${index}]`, errors));
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (schema.type === 'string') {
|
|
42
|
+
if (typeof value !== 'string') {
|
|
43
|
+
errors.push(`${path} must be a string`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (typeof schema.maxLength === 'number' && value.length > schema.maxLength) {
|
|
47
|
+
errors.push(`${path} exceeds maxLength ${schema.maxLength}`);
|
|
48
|
+
}
|
|
49
|
+
if (Array.isArray(schema.enum) && !schema.enum.includes(value)) {
|
|
50
|
+
errors.push(`${path} must be one of: ${schema.enum.join(', ')}`);
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (schema.type === 'number' && typeof value !== 'number') {
|
|
55
|
+
errors.push(`${path} must be a number`);
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (schema.type === 'boolean' && typeof value !== 'boolean') {
|
|
59
|
+
errors.push(`${path} must be a boolean`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function validateSchema(data, schema) {
|
|
63
|
+
const errors = [];
|
|
64
|
+
validateValue(data, schema, '$', errors);
|
|
65
|
+
return { valid: errors.length === 0, errors };
|
|
66
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PaymentAuthorization, PaymentRequirement } from '../types/x402';
|
|
2
|
+
type MessageSigner = {
|
|
3
|
+
address: string;
|
|
4
|
+
signMessage(message: string): Promise<string>;
|
|
5
|
+
};
|
|
6
|
+
export declare function encodePaymentRequirement(requirement: PaymentRequirement): string;
|
|
7
|
+
export declare function decodePaymentRequirement(encodedRequirement: string): PaymentRequirement;
|
|
8
|
+
export declare function createPaymentAuthorizationMessage(requirement: PaymentRequirement, requesterAddress: string): string;
|
|
9
|
+
export declare function signPaymentRequirement(wallet: MessageSigner, requirement: PaymentRequirement, network?: string): Promise<PaymentAuthorization>;
|
|
10
|
+
export declare function verifyPaymentAuthorization(requirement: PaymentRequirement, authorization: PaymentAuthorization): boolean;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.encodePaymentRequirement = encodePaymentRequirement;
|
|
4
|
+
exports.decodePaymentRequirement = decodePaymentRequirement;
|
|
5
|
+
exports.createPaymentAuthorizationMessage = createPaymentAuthorizationMessage;
|
|
6
|
+
exports.signPaymentRequirement = signPaymentRequirement;
|
|
7
|
+
exports.verifyPaymentAuthorization = verifyPaymentAuthorization;
|
|
8
|
+
const ethers_1 = require("ethers");
|
|
9
|
+
const x402_1 = require("../types/x402");
|
|
10
|
+
const hash_1 = require("../utils/hash");
|
|
11
|
+
function encodePaymentRequirement(requirement) {
|
|
12
|
+
return Buffer.from(JSON.stringify(x402_1.PaymentRequirementSchema.parse(requirement))).toString('base64');
|
|
13
|
+
}
|
|
14
|
+
function decodePaymentRequirement(encodedRequirement) {
|
|
15
|
+
return x402_1.PaymentRequirementSchema.parse(JSON.parse(Buffer.from(encodedRequirement, 'base64').toString('utf8')));
|
|
16
|
+
}
|
|
17
|
+
function createPaymentAuthorizationMessage(requirement, requesterAddress) {
|
|
18
|
+
const accept = requirement.accepts.find((option) => option.network === requirement.accepts[0]?.network) ?? requirement.accepts[0];
|
|
19
|
+
return (0, hash_1.hashValue)({
|
|
20
|
+
quoteId: requirement.quoteId,
|
|
21
|
+
network: accept.network,
|
|
22
|
+
amount: accept.amount,
|
|
23
|
+
token: accept.token,
|
|
24
|
+
payTo: accept.payTo,
|
|
25
|
+
memo: accept.memo,
|
|
26
|
+
expiresAt: requirement.expiresAt,
|
|
27
|
+
requesterAddress,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
async function signPaymentRequirement(wallet, requirement, network) {
|
|
31
|
+
const requesterAddress = (0, ethers_1.getAddress)(wallet.address);
|
|
32
|
+
const accept = requirement.accepts.find((option) => option.network === network) ?? requirement.accepts[0];
|
|
33
|
+
const message = (0, hash_1.hashValue)({
|
|
34
|
+
quoteId: requirement.quoteId,
|
|
35
|
+
network: accept.network,
|
|
36
|
+
amount: accept.amount,
|
|
37
|
+
token: accept.token,
|
|
38
|
+
payTo: accept.payTo,
|
|
39
|
+
memo: accept.memo,
|
|
40
|
+
expiresAt: requirement.expiresAt,
|
|
41
|
+
requesterAddress,
|
|
42
|
+
});
|
|
43
|
+
const signature = await wallet.signMessage(message);
|
|
44
|
+
return {
|
|
45
|
+
quoteId: requirement.quoteId,
|
|
46
|
+
network: accept.network,
|
|
47
|
+
amount: accept.amount,
|
|
48
|
+
token: accept.token,
|
|
49
|
+
payTo: accept.payTo,
|
|
50
|
+
memo: accept.memo,
|
|
51
|
+
expiresAt: requirement.expiresAt,
|
|
52
|
+
requesterAddress,
|
|
53
|
+
signature,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function verifyPaymentAuthorization(requirement, authorization) {
|
|
57
|
+
const message = (0, hash_1.hashValue)({
|
|
58
|
+
quoteId: requirement.quoteId,
|
|
59
|
+
network: authorization.network,
|
|
60
|
+
amount: authorization.amount,
|
|
61
|
+
token: authorization.token,
|
|
62
|
+
payTo: authorization.payTo,
|
|
63
|
+
memo: authorization.memo,
|
|
64
|
+
expiresAt: requirement.expiresAt,
|
|
65
|
+
requesterAddress: authorization.requesterAddress,
|
|
66
|
+
});
|
|
67
|
+
return (0, ethers_1.getAddress)((0, ethers_1.verifyMessage)(message, authorization.signature)) === (0, ethers_1.getAddress)(authorization.requesterAddress);
|
|
68
|
+
}
|