@ckbfs/api 1.2.3 → 1.2.5

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.
@@ -1,8 +1,42 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.IdentifierType = void 0;
6
40
  exports.readFile = readFile;
7
41
  exports.readFileAsText = readFileAsText;
8
42
  exports.readFileAsUint8Array = readFileAsUint8Array;
@@ -12,6 +46,18 @@ exports.splitFileIntoChunks = splitFileIntoChunks;
12
46
  exports.combineChunksToFile = combineChunksToFile;
13
47
  exports.getFileContentFromChain = getFileContentFromChain;
14
48
  exports.saveFileFromChain = saveFileFromChain;
49
+ exports.decodeWitnessContent = decodeWitnessContent;
50
+ exports.decodeMultipleWitnessContents = decodeMultipleWitnessContents;
51
+ exports.extractFileFromWitnesses = extractFileFromWitnesses;
52
+ exports.decodeFileFromWitnessData = decodeFileFromWitnessData;
53
+ exports.saveFileFromWitnessData = saveFileFromWitnessData;
54
+ exports.parseIdentifier = parseIdentifier;
55
+ exports.getFileContentFromChainByIdentifier = getFileContentFromChainByIdentifier;
56
+ exports.getFileContentFromChainByTypeId = getFileContentFromChainByTypeId;
57
+ exports.saveFileFromChainByIdentifier = saveFileFromChainByIdentifier;
58
+ exports.saveFileFromChainByTypeId = saveFileFromChainByTypeId;
59
+ exports.decodeFileFromChainByIdentifier = decodeFileFromChainByIdentifier;
60
+ exports.decodeFileFromChainByTypeId = decodeFileFromChainByTypeId;
15
61
  const fs_1 = __importDefault(require("fs"));
16
62
  const path_1 = __importDefault(require("path"));
17
63
  /**
@@ -31,7 +77,7 @@ function readFile(filePath) {
31
77
  * @returns String containing the file contents
32
78
  */
33
79
  function readFileAsText(filePath) {
34
- return fs_1.default.readFileSync(filePath, 'utf-8');
80
+ return fs_1.default.readFileSync(filePath, "utf-8");
35
81
  }
36
82
  /**
37
83
  * Reads a file as Uint8Array from the file system
@@ -63,27 +109,27 @@ function writeFile(filePath, data) {
63
109
  function getContentType(filePath) {
64
110
  const extension = path_1.default.extname(filePath).toLowerCase();
65
111
  const mimeTypes = {
66
- '.txt': 'text/plain',
67
- '.html': 'text/html',
68
- '.htm': 'text/html',
69
- '.css': 'text/css',
70
- '.js': 'application/javascript',
71
- '.json': 'application/json',
72
- '.jpg': 'image/jpeg',
73
- '.jpeg': 'image/jpeg',
74
- '.png': 'image/png',
75
- '.gif': 'image/gif',
76
- '.svg': 'image/svg+xml',
77
- '.pdf': 'application/pdf',
78
- '.mp3': 'audio/mpeg',
79
- '.mp4': 'video/mp4',
80
- '.wav': 'audio/wav',
81
- '.xml': 'application/xml',
82
- '.zip': 'application/zip',
83
- '.md': 'text/markdown',
84
- '.markdown': 'text/markdown',
112
+ ".txt": "text/plain",
113
+ ".html": "text/html",
114
+ ".htm": "text/html",
115
+ ".css": "text/css",
116
+ ".js": "application/javascript",
117
+ ".json": "application/json",
118
+ ".jpg": "image/jpeg",
119
+ ".jpeg": "image/jpeg",
120
+ ".png": "image/png",
121
+ ".gif": "image/gif",
122
+ ".svg": "image/svg+xml",
123
+ ".pdf": "application/pdf",
124
+ ".mp3": "audio/mpeg",
125
+ ".mp4": "video/mp4",
126
+ ".wav": "audio/wav",
127
+ ".xml": "application/xml",
128
+ ".zip": "application/zip",
129
+ ".md": "text/markdown",
130
+ ".markdown": "text/markdown",
85
131
  };
86
- return mimeTypes[extension] || 'application/octet-stream';
132
+ return mimeTypes[extension] || "application/octet-stream";
87
133
  }
88
134
  /**
89
135
  * Splits a file into chunks of a specific size
@@ -105,7 +151,7 @@ function splitFileIntoChunks(filePath, chunkSize) {
105
151
  * @param outputPath The path to write the combined file to
106
152
  */
