@hashgraphonline/standards-agent-kit 0.2.127 → 0.2.129
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/dist/cjs/standards-agent-kit.cjs +1 -1
- package/dist/cjs/standards-agent-kit.cjs.map +1 -1
- package/dist/cjs/tools/inscriber/InscribeFromBufferTool.d.ts +2 -10
- package/dist/cjs/tools/inscriber/InscribeHashinalTool.d.ts +62 -30
- package/dist/cjs/utils/content-resolver.d.ts +10 -0
- package/dist/cjs/utils/metadata-defaults.d.ts +18 -0
- package/dist/cjs/validation/content-ref-schemas.d.ts +9 -0
- package/dist/cjs/validation/hip412-schemas.d.ts +125 -0
- package/dist/es/standards-agent-kit.es36.js +11 -63
- package/dist/es/standards-agent-kit.es36.js.map +1 -1
- package/dist/es/standards-agent-kit.es37.js +159 -33
- package/dist/es/standards-agent-kit.es37.js.map +1 -1
- package/dist/es/standards-agent-kit.es44.js +57 -0
- package/dist/es/standards-agent-kit.es44.js.map +1 -0
- package/dist/es/standards-agent-kit.es45.js +6 -0
- package/dist/es/standards-agent-kit.es45.js.map +1 -0
- package/dist/es/standards-agent-kit.es46.js +43 -0
- package/dist/es/standards-agent-kit.es46.js.map +1 -0
- package/dist/es/standards-agent-kit.es47.js +15 -0
- package/dist/es/standards-agent-kit.es47.js.map +1 -0
- package/dist/es/tools/inscriber/InscribeFromBufferTool.d.ts +2 -10
- package/dist/es/tools/inscriber/InscribeHashinalTool.d.ts +62 -30
- package/dist/es/utils/content-resolver.d.ts +10 -0
- package/dist/es/utils/metadata-defaults.d.ts +18 -0
- package/dist/es/validation/content-ref-schemas.d.ts +9 -0
- package/dist/es/validation/hip412-schemas.d.ts +125 -0
- package/dist/umd/standards-agent-kit.umd.js +1 -1
- package/dist/umd/standards-agent-kit.umd.js.map +1 -1
- package/dist/umd/tools/inscriber/InscribeFromBufferTool.d.ts +2 -10
- package/dist/umd/tools/inscriber/InscribeHashinalTool.d.ts +62 -30
- package/dist/umd/utils/content-resolver.d.ts +10 -0
- package/dist/umd/utils/metadata-defaults.d.ts +18 -0
- package/dist/umd/validation/content-ref-schemas.d.ts +9 -0
- package/dist/umd/validation/hip412-schemas.d.ts +125 -0
- package/package.json +34 -31
- package/src/tools/inscriber/InscribeFromBufferTool.ts +30 -133
- package/src/tools/inscriber/InscribeHashinalTool.ts +271 -46
- package/src/utils/content-resolver.ts +87 -0
- package/src/utils/metadata-defaults.ts +25 -0
- package/src/validation/content-ref-schemas.ts +20 -0
- package/src/validation/hip412-schemas.ts +52 -0
|
@@ -1,57 +1,23 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { BaseInscriberQueryTool } from './base-inscriber-tools';
|
|
3
|
-
import { InscriptionOptions
|
|
3
|
+
import { InscriptionOptions } from '@hashgraphonline/standards-sdk';
|
|
4
4
|
import { CallbackManagerForToolRun } from '@langchain/core/callbacks/manager';
|
|
5
|
+
import { resolveContent } from '../../utils/content-resolver';
|
|
6
|
+
import { contentRefSchema } from '../../validation/content-ref-schemas';
|
|
5
7
|
import { loadConfig } from '../../config/ContentReferenceConfig';
|
|
6
8
|
|
|
7
9
|
const inscribeFromBufferSchema = z.object({
|
|
8
|
-
base64Data: z
|
|
9
|
-
.
|
|
10
|
-
|
|
11
|
-
.describe(
|
|
12
|
-
'The content to inscribe. Accept BOTH plain text AND base64. Also accepts content reference IDs in format "content-ref:[id]". When user says "inscribe it" or "inscribe the content", use the EXACT content from your previous message or from MCP tool results. DO NOT generate new content. DO NOT create repetitive text. Pass the actual search results or other retrieved content EXACTLY as you received it.'
|
|
13
|
-
),
|
|
14
|
-
fileName: z
|
|
15
|
-
.string()
|
|
16
|
-
.min(1, 'File name cannot be empty')
|
|
17
|
-
.describe('Name for the inscribed content. Required for all inscriptions.'),
|
|
10
|
+
base64Data: z.union([z.string(), contentRefSchema])
|
|
11
|
+
.describe('Content to inscribe as base64 data, plain text, or content reference'),
|
|
12
|
+
fileName: z.string().min(1).describe('Name for the inscribed content'),
|
|
18
13
|
mimeType: z.string().optional().describe('MIME type of the content'),
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
.record(z.unknown())
|
|
25
|
-
.optional()
|
|
26
|
-
.describe('Metadata to attach to the inscription'),
|
|
27
|
-
tags: z
|
|
28
|
-
.array(z.string())
|
|
29
|
-
.optional()
|
|
30
|
-
.describe('Tags to categorize the inscription'),
|
|
31
|
-
chunkSize: z
|
|
32
|
-
.number()
|
|
33
|
-
.int()
|
|
34
|
-
.positive()
|
|
35
|
-
.optional()
|
|
36
|
-
.describe('Chunk size for large files'),
|
|
37
|
-
waitForConfirmation: z
|
|
38
|
-
.boolean()
|
|
39
|
-
.optional()
|
|
40
|
-
.describe('Whether to wait for inscription confirmation'),
|
|
41
|
-
timeoutMs: z
|
|
42
|
-
.number()
|
|
43
|
-
.int()
|
|
44
|
-
.positive()
|
|
45
|
-
.optional()
|
|
46
|
-
.describe(
|
|
47
|
-
'Timeout in milliseconds for inscription (default: no timeout - waits until completion)'
|
|
48
|
-
),
|
|
14
|
+
metadata: z.record(z.unknown()).optional().describe('Metadata to attach'),
|
|
15
|
+
tags: z.array(z.string()).optional().describe('Tags to categorize the inscription'),
|
|
16
|
+
chunkSize: z.number().int().positive().optional().describe('Chunk size for large files'),
|
|
17
|
+
waitForConfirmation: z.boolean().optional().describe('Wait for inscription confirmation'),
|
|
18
|
+
timeoutMs: z.number().int().positive().optional().describe('Timeout in milliseconds'),
|
|
49
19
|
apiKey: z.string().optional().describe('API key for inscription service'),
|
|
50
|
-
quoteOnly: z
|
|
51
|
-
.boolean()
|
|
52
|
-
.optional()
|
|
53
|
-
.default(false)
|
|
54
|
-
.describe('If true, returns a cost quote instead of executing the inscription'),
|
|
20
|
+
quoteOnly: z.boolean().optional().default(false).describe('Return cost quote only'),
|
|
55
21
|
});
|
|
56
22
|
|
|
57
23
|
export class InscribeFromBufferTool extends BaseInscriberQueryTool<
|
|
@@ -59,7 +25,7 @@ export class InscribeFromBufferTool extends BaseInscriberQueryTool<
|
|
|
59
25
|
> {
|
|
60
26
|
name = 'inscribeFromBuffer';
|
|
61
27
|
description =
|
|
62
|
-
'Inscribe content that you have already retrieved or displayed. When user says "inscribe it" after you showed search results or other content, use THIS tool. The base64Data field accepts PLAIN TEXT (not just base64) and content reference IDs in format "content-ref:[id]". Pass the EXACT content from your previous response or MCP tool output. DO NOT generate new content or create repetitive text. Content references are automatically resolved to the original content for inscription. Set quoteOnly=true to get cost estimates without executing the inscription.';
|
|
28
|
+
'Inscribe content that you have already retrieved or displayed as standard file inscription (NOT for hashinal NFTs - use InscribeHashinalTool for NFTs). When user says "inscribe it" after you showed search results or other content, use THIS tool. The base64Data field accepts PLAIN TEXT (not just base64) and content reference IDs in format "content-ref:[id]". Pass the EXACT content from your previous response or MCP tool output. DO NOT generate new content or create repetitive text. Content references are automatically resolved to the original content for inscription. Set quoteOnly=true to get cost estimates without executing the inscription.';
|
|
63
29
|
|
|
64
30
|
private config = loadConfig();
|
|
65
31
|
|
|
@@ -73,7 +39,7 @@ export class InscribeFromBufferTool extends BaseInscriberQueryTool<
|
|
|
73
39
|
): Promise<unknown> {
|
|
74
40
|
this.validateInput(params);
|
|
75
41
|
|
|
76
|
-
const resolvedContent = await
|
|
42
|
+
const resolvedContent = await resolveContent(
|
|
77
43
|
params.base64Data,
|
|
78
44
|
params.mimeType,
|
|
79
45
|
params.fileName
|
|
@@ -86,11 +52,13 @@ export class InscribeFromBufferTool extends BaseInscriberQueryTool<
|
|
|
86
52
|
const resolvedFileName = resolvedContent.fileName || params.fileName;
|
|
87
53
|
|
|
88
54
|
const options: InscriptionOptions = {
|
|
89
|
-
mode:
|
|
55
|
+
mode: 'file',
|
|
90
56
|
metadata: params.metadata,
|
|
91
57
|
tags: params.tags,
|
|
92
58
|
chunkSize: params.chunkSize,
|
|
93
|
-
waitForConfirmation: params.quoteOnly
|
|
59
|
+
waitForConfirmation: params.quoteOnly
|
|
60
|
+
? false
|
|
61
|
+
: params.waitForConfirmation ?? true,
|
|
94
62
|
waitMaxAttempts: 10,
|
|
95
63
|
waitIntervalMs: 3000,
|
|
96
64
|
apiKey: params.apiKey,
|
|
@@ -113,7 +81,7 @@ export class InscribeFromBufferTool extends BaseInscriberQueryTool<
|
|
|
113
81
|
},
|
|
114
82
|
options
|
|
115
83
|
);
|
|
116
|
-
|
|
84
|
+
|
|
117
85
|
return {
|
|
118
86
|
success: true,
|
|
119
87
|
quote: {
|
|
@@ -126,11 +94,15 @@ export class InscribeFromBufferTool extends BaseInscriberQueryTool<
|
|
|
126
94
|
mimeType: resolvedMimeType,
|
|
127
95
|
sizeBytes: buffer.length,
|
|
128
96
|
},
|
|
129
|
-
message: `Estimated Quote for buffer content: ${resolvedFileName} (${(
|
|
97
|
+
message: `Estimated Quote for buffer content: ${resolvedFileName} (${(
|
|
98
|
+
buffer.length / 1024
|
|
99
|
+
).toFixed(2)} KB)\nTotal cost: ${quote.totalCostHbar} HBAR`,
|
|
130
100
|
};
|
|
131
101
|
} catch (error) {
|
|
132
102
|
const errorMessage =
|
|
133
|
-
error instanceof Error
|
|
103
|
+
error instanceof Error
|
|
104
|
+
? error.message
|
|
105
|
+
: 'Failed to generate inscription quote';
|
|
134
106
|
throw new Error(`Quote generation failed: ${errorMessage}`);
|
|
135
107
|
}
|
|
136
108
|
}
|
|
@@ -247,7 +219,8 @@ export class InscribeFromBufferTool extends BaseInscriberQueryTool<
|
|
|
247
219
|
options: InscriptionOptions
|
|
248
220
|
): string {
|
|
249
221
|
if (result.confirmed && !result.quote) {
|
|
250
|
-
const topicId =
|
|
222
|
+
const topicId =
|
|
223
|
+
result.inscription?.topic_id || (result.result as any).topicId;
|
|
251
224
|
const network = options.network || 'testnet';
|
|
252
225
|
const cdnUrl = topicId
|
|
253
226
|
? `https://kiloscribe.com/api/inscription-cdn/${topicId}?network=${network}`
|
|
@@ -260,88 +233,12 @@ export class InscribeFromBufferTool extends BaseInscriberQueryTool<
|
|
|
260
233
|
}
|
|
261
234
|
|
|
262
235
|
if (!result.quote && !result.confirmed) {
|
|
263
|
-
return `Successfully submitted inscription to the Hedera network!\n\nTransaction ID: ${
|
|
236
|
+
return `Successfully submitted inscription to the Hedera network!\n\nTransaction ID: ${
|
|
237
|
+
(result.result as any).transactionId
|
|
238
|
+
}\n\nThe inscription is processing and will be confirmed shortly.`;
|
|
264
239
|
}
|
|
265
240
|
|
|
266
241
|
return 'Inscription operation completed.';
|
|
267
242
|
}
|
|
268
243
|
|
|
269
|
-
private async resolveContent(
|
|
270
|
-
input: string,
|
|
271
|
-
providedMimeType?: string,
|
|
272
|
-
providedFileName?: string
|
|
273
|
-
): Promise<{
|
|
274
|
-
buffer: Buffer;
|
|
275
|
-
mimeType?: string;
|
|
276
|
-
fileName?: string;
|
|
277
|
-
wasReference?: boolean;
|
|
278
|
-
}> {
|
|
279
|
-
const trimmedInput = input.trim();
|
|
280
|
-
|
|
281
|
-
const resolver = this.getContentResolver() || ContentResolverRegistry.getResolver();
|
|
282
|
-
|
|
283
|
-
if (!resolver) {
|
|
284
|
-
return this.handleDirectContent(trimmedInput, providedMimeType, providedFileName);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
const referenceId = resolver.extractReferenceId(trimmedInput);
|
|
288
|
-
|
|
289
|
-
if (referenceId) {
|
|
290
|
-
try {
|
|
291
|
-
const resolution = await resolver.resolveReference(referenceId);
|
|
292
|
-
|
|
293
|
-
return {
|
|
294
|
-
buffer: resolution.content,
|
|
295
|
-
mimeType: resolution.metadata?.mimeType || providedMimeType,
|
|
296
|
-
fileName: resolution.metadata?.fileName || providedFileName,
|
|
297
|
-
wasReference: true,
|
|
298
|
-
};
|
|
299
|
-
} catch (error) {
|
|
300
|
-
const errorMsg =
|
|
301
|
-
error instanceof Error
|
|
302
|
-
? error.message
|
|
303
|
-
: 'Unknown error resolving reference';
|
|
304
|
-
throw new Error(`Reference resolution failed: ${errorMsg}`);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
return this.handleDirectContent(trimmedInput, providedMimeType, providedFileName);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
private handleDirectContent(
|
|
312
|
-
input: string,
|
|
313
|
-
providedMimeType?: string,
|
|
314
|
-
providedFileName?: string
|
|
315
|
-
): {
|
|
316
|
-
buffer: Buffer;
|
|
317
|
-
mimeType?: string;
|
|
318
|
-
fileName?: string;
|
|
319
|
-
wasReference?: boolean;
|
|
320
|
-
} {
|
|
321
|
-
const isValidBase64 = /^[A-Za-z0-9+/]*={0,2}$/.test(input);
|
|
322
|
-
|
|
323
|
-
if (isValidBase64) {
|
|
324
|
-
try {
|
|
325
|
-
const buffer = Buffer.from(input, 'base64');
|
|
326
|
-
return {
|
|
327
|
-
buffer,
|
|
328
|
-
mimeType: providedMimeType,
|
|
329
|
-
fileName: providedFileName,
|
|
330
|
-
wasReference: false,
|
|
331
|
-
};
|
|
332
|
-
} catch (error) {
|
|
333
|
-
throw new Error(
|
|
334
|
-
'Failed to decode base64 data. Please ensure the data is properly encoded.'
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
const buffer = Buffer.from(input, 'utf8');
|
|
340
|
-
return {
|
|
341
|
-
buffer,
|
|
342
|
-
mimeType: providedMimeType || 'text/plain',
|
|
343
|
-
fileName: providedFileName,
|
|
344
|
-
wasReference: false,
|
|
345
|
-
};
|
|
346
|
-
}
|
|
347
244
|
}
|
|
@@ -1,17 +1,66 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { BaseInscriberQueryTool } from './base-inscriber-tools';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
InscriptionOptions,
|
|
5
|
+
ContentResolverRegistry,
|
|
6
|
+
} from '@hashgraphonline/standards-sdk';
|
|
4
7
|
import { CallbackManagerForToolRun } from '@langchain/core/callbacks/manager';
|
|
8
|
+
import { validateHIP412Metadata } from '../../validation/hip412-schemas';
|
|
9
|
+
import { contentRefSchema } from '../../validation/content-ref-schemas';
|
|
10
|
+
import { generateDefaultMetadata } from '../../utils/metadata-defaults';
|
|
11
|
+
|
|
5
12
|
|
|
6
13
|
/**
|
|
7
14
|
* Schema for inscribing Hashinal NFT
|
|
8
15
|
*/
|
|
9
16
|
const inscribeHashinalSchema = z.object({
|
|
10
|
-
url: z
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
url: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe(
|
|
21
|
+
'The URL of the content to inscribe as Hashinal NFT (use this OR contentRef)'
|
|
22
|
+
),
|
|
23
|
+
contentRef: contentRefSchema
|
|
24
|
+
.optional()
|
|
25
|
+
.describe(
|
|
26
|
+
'Content reference ID in format "content-ref:[id]" for already stored content (use this OR url)'
|
|
27
|
+
),
|
|
28
|
+
base64Data: z
|
|
29
|
+
.string()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe(
|
|
32
|
+
'Base64 encoded content data (use this if neither url nor contentRef provided)'
|
|
33
|
+
),
|
|
34
|
+
fileName: z
|
|
35
|
+
.string()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe(
|
|
38
|
+
'File name for the content (required when using base64Data or contentRef)'
|
|
39
|
+
),
|
|
40
|
+
mimeType: z
|
|
41
|
+
.string()
|
|
42
|
+
.optional()
|
|
43
|
+
.describe('MIME type of the content (e.g., "image/png", "image/jpeg")'),
|
|
44
|
+
name: z
|
|
45
|
+
.string()
|
|
46
|
+
.optional()
|
|
47
|
+
.describe(
|
|
48
|
+
'Name of the Hashinal NFT (defaults to filename if not provided)'
|
|
49
|
+
),
|
|
50
|
+
creator: z
|
|
51
|
+
.string()
|
|
52
|
+
.optional()
|
|
53
|
+
.describe('Creator account ID or name (defaults to operator account)'),
|
|
54
|
+
description: z
|
|
55
|
+
.string()
|
|
56
|
+
.optional()
|
|
57
|
+
.describe(
|
|
58
|
+
'Description of the Hashinal NFT (auto-generated if not provided)'
|
|
59
|
+
),
|
|
60
|
+
type: z
|
|
61
|
+
.string()
|
|
62
|
+
.optional()
|
|
63
|
+
.describe('Type of NFT (auto-detected from MIME type if not provided)'),
|
|
15
64
|
attributes: z
|
|
16
65
|
.array(
|
|
17
66
|
z.object({
|
|
@@ -30,10 +79,14 @@ const inscribeHashinalSchema = z.object({
|
|
|
30
79
|
.url()
|
|
31
80
|
.optional()
|
|
32
81
|
.describe('URL to JSON metadata file'),
|
|
33
|
-
|
|
34
|
-
.
|
|
82
|
+
fileStandard: z
|
|
83
|
+
.enum(['1', '6'])
|
|
35
84
|
.optional()
|
|
36
|
-
.
|
|
85
|
+
.default('1')
|
|
86
|
+
.describe(
|
|
87
|
+
'HCS file standard: 1 for static Hashinals (HCS-5), 6 for dynamic Hashinals (HCS-6)'
|
|
88
|
+
),
|
|
89
|
+
tags: z.array(z.string()).optional().describe('Tags to categorize the NFT'),
|
|
37
90
|
chunkSize: z
|
|
38
91
|
.number()
|
|
39
92
|
.int()
|
|
@@ -49,25 +102,28 @@ const inscribeHashinalSchema = z.object({
|
|
|
49
102
|
.int()
|
|
50
103
|
.positive()
|
|
51
104
|
.optional()
|
|
52
|
-
.describe(
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
.describe('API key for inscription service'),
|
|
105
|
+
.describe(
|
|
106
|
+
'Timeout in milliseconds for inscription (default: no timeout - waits until completion)'
|
|
107
|
+
),
|
|
108
|
+
apiKey: z.string().optional().describe('API key for inscription service'),
|
|
57
109
|
quoteOnly: z
|
|
58
110
|
.boolean()
|
|
59
111
|
.optional()
|
|
60
112
|
.default(false)
|
|
61
|
-
.describe(
|
|
113
|
+
.describe(
|
|
114
|
+
'If true, returns a cost quote instead of executing the inscription'
|
|
115
|
+
),
|
|
62
116
|
});
|
|
63
117
|
|
|
64
|
-
|
|
65
118
|
/**
|
|
66
119
|
* Tool for inscribing Hashinal NFTs
|
|
67
120
|
*/
|
|
68
|
-
export class InscribeHashinalTool extends BaseInscriberQueryTool<
|
|
121
|
+
export class InscribeHashinalTool extends BaseInscriberQueryTool<
|
|
122
|
+
typeof inscribeHashinalSchema
|
|
123
|
+
> {
|
|
69
124
|
name = 'inscribeHashinal';
|
|
70
|
-
description =
|
|
125
|
+
description =
|
|
126
|
+
'STEP 1: Inscribe content as Hashinal NFT. This tool creates the inscription and returns metadataForMinting (HRL format). CRITICAL: You MUST use the metadataForMinting field from this tool output as the metadata parameter when calling mint NFT tools. DO NOT use the original content reference for minting. This tool only inscribes - call minting tools separately after this completes. Use fileStandard=6 for dynamic Hashinals (HCS-6) or fileStandard=1 for static Hashinals (HCS-5).';
|
|
71
127
|
|
|
72
128
|
get specificInputSchema() {
|
|
73
129
|
return inscribeHashinalSchema;
|
|
@@ -77,36 +133,90 @@ export class InscribeHashinalTool extends BaseInscriberQueryTool<typeof inscribe
|
|
|
77
133
|
params: z.infer<typeof inscribeHashinalSchema>,
|
|
78
134
|
_runManager?: CallbackManagerForToolRun
|
|
79
135
|
): Promise<unknown> {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
136
|
+
// Validate input - must have either url, contentRef, or base64Data
|
|
137
|
+
if (!params.url && !params.contentRef && !params.base64Data) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
'Must provide either url, contentRef, or base64Data for the Hashinal NFT content'
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const operatorAccount =
|
|
144
|
+
this.inscriberBuilder['hederaKit']?.client?.operatorAccountId?.toString() || '0.0.unknown';
|
|
145
|
+
|
|
146
|
+
const rawMetadata = {
|
|
147
|
+
...generateDefaultMetadata({
|
|
148
|
+
name: params.name,
|
|
149
|
+
creator: params.creator,
|
|
150
|
+
description: params.description,
|
|
151
|
+
type: params.type,
|
|
152
|
+
fileName: params.fileName,
|
|
153
|
+
mimeType: params.mimeType,
|
|
154
|
+
operatorAccount,
|
|
155
|
+
}),
|
|
85
156
|
attributes: params.attributes,
|
|
86
157
|
properties: params.properties,
|
|
87
158
|
};
|
|
88
159
|
|
|
160
|
+
// Validate metadata against HIP-412 standard
|
|
161
|
+
let validatedMetadata;
|
|
162
|
+
try {
|
|
163
|
+
validatedMetadata = validateHIP412Metadata(rawMetadata);
|
|
164
|
+
} catch (error) {
|
|
165
|
+
const errorMessage =
|
|
166
|
+
error instanceof Error ? error.message : String(error);
|
|
167
|
+
throw new Error(`Metadata validation error: ${errorMessage}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
89
170
|
const options: InscriptionOptions = {
|
|
90
171
|
mode: 'hashinal',
|
|
91
|
-
metadata,
|
|
172
|
+
metadata: validatedMetadata,
|
|
92
173
|
jsonFileURL: params.jsonFileURL,
|
|
174
|
+
fileStandard: params.fileStandard,
|
|
93
175
|
tags: params.tags,
|
|
94
176
|
chunkSize: params.chunkSize,
|
|
95
|
-
waitForConfirmation: params.quoteOnly
|
|
177
|
+
waitForConfirmation: params.quoteOnly
|
|
178
|
+
? false
|
|
179
|
+
: params.waitForConfirmation ?? true,
|
|
96
180
|
waitMaxAttempts: 10,
|
|
97
181
|
waitIntervalMs: 3000,
|
|
98
182
|
apiKey: params.apiKey,
|
|
99
|
-
network: this.inscriberBuilder['hederaKit'].client.network
|
|
183
|
+
network: this.inscriberBuilder['hederaKit'].client.network
|
|
184
|
+
.toString()
|
|
185
|
+
.includes('mainnet')
|
|
186
|
+
? 'mainnet'
|
|
187
|
+
: 'testnet',
|
|
100
188
|
quoteOnly: params.quoteOnly,
|
|
101
189
|
};
|
|
102
190
|
|
|
191
|
+
// Determine inscription data based on input type
|
|
192
|
+
let inscriptionData: any;
|
|
193
|
+
|
|
194
|
+
if (params.url) {
|
|
195
|
+
inscriptionData = { type: 'url', url: params.url };
|
|
196
|
+
} else if (params.contentRef || params.base64Data) {
|
|
197
|
+
// Handle content reference or base64 data
|
|
198
|
+
const inputData = params.contentRef || params.base64Data || '';
|
|
199
|
+
const { buffer, mimeType, fileName } = await this.resolveContent(
|
|
200
|
+
inputData,
|
|
201
|
+
params.mimeType,
|
|
202
|
+
params.fileName
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
inscriptionData = {
|
|
206
|
+
type: 'buffer' as const,
|
|
207
|
+
buffer,
|
|
208
|
+
fileName: fileName || params.fileName || 'hashinal-content',
|
|
209
|
+
mimeType: mimeType || params.mimeType,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
103
213
|
if (params.quoteOnly) {
|
|
104
214
|
try {
|
|
105
215
|
const quote = await this.generateInscriptionQuote(
|
|
106
|
-
|
|
216
|
+
inscriptionData,
|
|
107
217
|
options
|
|
108
218
|
);
|
|
109
|
-
|
|
219
|
+
|
|
110
220
|
return {
|
|
111
221
|
success: true,
|
|
112
222
|
quote: {
|
|
@@ -124,49 +234,164 @@ export class InscribeHashinalTool extends BaseInscriberQueryTool<typeof inscribe
|
|
|
124
234
|
};
|
|
125
235
|
} catch (error) {
|
|
126
236
|
const errorMessage =
|
|
127
|
-
error instanceof Error
|
|
237
|
+
error instanceof Error
|
|
238
|
+
? error.message
|
|
239
|
+
: 'Failed to generate inscription quote';
|
|
128
240
|
throw new Error(`Quote generation failed: ${errorMessage}`);
|
|
129
241
|
}
|
|
130
242
|
}
|
|
131
243
|
|
|
132
244
|
try {
|
|
133
245
|
let result: Awaited<ReturnType<typeof this.inscriberBuilder.inscribe>>;
|
|
134
|
-
|
|
246
|
+
|
|
135
247
|
if (params.timeoutMs) {
|
|
136
248
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
137
249
|
setTimeout(
|
|
138
|
-
() =>
|
|
250
|
+
() =>
|
|
251
|
+
reject(
|
|
252
|
+
new Error(`Inscription timed out after ${params.timeoutMs}ms`)
|
|
253
|
+
),
|
|
139
254
|
params.timeoutMs
|
|
140
255
|
);
|
|
141
256
|
});
|
|
142
257
|
|
|
143
258
|
result = await Promise.race([
|
|
144
|
-
this.inscriberBuilder.inscribe(
|
|
145
|
-
|
|
146
|
-
options
|
|
147
|
-
),
|
|
148
|
-
timeoutPromise
|
|
259
|
+
this.inscriberBuilder.inscribe(inscriptionData, options),
|
|
260
|
+
timeoutPromise,
|
|
149
261
|
]);
|
|
150
262
|
} else {
|
|
151
|
-
result = await this.inscriberBuilder.inscribe(
|
|
152
|
-
{ type: 'url', url: params.url },
|
|
153
|
-
options
|
|
154
|
-
);
|
|
263
|
+
result = await this.inscriberBuilder.inscribe(inscriptionData, options);
|
|
155
264
|
}
|
|
156
265
|
|
|
157
266
|
if (result.confirmed && !result.quote) {
|
|
158
|
-
const
|
|
267
|
+
const imageTopicId = result.inscription?.topic_id;
|
|
268
|
+
const jsonTopicId = result.inscription?.jsonTopicId;
|
|
159
269
|
const network = options.network || 'testnet';
|
|
160
|
-
|
|
161
|
-
|
|
270
|
+
|
|
271
|
+
const cdnUrl = jsonTopicId
|
|
272
|
+
? `https://kiloscribe.com/api/inscription-cdn/${jsonTopicId}?network=${network}`
|
|
273
|
+
: null;
|
|
274
|
+
|
|
275
|
+
const fileStandard = params.fileStandard || '1';
|
|
276
|
+
const hrl = jsonTopicId ? `hcs://${fileStandard}/${jsonTopicId}` : null;
|
|
277
|
+
const standardType = fileStandard === '6' ? 'Dynamic' : 'Static';
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
success: true,
|
|
281
|
+
transactionId: (result.result as any).transactionId,
|
|
282
|
+
imageTopicId: imageTopicId || 'N/A',
|
|
283
|
+
jsonTopicId: jsonTopicId || 'N/A',
|
|
284
|
+
metadataForMinting: hrl,
|
|
285
|
+
hcsStandard: `hcs://${fileStandard}`,
|
|
286
|
+
fileStandard: fileStandard,
|
|
287
|
+
standardType: standardType,
|
|
288
|
+
message: `STEP 1 COMPLETE: ${standardType} Hashinal inscription finished! NOW FOR STEP 2: Use EXACTLY this metadata for minting: "${hrl}". DO NOT use the original content reference.`,
|
|
289
|
+
cdnUrl: cdnUrl,
|
|
290
|
+
NEXT_STEP_INSTRUCTIONS: `Call mint NFT tool with metadata: ["${hrl}"]`,
|
|
291
|
+
WARNING:
|
|
292
|
+
'DO NOT use content-ref for minting - only use the metadataForMinting value above',
|
|
293
|
+
};
|
|
162
294
|
} else if (!result.quote && !result.confirmed) {
|
|
163
|
-
return `Successfully submitted Hashinal NFT inscription to the Hedera network!\n\nTransaction ID: ${
|
|
295
|
+
return `Successfully submitted Hashinal NFT inscription to the Hedera network!\n\nTransaction ID: ${
|
|
296
|
+
(result.result as any).transactionId
|
|
297
|
+
}\n\nThe inscription is processing and will be confirmed shortly.`;
|
|
164
298
|
} else {
|
|
165
299
|
return 'Inscription operation completed.';
|
|
166
300
|
}
|
|
167
301
|
} catch (error) {
|
|
168
|
-
const errorMessage =
|
|
302
|
+
const errorMessage =
|
|
303
|
+
error instanceof Error
|
|
304
|
+
? error.message
|
|
305
|
+
: 'Failed to inscribe Hashinal NFT';
|
|
169
306
|
throw new Error(`Inscription failed: ${errorMessage}`);
|
|
170
307
|
}
|
|
171
308
|
}
|
|
172
|
-
|
|
309
|
+
|
|
310
|
+
private async resolveContent(
|
|
311
|
+
input: string,
|
|
312
|
+
providedMimeType?: string,
|
|
313
|
+
providedFileName?: string
|
|
314
|
+
): Promise<{
|
|
315
|
+
buffer: Buffer;
|
|
316
|
+
mimeType?: string;
|
|
317
|
+
fileName?: string;
|
|
318
|
+
wasReference?: boolean;
|
|
319
|
+
}> {
|
|
320
|
+
const trimmedInput = input.trim();
|
|
321
|
+
|
|
322
|
+
const resolver =
|
|
323
|
+
this.getContentResolver() || ContentResolverRegistry.getResolver();
|
|
324
|
+
|
|
325
|
+
if (!resolver) {
|
|
326
|
+
return this.handleDirectContent(
|
|
327
|
+
trimmedInput,
|
|
328
|
+
providedMimeType,
|
|
329
|
+
providedFileName
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const referenceId = resolver.extractReferenceId(trimmedInput);
|
|
334
|
+
|
|
335
|
+
if (referenceId) {
|
|
336
|
+
try {
|
|
337
|
+
const resolution = await resolver.resolveReference(referenceId);
|
|
338
|
+
|
|
339
|
+
return {
|
|
340
|
+
buffer: resolution.content,
|
|
341
|
+
mimeType: resolution.metadata?.mimeType || providedMimeType,
|
|
342
|
+
fileName: resolution.metadata?.fileName || providedFileName,
|
|
343
|
+
wasReference: true,
|
|
344
|
+
};
|
|
345
|
+
} catch (error) {
|
|
346
|
+
const errorMsg =
|
|
347
|
+
error instanceof Error
|
|
348
|
+
? error.message
|
|
349
|
+
: 'Unknown error resolving reference';
|
|
350
|
+
throw new Error(`Reference resolution failed: ${errorMsg}`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return this.handleDirectContent(
|
|
355
|
+
trimmedInput,
|
|
356
|
+
providedMimeType,
|
|
357
|
+
providedFileName
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
private handleDirectContent(
|
|
362
|
+
input: string,
|
|
363
|
+
providedMimeType?: string,
|
|
364
|
+
providedFileName?: string
|
|
365
|
+
): {
|
|
366
|
+
buffer: Buffer;
|
|
367
|
+
mimeType?: string;
|
|
368
|
+
fileName?: string;
|
|
369
|
+
wasReference?: boolean;
|
|
370
|
+
} {
|
|
371
|
+
const isValidBase64 = /^[A-Za-z0-9+/]*={0,2}$/.test(input);
|
|
372
|
+
|
|
373
|
+
if (isValidBase64) {
|
|
374
|
+
try {
|
|
375
|
+
const buffer = Buffer.from(input, 'base64');
|
|
376
|
+
return {
|
|
377
|
+
buffer,
|
|
378
|
+
mimeType: providedMimeType,
|
|
379
|
+
fileName: providedFileName,
|
|
380
|
+
wasReference: false,
|
|
381
|
+
};
|
|
382
|
+
} catch (error) {
|
|
383
|
+
throw new Error(
|
|
384
|
+
'Failed to decode base64 data. Please ensure the data is properly encoded.'
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const buffer = Buffer.from(input, 'utf8');
|
|
390
|
+
return {
|
|
391
|
+
buffer,
|
|
392
|
+
mimeType: providedMimeType || 'text/plain',
|
|
393
|
+
fileName: providedFileName,
|
|
394
|
+
wasReference: false,
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
}
|