107
153
  function combineChunksToFile(chunks, outputPath) {
108
- const combinedBuffer = Buffer.concat(chunks.map(chunk => Buffer.from(chunk)));
154
+ const combinedBuffer = Buffer.concat(chunks.map((chunk) => Buffer.from(chunk)));
109
155
  writeFile(outputPath, combinedBuffer);
110
156
  }
111
157
  /**
@@ -115,12 +161,12 @@ function combineChunksToFile(chunks, outputPath) {
115
161
  */
116
162
  function safelyDecode(buffer) {
117
163
  if (!buffer)
118
- return '[Unknown]';
164
+ return "[Unknown]";
119
165
  try {
120
166
  if (buffer instanceof Uint8Array) {
121
167
  return new TextDecoder().decode(buffer);
122
168
  }
123
- else if (typeof buffer === 'string') {
169
+ else if (typeof buffer === "string") {
124
170
  return buffer;
125
171
  }
126
172
  else {
@@ -128,7 +174,7 @@ function safelyDecode(buffer) {
128
174
  }
129
175
  }
130
176
  catch (e) {
131
- return '[Decode Error]';
177
+ return "[Decode Error]";
132
178
  }
133
179
  }
134
180
  /**
@@ -151,7 +197,8 @@ async function getFileContentFromChain(client, outPoint, ckbfsData) {
151
197
  throw new Error(`Transaction ${currentOutPoint.txHash} not found`);
152
198
  }
153
199
  // Get content from witnesses
154
- const indexes = currentData.indexes || (currentData.index !== undefined ? [currentData.index] : []);
200
+ const indexes = currentData.indexes ||
201
+ (currentData.index !== undefined ? [currentData.index] : []);
155
202
  if (indexes.length > 0) {
156
203
  // Get content from each witness index
157
204
  for (const idx of indexes) {
@@ -160,9 +207,9 @@ async function getFileContentFromChain(client, outPoint, ckbfsData) {
160
207
  continue;
161
208
  }
162
209
  const witnessHex = tx.transaction.witnesses[idx];
163
- const witness = Buffer.from(witnessHex.slice(2), 'hex'); // Remove 0x prefix
210
+ const witness = Buffer.from(witnessHex.slice(2), "hex"); // Remove 0x prefix
164
211
  // Extract content (skip CKBFS header + version byte)
165
- if (witness.length >= 6 && witness.slice(0, 5).toString() === 'CKBFS') {
212
+ if (witness.length >= 6 && witness.slice(0, 5).toString() === "CKBFS") {
166
213
  const content = witness.slice(6);
167
214
  contentPieces.unshift(content); // Add to beginning of array (we're going backwards)
168
215
  }
@@ -183,7 +230,8 @@ async function getFileContentFromChain(client, outPoint, ckbfsData) {
183
230
  continue;
184
231
  }
185
232
  // Get content from backlink witnesses
186
- const backIndexes = backlink.indexes || (backlink.index !== undefined ? [backlink.index] : []);
233
+ const backIndexes = backlink.indexes ||
234
+ (backlink.index !== undefined ? [backlink.index] : []);
187
235
  if (backIndexes.length > 0) {
188
236
  // Get content from each witness index
189
237
  for (const idx of backIndexes) {
@@ -192,9 +240,10 @@ async function getFileContentFromChain(client, outPoint, ckbfsData) {
192
240
  continue;
193
241
  }
194
242
  const witnessHex = backTx.transaction.witnesses[idx];
195
- const witness = Buffer.from(witnessHex.slice(2), 'hex'); // Remove 0x prefix
243
+ const witness = Buffer.from(witnessHex.slice(2), "hex"); // Remove 0x prefix
196
244
  // Extract content (skip CKBFS header + version byte)
197
- if (witness.length >= 6 && witness.slice(0, 5).toString() === 'CKBFS') {
245
+ if (witness.length >= 6 &&
246
+ witness.slice(0, 5).toString() === "CKBFS") {
198
247
  const content = witness.slice(6);
199
248
  contentPieces.unshift(content); // Add to beginning of array (we're going backwards)
200
249
  }
@@ -231,3 +280,522 @@ function saveFileFromChain(content, ckbfsData, outputPath) {
231
280
  console.log(`Size: ${content.length} bytes`);
232
281
  return filePath;
233
282
  }
283
+ /**
284
+ * Decodes content from a single CKBFS witness
285
+ * @param witnessHex The witness data in hex format (with or without 0x prefix)
286
+ * @returns Object containing the decoded content and metadata, or null if not a valid CKBFS witness
287
+ */
288
+ function decodeWitnessContent(witnessHex) {
289
+ try {
290
+ // Remove 0x prefix if present
291
+ const hexData = witnessHex.startsWith("0x")
292
+ ? witnessHex.slice(2)
293
+ : witnessHex;
294
+ const witness = Buffer.from(hexData, "hex");
295
+ // Check if it's a valid CKBFS witness
296
+ if (witness.length < 6) {
297
+ return null;
298
+ }
299
+ // Check CKBFS header
300
+ const header = witness.slice(0, 5).toString();
301
+ if (header !== "CKBFS") {
302
+ return null;
303
+ }
304
+ // Extract content (skip CKBFS header + version byte)
305
+ const content = witness.slice(6);
306
+ return {
307
+ content,
308
+ isValid: true,
309
+ };
310
+ }
311
+ catch (error) {
312
+ console.warn("Error decoding witness content:", error);
313
+ return null;
314
+ }
315
+ }
316
+ /**
317
+ * Decodes and combines content from multiple CKBFS witnesses
318
+ * @param witnessHexArray Array of witness data in hex format
319
+ * @param preserveOrder Whether to preserve the order of witnesses (default: true)
320
+ * @returns Combined content from all valid CKBFS witnesses
321
+ */
322
+ function decodeMultipleWitnessContents(witnessHexArray, preserveOrder = true) {
323
+ const contentPieces = [];
324
+ for (let i = 0; i < witnessHexArray.length; i++) {
325
+ const witnessHex = witnessHexArray[i];
326
+ const decoded = decodeWitnessContent(witnessHex);
327
+ if (decoded && decoded.isValid) {
328
+ if (preserveOrder) {
329
+ contentPieces.push(decoded.content);
330
+ }
331
+ else {
332
+ contentPieces.unshift(decoded.content);
333
+ }
334
+ }
335
+ else {
336
+ console.warn(`Witness at index ${i} is not a valid CKBFS witness`);
337
+ }
338
+ }
339
+ return Buffer.concat(contentPieces);
340
+ }
341
+ /**
342
+ * Extracts complete file content from witnesses using specified indexes
343
+ * @param witnesses Array of all witnesses from a transaction
344
+ * @param indexes Array of witness indexes that contain CKBFS content
345
+ * @returns Combined content from the specified witness indexes
346
+ */
347
+ function extractFileFromWitnesses(witnesses, indexes) {
348
+ const relevantWitnesses = [];
349
+ for (const idx of indexes) {
350
+ if (idx >= witnesses.length) {
351
+ console.warn(`Witness index ${idx} out of range (total witnesses: ${witnesses.length})`);
352
+ continue;
353
+ }
354
+ relevantWitnesses.push(witnesses[idx]);
355
+ }
356
+ return decodeMultipleWitnessContents(relevantWitnesses, true);
357
+ }
358
+ /**
359
+ * Decodes file content directly from witness data without blockchain queries
360
+ * @param witnessData Object containing witness information
361
+ * @returns Object containing the decoded file content and metadata
362
+ */
363
+ function decodeFileFromWitnessData(witnessData) {
364
+ const { witnesses, indexes, filename, contentType } = witnessData;
365
+ // Normalize indexes to array
366
+ const indexArray = Array.isArray(indexes) ? indexes : [indexes];
367
+ // Extract content from witnesses
368
+ const content = extractFileFromWitnesses(witnesses, indexArray);
369
+ return {
370
+ content,
371
+ filename,
372
+ contentType,
373
+ size: content.length,
374
+ };
375
+ }
376
+ /**
377
+ * Saves decoded file content directly from witness data
378
+ * @param witnessData Object containing witness information
379
+ * @param outputPath Optional path to save the file
380
+ * @returns The path where the file was saved
381
+ */
382
+ function saveFileFromWitnessData(witnessData, outputPath) {
383
+ const decoded = decodeFileFromWitnessData(witnessData);
384
+ // Determine output path
385
+ const filename = decoded.filename || "decoded_file";
386
+ const filePath = outputPath || filename;
387
+ // Ensure directory exists
388
+ const directory = path_1.default.dirname(filePath);
389
+ if (!fs_1.default.existsSync(directory)) {
390
+ fs_1.default.mkdirSync(directory, { recursive: true });
391
+ }
392
+ // Write file
393
+ fs_1.default.writeFileSync(filePath, decoded.content);
394
+ console.log(`File saved to: ${filePath}`);
395
+ console.log(`Size: ${decoded.size} bytes`);
396
+ console.log(`Content type: ${decoded.contentType || "unknown"}`);
397
+ return filePath;
398
+ }
399
+ /**
400
+ * Identifier types for CKBFS cells
401
+ */
402
+ var IdentifierType;
403
+ (function (IdentifierType) {
404
+ IdentifierType["TypeID"] = "typeId";
405
+ IdentifierType["OutPoint"] = "outPoint";
406
+ IdentifierType["Unknown"] = "unknown";
407
+ })(IdentifierType || (exports.IdentifierType = IdentifierType = {}));
408
+ /**
409
+ * Detects and parses different CKBFS identifier formats
410
+ * @param identifier The identifier string to parse
411
+ * @returns Parsed identifier information
412
+ */
413
+ function parseIdentifier(identifier) {
414
+ const trimmed = identifier.trim();
415
+ // Type 1: Pure TypeID hex string (with or without 0x prefix)
416
+ if (trimmed.match(/^(0x)?[a-fA-F0-9]{64}$/)) {
417
+ const typeId = trimmed.startsWith("0x") ? trimmed : `0x${trimmed}`;
418
+ return {
419
+ type: IdentifierType.TypeID,
420
+ typeId,
421
+ original: identifier,
422
+ };
423
+ }
424
+ // Type 2: CKBFS URI with TypeID
425
+ if (trimmed.startsWith("ckbfs://")) {
426
+ const content = trimmed.slice(8); // Remove "ckbfs://"
427
+ // Check if it's TypeID format (64 hex characters)
428
+ if (content.match(/^[a-fA-F0-9]{64}$/)) {
429
+ return {
430
+ type: IdentifierType.TypeID,
431
+ typeId: `0x${content}`,
432
+ original: identifier,
433
+ };
434
+ }
435
+ // Type 3: CKBFS URI with transaction hash and index (txhash + 'i' + index)
436
+ const outPointMatch = content.match(/^([a-fA-F0-9]{64})i(\d+)$/);
437
+ if (outPointMatch) {
438
+ const [, txHash, indexStr] = outPointMatch;
439
+ return {
440
+ type: IdentifierType.OutPoint,
441
+ txHash: `0x${txHash}`,
442
+ index: parseInt(indexStr, 10),
443
+ original: identifier,
444
+ };
445
+ }
446
+ }
447
+ // Unknown format
448
+ return {
449
+ type: IdentifierType.Unknown,
450
+ original: identifier,
451
+ };
452
+ }
453
+ /**
454
+ * Resolves a CKBFS cell using any supported identifier format
455
+ * @param client The CKB client to use for blockchain queries
456
+ * @param identifier The identifier (TypeID, CKBFS URI, or outPoint URI)
457
+ * @param options Optional configuration for network, version, and useTypeID
458
+ * @returns Promise resolving to the found cell and transaction info, or null if not found
459
+ */
460
+ async function resolveCKBFSCell(client, identifier, options = {}) {
461
+ const { network = "testnet", version = "20241025.db973a8e8032", useTypeID = false, } = options;
462
+ const parsedId = parseIdentifier(identifier);
463
+ try {
464
+ if (parsedId.type === IdentifierType.TypeID && parsedId.typeId) {
465
+ // Use existing TypeID resolution logic
466
+ const cellInfo = await findCKBFSCellByTypeId(client, parsedId.typeId, network, version, useTypeID);
467
+ if (cellInfo) {
468
+ return {
469
+ ...cellInfo,
470
+ parsedId,
471
+ };
472
+ }
473
+ }
474
+ else if (parsedId.type === IdentifierType.OutPoint &&
475
+ parsedId.txHash &&
476
+ parsedId.index !== undefined) {
477
+ // Resolve using transaction hash and index
478
+ const txWithStatus = await client.getTransaction(parsedId.txHash);
479
+ if (!txWithStatus || !txWithStatus.transaction) {
480
+ console.warn(`Transaction ${parsedId.txHash} not found`);
481
+ return null;
482
+ }
483
+ // Import Transaction class dynamically
484
+ const { Transaction } = await Promise.resolve().then(() => __importStar(require("@ckb-ccc/core")));
485
+ const tx = Transaction.from(txWithStatus.transaction);
486
+ // Check if the index is valid
487
+ if (parsedId.index >= tx.outputs.length) {
488
+ console.warn(`Output index ${parsedId.index} out of range for transaction ${parsedId.txHash}`);
489
+ return null;
490
+ }
491
+ const output = tx.outputs[parsedId.index];
492
+ // Verify it's a CKBFS cell by checking if it has a type script
493
+ if (!output.type) {
494
+ console.warn(`Output at index ${parsedId.index} is not a CKBFS cell (no type script)`);
495
+ return null;
496
+ }
497
+ // Create a mock cell object similar to what findSingletonCellByType returns
498
+ const cell = {
499
+ outPoint: {
500
+ txHash: parsedId.txHash,
501
+ index: parsedId.index,
502
+ },
503
+ output,
504
+ };
505
+ return {
506
+ cell,
507
+ transaction: txWithStatus.transaction,
508
+ outPoint: {
509
+ txHash: parsedId.txHash,
510
+ index: parsedId.index,
511
+ },
512
+ parsedId,
513
+ };
514
+ }
515
+ console.warn(`Unable to resolve identifier: ${identifier} (type: ${parsedId.type})`);
516
+ return null;
517
+ }
518
+ catch (error) {
519
+ console.error(`Error resolving CKBFS cell for identifier ${identifier}:`, error);
520
+ return null;
521
+ }
522
+ }
523
+ /**
524
+ * Finds a CKBFS cell by TypeID
525
+ * @param client The CKB client to use for blockchain queries
526
+ * @param typeId The TypeID (args) of the CKBFS cell to find
527
+ * @param network The network type (mainnet or testnet)
528
+ * @param version The protocol version to use
529
+ * @param useTypeID Whether to use type ID instead of code hash for script matching
530
+ * @returns Promise resolving to the found cell and transaction info, or null if not found
531
+ */
532
+ async function findCKBFSCellByTypeId(client, typeId, network = "testnet", version = "20241025.db973a8e8032", useTypeID = false) {
533
+ try {
534
+ // Import constants dynamically to avoid circular dependencies
535
+ const { getCKBFSScriptConfig, NetworkType, ProtocolVersion } = await Promise.resolve().then(() => __importStar(require("./constants")));
536
+ // Get CKBFS script config
537
+ const networkType = network === "mainnet" ? NetworkType.Mainnet : NetworkType.Testnet;
538
+ const protocolVersion = version === "20240906.ce6724722cf6"
539
+ ? ProtocolVersion.V1
540
+ : ProtocolVersion.V2;
541
+ const config = getCKBFSScriptConfig(networkType, protocolVersion, useTypeID);
542
+ // Create the script to search for
543
+ const script = {
544
+ codeHash: config.codeHash,
545
+ hashType: config.hashType,
546
+ args: typeId.startsWith("0x") ? typeId : `0x${typeId}`,
547
+ };
548
+ // Find the cell by type script
549
+ const cell = await client.findSingletonCellByType(script, true);
550
+ if (!cell) {
551
+ return null;
552
+ }
553
+ // Get the transaction that contains this cell
554
+ const txHash = cell.outPoint.txHash;
555
+ const txWithStatus = await client.getTransaction(txHash);
556
+ if (!txWithStatus || !txWithStatus.transaction) {
557
+ throw new Error(`Transaction ${txHash} not found`);
558
+ }
559
+ return {
560
+ cell,
561
+ transaction: txWithStatus.transaction,
562
+ outPoint: {
563
+ txHash: cell.outPoint.txHash,
564
+ index: cell.outPoint.index,
565
+ },
566
+ };
567
+ }
568
+ catch (error) {
569
+ console.warn(`Error finding CKBFS cell by TypeID ${typeId}:`, error);
570
+ return null;
571
+ }
572
+ }
573
+ /**
574
+ * Retrieves complete file content from the blockchain using any supported identifier
575
+ * @param client The CKB client to use for blockchain queries
576
+ * @param identifier The identifier (TypeID hex, CKBFS TypeID URI, or CKBFS outPoint URI)
577
+ * @param options Optional configuration for network, version, and useTypeID
578
+ * @returns Promise resolving to the complete file content and metadata
579
+ */
580
+ async function getFileContentFromChainByIdentifier(client, identifier, options = {}) {
581
+ const { network = "testnet", version = "20241025.db973a8e8032", useTypeID = false, } = options;
582
+ try {
583
+ // Resolve the CKBFS cell using any supported identifier format
584
+ const cellInfo = await resolveCKBFSCell(client, identifier, {
585
+ network,
586
+ version,
587
+ useTypeID,
588
+ });
589
+ if (!cellInfo) {
590
+ console.warn(`CKBFS cell with identifier ${identifier} not found`);
591
+ return null;
592
+ }
593
+ const { cell, transaction, outPoint, parsedId } = cellInfo;
594
+ // Import Transaction class dynamically
595
+ const { Transaction } = await Promise.resolve().then(() => __importStar(require("@ckb-ccc/core")));
596
+ const tx = Transaction.from(transaction);
597
+ // Get output data from the cell
598
+ const outputIndex = outPoint.index;
599
+ const outputData = tx.outputsData[outputIndex];
600
+ if (!outputData) {
601
+ throw new Error(`Output data not found for cell at index ${outputIndex}`);
602
+ }
603
+ // Import required modules dynamically
604
+ const { ccc } = await Promise.resolve().then(() => __importStar(require("@ckb-ccc/core")));
605
+ const { CKBFSData } = await Promise.resolve().then(() => __importStar(require("./molecule")));
606
+ const { ProtocolVersion } = await Promise.resolve().then(() => __importStar(require("./constants")));
607
+ // Parse the output data
608
+ const rawData = outputData.startsWith("0x")
609
+ ? ccc.bytesFrom(outputData.slice(2), "hex")
610
+ : Buffer.from(outputData, "hex");
611
+ // Try to unpack CKBFS data with both protocol versions
612
+ let ckbfsData;
613
+ let protocolVersion = version;
614
+ try {
615
+ ckbfsData = CKBFSData.unpack(rawData, ProtocolVersion.V2);
616
+ }
617
+ catch (error) {
618
+ try {
619
+ ckbfsData = CKBFSData.unpack(rawData, ProtocolVersion.V1);
620
+ protocolVersion = "20240906.ce6724722cf6";
621
+ }
622
+ catch (v1Error) {
623
+ throw new Error(`Failed to unpack CKBFS data with both versions: V2(${error}), V1(${v1Error})`);
624
+ }
625
+ }
626
+ console.log(`Found CKBFS file: ${ckbfsData.filename}`);
627
+ console.log(`Content type: ${ckbfsData.contentType}`);
628
+ console.log(`Protocol version: ${protocolVersion}`);
629
+ // Use existing function to get complete file content
630
+ const content = await getFileContentFromChain(client, outPoint, ckbfsData);
631
+ return {
632
+ content,
633
+ filename: ckbfsData.filename,
634
+ contentType: ckbfsData.contentType,
635
+ checksum: ckbfsData.checksum,
636
+ size: content.length,
637
+ backLinks: ckbfsData.backLinks || [],
638
+ parsedId,
639
+ };
640
+ }
641
+ catch (error) {
642
+ console.error(`Error retrieving file by identifier ${identifier}:`, error);
643
+ throw error;
644
+ }
645
+ }
646
+ /**
647
+ * Retrieves complete file content from the blockchain using TypeID (legacy function)
648
+ * @param client The CKB client to use for blockchain queries
649
+ * @param typeId The TypeID (args) of the CKBFS cell
650
+ * @param options Optional configuration for network, version, and useTypeID
651
+ * @returns Promise resolving to the complete file content and metadata
652
+ */
653
+ async function getFileContentFromChainByTypeId(client, typeId, options = {}) {
654
+ const result = await getFileContentFromChainByIdentifier(client, typeId, options);
655
+ if (result) {
656
+ const { parsedId, ...fileData } = result;
657
+ return fileData;
658
+ }
659
+ return null;
660
+ }
661
+ /**
662
+ * Saves file content retrieved from blockchain by identifier to disk
663
+ * @param client The CKB client to use for blockchain queries
664
+ * @param identifier The identifier (TypeID hex, CKBFS TypeID URI, or CKBFS outPoint URI)
665
+ * @param outputPath Optional path to save the file (defaults to filename from CKBFS data)
666
+ * @param options Optional configuration for network, version, and useTypeID
667
+ * @returns Promise resolving to the path where the file was saved, or null if file not found
668
+ */
669
+ async function saveFileFromChainByIdentifier(client, identifier, outputPath, options = {}) {
670
+ try {
671
+ // Get file content by identifier
672
+ const fileData = await getFileContentFromChainByIdentifier(client, identifier, options);
673
+ if (!fileData) {
674
+ console.warn(`File with identifier ${identifier} not found`);
675
+ return null;
676
+ }
677
+ // Determine output path
678
+ const filePath = outputPath || fileData.filename;
679
+ // Ensure directory exists
680
+ const directory = path_1.default.dirname(filePath);
681
+ if (!fs_1.default.existsSync(directory)) {
682
+ fs_1.default.mkdirSync(directory, { recursive: true });
683
+ }
684
+ // Write file
685
+ fs_1.default.writeFileSync(filePath, fileData.content);
686
+ console.log(`File saved to: ${filePath}`);
687
+ console.log(`Size: ${fileData.size} bytes`);
688
+ console.log(`Content type: ${fileData.contentType}`);
689
+ console.log(`Checksum: ${fileData.checksum}`);
690
+ return filePath;
691
+ }
692
+ catch (error) {
693
+ console.error(`Error saving file by identifier ${identifier}:`, error);
694
+ throw error;
695
+ }
696
+ }
697
+ /**
698
+ * Saves file content retrieved from blockchain by TypeID to disk (legacy function)
699
+ * @param client The CKB client to use for blockchain queries
700
+ * @param typeId The TypeID (args) of the CKBFS cell
701
+ * @param outputPath Optional path to save the file (defaults to filename from CKBFS data)
702
+ * @param options Optional configuration for network, version, and useTypeID
703
+ * @returns Promise resolving to the path where the file was saved, or null if file not found
704
+ */
705
+ async function saveFileFromChainByTypeId(client, typeId, outputPath, options = {}) {
706
+ return await saveFileFromChainByIdentifier(client, typeId, outputPath, options);
707
+ }
708
+ /**
709
+ * Decodes file content directly from identifier using witness decoding (new method)
710
+ * @param client The CKB client to use for blockchain queries
711
+ * @param identifier The identifier (TypeID hex, CKBFS TypeID URI, or CKBFS outPoint URI)
712
+ * @param options Optional configuration for network, version, and useTypeID
713
+ * @returns Promise resolving to the decoded file content and metadata, or null if not found
714
+ */
715
+ async function decodeFileFromChainByIdentifier(client, identifier, options = {}) {
716
+ const { network = "testnet", version = "20241025.db973a8e8032", useTypeID = false, } = options;
717
+ try {
718
+ // Resolve the CKBFS cell using any supported identifier format
719
+ const cellInfo = await resolveCKBFSCell(client, identifier, {
720
+ network,
721
+ version,
722
+ useTypeID,
723
+ });
724
+ if (!cellInfo) {
725
+ console.warn(`CKBFS cell with identifier ${identifier} not found`);
726
+ return null;
727
+ }
728
+ const { cell, transaction, outPoint, parsedId } = cellInfo;
729
+ // Import required modules dynamically
730
+ const { Transaction, ccc } = await Promise.resolve().then(() => __importStar(require("@ckb-ccc/core")));
731
+ const { CKBFSData } = await Promise.resolve().then(() => __importStar(require("./molecule")));
732
+ const { ProtocolVersion } = await Promise.resolve().then(() => __importStar(require("./constants")));
733
+ const tx = Transaction.from(transaction);
734
+ // Get output data from the cell
735
+ const outputIndex = outPoint.index;
736
+ const outputData = tx.outputsData[outputIndex];
737
+ if (!outputData) {
738
+ throw new Error(`Output data not found for cell at index ${outputIndex}`);
739
+ }
740
+ // Parse the output data
741
+ const rawData = outputData.startsWith("0x")
742
+ ? ccc.bytesFrom(outputData.slice(2), "hex")
743
+ : Buffer.from(outputData, "hex");
744
+ // Try to unpack CKBFS data with both protocol versions
745
+ let ckbfsData;
746
+ let protocolVersion = version;
747
+ try {
748
+ ckbfsData = CKBFSData.unpack(rawData, ProtocolVersion.V2);
749
+ }
750
+ catch (error) {
751
+ try {
752
+ ckbfsData = CKBFSData.unpack(rawData, ProtocolVersion.V1);
753
+ protocolVersion = "20240906.ce6724722cf6";
754
+ }
755
+ catch (v1Error) {
756
+ throw new Error(`Failed to unpack CKBFS data with both versions: V2(${error}), V1(${v1Error})`);
757
+ }
758
+ }
759
+ console.log(`Found CKBFS file: ${ckbfsData.filename}`);
760
+ console.log(`Content type: ${ckbfsData.contentType}`);
761
+ console.log(`Using direct witness decoding method`);
762
+ // Get witness indexes from CKBFS data
763
+ const indexes = ckbfsData.indexes ||
764
+ (ckbfsData.index !== undefined ? [ckbfsData.index] : []);
765
+ // Use direct witness decoding method
766
+ const content = decodeFileFromWitnessData({
767
+ witnesses: tx.witnesses,
768
+ indexes: indexes,
769
+ filename: ckbfsData.filename,
770
+ contentType: ckbfsData.contentType,
771
+ });
772
+ return {
773
+ content: content.content,
774
+ filename: ckbfsData.filename,
775
+ contentType: ckbfsData.contentType,
776
+ checksum: ckbfsData.checksum,
777
+ size: content.size,
778
+ backLinks: ckbfsData.backLinks || [],
779
+ parsedId,
780
+ };
781
+ }
782
+ catch (error) {
783
+ console.error(`Error decoding file by identifier ${identifier}:`, error);
784
+ throw error;
785
+ }
786
+ }
787
+ /**
788
+ * Decodes file content directly from TypeID using witness decoding (legacy function)
789
+ * @param client The CKB client to use for blockchain queries
790
+ * @param typeId The TypeID (args) of the CKBFS cell
791
+ * @param options Optional configuration for network, version, and useTypeID
792
+ * @returns Promise resolving to the decoded file content and metadata, or null if not found
793
+ */
794
+ async function decodeFileFromChainByTypeId(client, typeId, options = {}) {
795
+ const result = await decodeFileFromChainByIdentifier(client, typeId, options);
796
+ if (result) {
797
+ const { parsedId, ...fileData } = result;
798
+ return fileData;
799
+ }
800
+ return null;
801
+ }