@ckbfs/api 1.5.1 → 2.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.
Files changed (45) hide show
  1. package/README.md +31 -6
  2. package/RFC.v3.md +210 -0
  3. package/dist/index.d.ts +72 -7
  4. package/dist/index.js +437 -75
  5. package/dist/utils/checksum.d.ts +16 -0
  6. package/dist/utils/checksum.js +74 -8
  7. package/dist/utils/constants.d.ts +2 -1
  8. package/dist/utils/constants.js +12 -2
  9. package/dist/utils/file.d.ts +44 -0
  10. package/dist/utils/file.js +303 -30
  11. package/dist/utils/molecule.d.ts +13 -1
  12. package/dist/utils/molecule.js +32 -5
  13. package/dist/utils/transaction-backup.d.ts +117 -0
  14. package/dist/utils/transaction-backup.js +624 -0
  15. package/dist/utils/transaction.d.ts +7 -115
  16. package/dist/utils/transaction.js +45 -622
  17. package/dist/utils/transactions/index.d.ts +8 -0
  18. package/dist/utils/transactions/index.js +31 -0
  19. package/dist/utils/transactions/shared.d.ts +57 -0
  20. package/dist/utils/transactions/shared.js +17 -0
  21. package/dist/utils/transactions/v1v2.d.ts +80 -0
  22. package/dist/utils/transactions/v1v2.js +592 -0
  23. package/dist/utils/transactions/v3.d.ts +124 -0
  24. package/dist/utils/transactions/v3.js +369 -0
  25. package/dist/utils/witness.d.ts +45 -0
  26. package/dist/utils/witness.js +145 -3
  27. package/examples/append-v3.ts +310 -0
  28. package/examples/chunked-publish.ts +307 -0
  29. package/examples/publish-v3.ts +152 -0
  30. package/examples/publish.ts +4 -4
  31. package/examples/retrieve-v3.ts +222 -0
  32. package/package.json +6 -2
  33. package/small-example.txt +1 -0
  34. package/src/index.ts +568 -87
  35. package/src/utils/checksum.ts +90 -9
  36. package/src/utils/constants.ts +19 -2
  37. package/src/utils/file.ts +386 -35
  38. package/src/utils/molecule.ts +43 -6
  39. package/src/utils/transaction-backup.ts +849 -0
  40. package/src/utils/transaction.ts +39 -848
  41. package/src/utils/transactions/index.ts +16 -0
  42. package/src/utils/transactions/shared.ts +64 -0
  43. package/src/utils/transactions/v1v2.ts +791 -0
  44. package/src/utils/transactions/v3.ts +564 -0
  45. package/src/utils/witness.ts +193 -0
@@ -0,0 +1,624 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureHexPrefix = ensureHexPrefix;
4
+ exports.createCKBFSCell = createCKBFSCell;
5
+ exports.preparePublishTransaction = preparePublishTransaction;
6
+ exports.createPublishTransaction = createPublishTransaction;
7
+ exports.prepareAppendTransaction = prepareAppendTransaction;
8
+ exports.createAppendTransaction = createAppendTransaction;
9
+ exports.createAppendTransactionDry = createAppendTransactionDry;
10
+ exports.publishCKBFS = publishCKBFS;
11
+ exports.appendCKBFS = appendCKBFS;
12
+ const core_1 = require("@ckb-ccc/core");
13
+ const checksum_1 = require("./checksum");
14
+ const molecule_1 = require("./molecule");
15
+ const witness_1 = require("./witness");
16
+ const constants_1 = require("./constants");
17
+ /**
18
+ * Ensures a string is prefixed with '0x'
19
+ * @param value The string to ensure is hex prefixed
20
+ * @returns A hex prefixed string
21
+ */
22
+ function ensureHexPrefix(value) {
23
+ if (value.startsWith("0x")) {
24
+ return value;
25
+ }
26
+ return `0x${value}`;
27
+ }
28
+ /**
29
+ * Creates a CKBFS cell
30
+ * @param options Options for creating the CKBFS cell
31
+ * @returns The created cell output
32
+ */
33
+ function createCKBFSCell(options) {
34
+ const { contentType, filename, capacity, lock, network = constants_1.DEFAULT_NETWORK, version = constants_1.DEFAULT_VERSION, useTypeID = false, } = options;
35
+ // Get CKBFS script config
36
+ const config = (0, constants_1.getCKBFSScriptConfig)(network, version, useTypeID);
37
+ // Create pre CKBFS type script
38
+ const preCkbfsTypeScript = new core_1.Script(ensureHexPrefix(config.codeHash), config.hashType, "0x0000000000000000000000000000000000000000000000000000000000000000");
39
+ // Return the cell output
40
+ return {
41
+ lock,
42
+ type: preCkbfsTypeScript,
43
+ capacity: capacity || 200n * 100000000n, // Default 200 CKB
44
+ };
45
+ }
46
+ /**
47
+ * Prepares a transaction for publishing a file to CKBFS without fee and change handling
48
+ * You will need to manually set the typeID if you did not provide inputs, or just check is return value emptyTypeID is true
49
+ * @param options Options for publishing the file
50
+ * @returns Promise resolving to the prepared transaction and the output index of CKBFS Cell
51
+ */
52
+ async function preparePublishTransaction(options) {
53
+ const { from, contentChunks, contentType, filename, lock, capacity, network = constants_1.DEFAULT_NETWORK, version = constants_1.DEFAULT_VERSION, useTypeID = false, } = options;
54
+ // Calculate checksum for the combined content
55
+ const combinedContent = Buffer.concat(contentChunks);
56
+ const checksum = await (0, checksum_1.calculateChecksum)(combinedContent);
57
+ // Create CKBFS witnesses - each chunk already includes the CKBFS header
58
+ // Pass 0 as version byte - this is the protocol version byte in the witness header
59
+ // not to be confused with the Protocol Version (V1 vs V2)
60
+ const ckbfsWitnesses = (0, witness_1.createChunkedCKBFSWitnesses)(contentChunks);
61
+ // Calculate the actual witness indices where our content is placed
62
+ const contentStartIndex = from?.witnesses.length || 1;
63
+ const witnessIndices = Array.from({ length: contentChunks.length }, (_, i) => contentStartIndex + i);
64
+ // Create CKBFS cell output data based on version
65
+ let outputData;
66
+ if (version === constants_1.ProtocolVersion.V1) {
67
+ // V1 format: Single index field (a single number, not an array)
68
+ // For V1, use the first index where content is placed
69
+ outputData = molecule_1.CKBFSData.pack({
70
+ index: contentStartIndex,
71
+ checksum,
72
+ contentType: contentType,
73
+ filename: filename,
74
+ backLinks: [],
75
+ }, version);
76
+ }
77
+ else {
78
+ // V2 format: Multiple indexes (array of numbers)
79
+ // For V2, use all the indices where content is placed
80
+ outputData = molecule_1.CKBFSData.pack({
81
+ indexes: witnessIndices,
82
+ checksum,
83
+ contentType,
84
+ filename,
85
+ backLinks: [],
86
+ }, version);
87
+ }
88
+ // Get CKBFS script config
89
+ const config = (0, constants_1.getCKBFSScriptConfig)(network, version, useTypeID);
90
+ const preCkbfsTypeScript = new core_1.Script(ensureHexPrefix(config.codeHash), config.hashType, "0x0000000000000000000000000000000000000000000000000000000000000000");
91
+ const ckbfsCellSize = BigInt(outputData.length +
92
+ preCkbfsTypeScript.occupiedSize +
93
+ lock.occupiedSize +
94
+ 8) * 100000000n;
95
+ // Create pre transaction without cell deps initially
96
+ let preTx;
97
+ if (from) {
98
+ // If from is not empty, inject/merge the fields
99
+ preTx = core_1.Transaction.from({
100
+ ...from,
101
+ outputs: from.outputs.length === 0
102
+ ? [
103
+ createCKBFSCell({
104
+ contentType,
105
+ filename,
106
+ lock,
107
+ network,
108
+ version,
109
+ useTypeID,
110
+ capacity: ckbfsCellSize || capacity,
111
+ }),
112
+ ]
113
+ : [
114
+ ...from.outputs,
115
+ createCKBFSCell({
116
+ contentType,
117
+ filename,
118
+ lock,
119
+ network,
120
+ version,
121
+ useTypeID,
122
+ capacity: ckbfsCellSize || capacity,
123
+ }),
124
+ ],
125
+ witnesses: from.witnesses.length === 0
126
+ ? [
127
+ [], // Empty secp witness for signing if not provided
128
+ ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
129
+ ]
130
+ : [
131
+ ...from.witnesses,
132
+ ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
133
+ ],
134
+ outputsData: from.outputsData.length === 0
135
+ ? [outputData]
136
+ : [
137
+ ...from.outputsData,
138
+ outputData,
139
+ ],
140
+ });
141
+ }
142
+ else {
143
+ preTx = core_1.Transaction.from({
144
+ outputs: [
145
+ createCKBFSCell({
146
+ contentType,
147
+ filename,
148
+ lock,
149
+ network,
150
+ version,
151
+ useTypeID,
152
+ capacity: ckbfsCellSize || capacity,
153
+ }),
154
+ ],
155
+ witnesses: [
156
+ [], // Empty secp witness for signing
157
+ ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
158
+ ],
159
+ outputsData: [outputData],
160
+ });
161
+ }
162
+ // Add the CKBFS dep group cell dependency
163
+ preTx.addCellDeps({
164
+ outPoint: {
165
+ txHash: ensureHexPrefix(config.depTxHash),
166
+ index: config.depIndex || 0,
167
+ },
168
+ depType: "depGroup",
169
+ });
170
+ // Create type ID args
171
+ const outputIndex = from ? from.outputs.length : 0;
172
+ const args = preTx.inputs.length > 0 ? core_1.ccc.hashTypeId(preTx.inputs[0], outputIndex) : "0x0000000000000000000000000000000000000000000000000000000000000000";
173
+ // Create CKBFS type script with type ID
174
+ const ckbfsTypeScript = new core_1.Script(ensureHexPrefix(config.codeHash), config.hashType, args);
175
+ // Create final transaction with same cell deps as preTx
176
+ const tx = core_1.Transaction.from({
177
+ cellDeps: preTx.cellDeps,
178
+ witnesses: preTx.witnesses,
179
+ outputsData: preTx.outputsData,
180
+ inputs: preTx.inputs,
181
+ outputs: outputIndex === 0
182
+ ? [
183
+ {
184
+ lock,
185
+ type: ckbfsTypeScript,
186
+ capacity: preTx.outputs[outputIndex].capacity,
187
+ },
188
+ ]
189
+ : [
190
+ ...preTx.outputs.slice(0, outputIndex), // Include rest of outputs (e.g., change)
191
+ {
192
+ lock,
193
+ type: ckbfsTypeScript,
194
+ capacity: preTx.outputs[outputIndex].capacity,
195
+ },
196
+ ],
197
+ });
198
+ return { tx, outputIndex, emptyTypeID: args === "0x0000000000000000000000000000000000000000000000000000000000000000" };
199
+ }
200
+ /**
201
+ * Creates a transaction for publishing a file to CKBFS
202
+ * @param signer The signer to use for the transaction
203
+ * @param options Options for publishing the file
204
+ * @returns Promise resolving to the created transaction
205
+ */
206
+ async function createPublishTransaction(signer, options) {
207
+ const { feeRate, lock, } = options;
208
+ // Use preparePublishTransaction to create the base transaction
209
+ const { tx: preTx, outputIndex, emptyTypeID } = await preparePublishTransaction(options);
210
+ // Complete inputs by capacity
211
+ await preTx.completeInputsByCapacity(signer);
212
+ // Complete fee change to lock
213
+ await preTx.completeFeeChangeToLock(signer, lock, feeRate || 2000);
214
+ // If emptyTypeID is true, we need to create the proper type ID args
215
+ if (emptyTypeID) {
216
+ // Get CKBFS script config
217
+ const config = (0, constants_1.getCKBFSScriptConfig)(options.network || constants_1.DEFAULT_NETWORK, options.version || constants_1.DEFAULT_VERSION, options.useTypeID || false);
218
+ // Create type ID args
219
+ const args = core_1.ccc.hashTypeId(preTx.inputs[0], outputIndex);
220
+ // Create CKBFS type script with type ID
221
+ const ckbfsTypeScript = new core_1.Script(ensureHexPrefix(config.codeHash), config.hashType, args);
222
+ // Create final transaction with updated type script
223
+ const tx = core_1.Transaction.from({
224
+ cellDeps: preTx.cellDeps,
225
+ witnesses: preTx.witnesses,
226
+ outputsData: preTx.outputsData,
227
+ inputs: preTx.inputs,
228
+ outputs: preTx.outputs.map((output, index) => index === outputIndex
229
+ ? {
230
+ lock,
231
+ type: ckbfsTypeScript,
232
+ capacity: output.capacity,
233
+ }
234
+ : output),
235
+ });
236
+ return tx;
237
+ }
238
+ else {
239
+ // If typeID was already set properly, just reset the first witness for signing
240
+ const tx = core_1.Transaction.from({
241
+ cellDeps: preTx.cellDeps,
242
+ witnesses: preTx.witnesses,
243
+ outputsData: preTx.outputsData,
244
+ inputs: preTx.inputs,
245
+ outputs: preTx.outputs,
246
+ });
247
+ return tx;
248
+ }
249
+ }
250
+ /**
251
+ * Prepares a transaction for appending content to a CKBFS file without fee and change handling
252
+ * @param options Options for appending content
253
+ * @returns Promise resolving to the prepared transaction and the output index of CKBFS Cell
254
+ */
255
+ async function prepareAppendTransaction(options) {
256
+ const { from, ckbfsCell, contentChunks, network = constants_1.DEFAULT_NETWORK, version = constants_1.DEFAULT_VERSION, } = options;
257
+ const { outPoint, data, type, lock, capacity } = ckbfsCell;
258
+ // Get CKBFS script config early to use version info
259
+ const config = (0, constants_1.getCKBFSScriptConfig)(network, version);
260
+ // Create CKBFS witnesses - each chunk already includes the CKBFS header
261
+ // Pass 0 as version byte - this is the protocol version byte in the witness header
262
+ // not to be confused with the Protocol Version (V1 vs V2)
263
+ const ckbfsWitnesses = (0, witness_1.createChunkedCKBFSWitnesses)(contentChunks);
264
+ // Combine the new content chunks for checksum calculation
265
+ const combinedContent = Buffer.concat(contentChunks);
266
+ // Update the existing checksum with the new content - this matches Adler32's
267
+ // cumulative nature as required by Rule 11 in the RFC
268
+ const contentChecksum = await (0, checksum_1.updateChecksum)(data.checksum, combinedContent);
269
+ // Calculate the actual witness indices where our content is placed
270
+ const contentStartIndex = from?.witnesses.length || 1;
271
+ const witnessIndices = Array.from({ length: contentChunks.length }, (_, i) => contentStartIndex + i);
272
+ // Create backlink for the current state based on version
273
+ let newBackLink;
274
+ if (version === constants_1.ProtocolVersion.V1) {
275
+ // V1 format: Use index field (single number)
276
+ newBackLink = {
277
+ // In V1, field order is index, checksum, txHash
278
+ // and index is a single number value, not an array
279
+ index: data.index ||
280
+ (data.indexes && data.indexes.length > 0 ? data.indexes[0] : 0),
281
+ checksum: data.checksum,
282
+ txHash: outPoint.txHash,
283
+ };
284
+ }
285
+ else {
286
+ // V2 format: Use indexes field (array of numbers)
287
+ newBackLink = {
288
+ // In V2, field order is indexes, checksum, txHash
289
+ // and indexes is an array of numbers
290
+ indexes: data.indexes || (data.index ? [data.index] : []),
291
+ checksum: data.checksum,
292
+ txHash: outPoint.txHash,
293
+ };
294
+ }
295
+ // Update backlinks - add the new one to the existing backlinks array
296
+ const backLinks = [...(data.backLinks || []), newBackLink];
297
+ // Define output data based on version
298
+ let outputData;
299
+ if (version === constants_1.ProtocolVersion.V1) {
300
+ // In V1, index is a single number, not an array
301
+ // The first witness index is used (V1 can only reference one witness)
302
+ outputData = molecule_1.CKBFSData.pack({
303
+ index: witnessIndices[0], // Use only the first index as a number
304
+ checksum: contentChecksum,
305
+ contentType: data.contentType,
306
+ filename: data.filename,
307
+ backLinks,
308
+ }, constants_1.ProtocolVersion.V1); // Explicitly use V1 for packing
309
+ }
310
+ else {
311
+ // In V2, indexes is an array of witness indices
312
+ outputData = molecule_1.CKBFSData.pack({
313
+ indexes: witnessIndices,
314
+ checksum: contentChecksum,
315
+ contentType: data.contentType,
316
+ filename: data.filename,
317
+ backLinks,
318
+ }, constants_1.ProtocolVersion.V2); // Explicitly use V2 for packing
319
+ }
320
+ // Calculate the required capacity for the output cell
321
+ // This accounts for:
322
+ // 1. The output data size
323
+ // 2. The type script's occupied size
324
+ // 3. The lock script's occupied size
325
+ // 4. A constant of 8 bytes (for header overhead)
326
+ const ckbfsCellSize = BigInt(outputData.length + type.occupiedSize + lock.occupiedSize + 8) *
327
+ 100000000n;
328
+ // Use the maximum value between calculated size and original capacity
329
+ // to ensure we have enough capacity but don't decrease capacity unnecessarily
330
+ const outputCapacity = ckbfsCellSize > capacity ? ckbfsCellSize : capacity;
331
+ // Create initial transaction with the CKBFS cell input
332
+ let preTx;
333
+ if (from) {
334
+ // If from is not empty, inject/merge the fields
335
+ preTx = core_1.Transaction.from({
336
+ ...from,
337
+ inputs: from.inputs.length === 0
338
+ ? [
339
+ {
340
+ previousOutput: {
341
+ txHash: outPoint.txHash,
342
+ index: outPoint.index,
343
+ },
344
+ since: "0x0",
345
+ },
346
+ ]
347
+ : [
348
+ ...from.inputs,
349
+ {
350
+ previousOutput: {
351
+ txHash: outPoint.txHash,
352
+ index: outPoint.index,
353
+ },
354
+ since: "0x0",
355
+ },
356
+ ],
357
+ outputs: from.outputs.length === 0
358
+ ? [
359
+ {
360
+ lock,
361
+ type,
362
+ capacity: outputCapacity,
363
+ },
364
+ ]
365
+ : [
366
+ ...from.outputs,
367
+ {
368
+ lock,
369
+ type,
370
+ capacity: outputCapacity,
371
+ },
372
+ ],
373
+ outputsData: from.outputsData.length === 0
374
+ ? [outputData]
375
+ : [
376
+ ...from.outputsData,
377
+ outputData,
378
+ ],
379
+ witnesses: from.witnesses.length === 0
380
+ ? [
381
+ [], // Empty secp witness for signing if not provided
382
+ ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
383
+ ]
384
+ : [
385
+ ...from.witnesses,
386
+ ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
387
+ ],
388
+ });
389
+ }
390
+ else {
391
+ preTx = core_1.Transaction.from({
392
+ inputs: [
393
+ {
394
+ previousOutput: {
395
+ txHash: outPoint.txHash,
396
+ index: outPoint.index,
397
+ },
398
+ since: "0x0",
399
+ },
400
+ ],
401
+ outputs: [
402
+ {
403
+ lock,
404
+ type,
405
+ capacity: outputCapacity,
406
+ },
407
+ ],
408
+ outputsData: [outputData],
409
+ witnesses: [
410
+ [], // Empty secp witness for signing
411
+ ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
412
+ ],
413
+ });
414
+ }
415
+ // Add the CKBFS dep group cell dependency
416
+ preTx.addCellDeps({
417
+ outPoint: {
418
+ txHash: ensureHexPrefix(config.depTxHash),
419
+ index: config.depIndex || 0,
420
+ },
421
+ depType: "depGroup",
422
+ });
423
+ const outputIndex = from ? from.outputs.length : 0;
424
+ return { tx: preTx, outputIndex };
425
+ }
426
+ /**
427
+ * Creates a transaction for appending content to a CKBFS file
428
+ * @param signer The signer to use for the transaction
429
+ * @param options Options for appending content
430
+ * @returns Promise resolving to the created transaction
431
+ */
432
+ async function createAppendTransaction(signer, options) {
433
+ const { ckbfsCell, feeRate, } = options;
434
+ const { lock } = ckbfsCell;
435
+ // Use prepareAppendTransaction to create the base transaction
436
+ const { tx: preTx, outputIndex } = await prepareAppendTransaction(options);
437
+ // Get the recommended address to ensure lock script cell deps are included
438
+ const address = await signer.getRecommendedAddressObj();
439
+ const inputsBefore = preTx.inputs.length;
440
+ // If we need more capacity than the original cell had, add additional inputs
441
+ if (preTx.outputs[outputIndex].capacity > ckbfsCell.capacity) {
442
+ console.log(`Need additional capacity: ${preTx.outputs[outputIndex].capacity - ckbfsCell.capacity} shannons`);
443
+ // Add more inputs to cover the increased capacity
444
+ await preTx.completeInputsByCapacity(signer);
445
+ }
446
+ const witnesses = [];
447
+ // add empty witness for signer if ckbfs's lock is the same as signer's lock
448
+ if (address.script.hash() === lock.hash()) {
449
+ witnesses.push("0x");
450
+ }
451
+ // add ckbfs witnesses (skip the first witness which is for signing)
452
+ witnesses.push(...preTx.witnesses.slice(1));
453
+ // Add empty witnesses for additional signer inputs
454
+ // This is to ensure that the transaction is valid and can be signed
455
+ for (let i = inputsBefore; i < preTx.inputs.length; i++) {
456
+ witnesses.push("0x");
457
+ }
458
+ preTx.witnesses = witnesses;
459
+ // Complete fee
460
+ await preTx.completeFeeChangeToLock(signer, address.script, feeRate || 2000);
461
+ return preTx;
462
+ }
463
+ /**
464
+ * Creates a transaction for appending content to a CKBFS file
465
+ * @param signer The signer to use for the transaction
466
+ * @param options Options for appending content
467
+ * @returns Promise resolving to the created transaction
468
+ */
469
+ async function createAppendTransactionDry(signer, options) {
470
+ const { ckbfsCell, contentChunks, network = constants_1.DEFAULT_NETWORK, version = constants_1.DEFAULT_VERSION, } = options;
471
+ const { outPoint, data, type, lock, capacity } = ckbfsCell;
472
+ // Get CKBFS script config early to use version info
473
+ const config = (0, constants_1.getCKBFSScriptConfig)(network, version);
474
+ // Create CKBFS witnesses - each chunk already includes the CKBFS header
475
+ // Pass 0 as version byte - this is the protocol version byte in the witness header
476
+ // not to be confused with the Protocol Version (V1 vs V2)
477
+ const ckbfsWitnesses = (0, witness_1.createChunkedCKBFSWitnesses)(contentChunks);
478
+ // Combine the new content chunks for checksum calculation
479
+ const combinedContent = Buffer.concat(contentChunks);
480
+ // Update the existing checksum with the new content - this matches Adler32's
481
+ // cumulative nature as required by Rule 11 in the RFC
482
+ const contentChecksum = await (0, checksum_1.updateChecksum)(data.checksum, combinedContent);
483
+ console.log(`Updated checksum from ${data.checksum} to ${contentChecksum} for appended content`);
484
+ // Get the recommended address to ensure lock script cell deps are included
485
+ const address = await signer.getRecommendedAddressObj();
486
+ // Calculate the actual witness indices where our content is placed
487
+ // CKBFS data starts at index 1 if signer's lock script is the same as ckbfs's lock script
488
+ // else CKBFS data starts at index 0
489
+ const contentStartIndex = address.script.hash() === lock.hash() ? 1 : 0;
490
+ const witnessIndices = Array.from({ length: contentChunks.length }, (_, i) => contentStartIndex + i);
491
+ // Create backlink for the current state based on version
492
+ let newBackLink;
493
+ if (version === constants_1.ProtocolVersion.V1) {
494
+ // V1 format: Use index field (single number)
495
+ newBackLink = {
496
+ // In V1, field order is index, checksum, txHash
497
+ // and index is a single number value, not an array
498
+ index: data.index ||
499
+ (data.indexes && data.indexes.length > 0 ? data.indexes[0] : 0),
500
+ checksum: data.checksum,
501
+ txHash: outPoint.txHash,
502
+ };
503
+ }
504
+ else {
505
+ // V2 format: Use indexes field (array of numbers)
506
+ newBackLink = {
507
+ // In V2, field order is indexes, checksum, txHash
508
+ // and indexes is an array of numbers
509
+ indexes: data.indexes || (data.index ? [data.index] : []),
510
+ checksum: data.checksum,
511
+ txHash: outPoint.txHash,
512
+ };
513
+ }
514
+ // Update backlinks - add the new one to the existing backlinks array
515
+ const backLinks = [...(data.backLinks || []), newBackLink];
516
+ // Define output data based on version
517
+ let outputData;
518
+ if (version === constants_1.ProtocolVersion.V1) {
519
+ // In V1, index is a single number, not an array
520
+ // The first witness index is used (V1 can only reference one witness)
521
+ outputData = molecule_1.CKBFSData.pack({
522
+ index: witnessIndices[0], // Use only the first index as a number
523
+ checksum: contentChecksum,
524
+ contentType: data.contentType,
525
+ filename: data.filename,
526
+ backLinks,
527
+ }, constants_1.ProtocolVersion.V1); // Explicitly use V1 for packing
528
+ }
529
+ else {
530
+ // In V2, indexes is an array of witness indices
531
+ outputData = molecule_1.CKBFSData.pack({
532
+ indexes: witnessIndices,
533
+ checksum: contentChecksum,
534
+ contentType: data.contentType,
535
+ filename: data.filename,
536
+ backLinks,
537
+ }, constants_1.ProtocolVersion.V2); // Explicitly use V2 for packing
538
+ }
539
+ // Calculate the required capacity for the output cell
540
+ // This accounts for:
541
+ // 1. The output data size
542
+ // 2. The type script's occupied size
543
+ // 3. The lock script's occupied size
544
+ // 4. A constant of 8 bytes (for header overhead)
545
+ const ckbfsCellSize = BigInt(outputData.length + type.occupiedSize + lock.occupiedSize + 8) *
546
+ 100000000n;
547
+ console.log(`Original capacity: ${capacity}, Calculated size: ${ckbfsCellSize}, Data size: ${outputData.length}`);
548
+ // Use the maximum value between calculated size and original capacity
549
+ // to ensure we have enough capacity but don't decrease capacity unnecessarily
550
+ const outputCapacity = ckbfsCellSize > capacity ? ckbfsCellSize : capacity;
551
+ // Create initial transaction with the CKBFS cell input
552
+ const tx = core_1.Transaction.from({
553
+ inputs: [
554
+ {
555
+ previousOutput: {
556
+ txHash: outPoint.txHash,
557
+ index: outPoint.index,
558
+ },
559
+ since: "0x0",
560
+ },
561
+ ],
562
+ outputs: [
563
+ {
564
+ lock,
565
+ type,
566
+ capacity: outputCapacity,
567
+ },
568
+ ],
569
+ outputsData: [outputData],
570
+ });
571
+ // Add the CKBFS dep group cell dependency
572
+ tx.addCellDeps({
573
+ outPoint: {
574
+ txHash: ensureHexPrefix(config.depTxHash),
575
+ index: config.depIndex || 0,
576
+ },
577
+ depType: "depGroup",
578
+ });
579
+ const inputsBefore = tx.inputs.length;
580
+ // // If we need more capacity than the original cell had, add additional inputs
581
+ // if (outputCapacity > capacity) {
582
+ // console.log(
583
+ // `Need additional capacity: ${outputCapacity - capacity} shannons`,
584
+ // );
585
+ // // Add more inputs to cover the increased capacity
586
+ // await tx.completeInputsByCapacity(signer);
587
+ // }
588
+ const witnesses = [];
589
+ // add empty witness for signer if ckbfs's lock is the same as signer's lock
590
+ if (address.script.hash() === lock.hash()) {
591
+ witnesses.push("0x");
592
+ }
593
+ // add ckbfs witnesses
594
+ witnesses.push(...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`));
595
+ // Add empty witnesses for signer's input
596
+ // This is to ensure that the transaction is valid and can be signed
597
+ for (let i = inputsBefore; i < tx.inputs.length; i++) {
598
+ witnesses.push("0x");
599
+ }
600
+ tx.witnesses = witnesses;
601
+ // Complete fee
602
+ //await tx.completeFeeChangeToLock(signer, address.script, feeRate || 2000);
603
+ return tx;
604
+ }
605
+ /**
606
+ * Creates a complete transaction for publishing a file to CKBFS
607
+ * @param signer The signer to use for the transaction
608
+ * @param options Options for publishing the file
609
+ * @returns Promise resolving to the signed transaction
610
+ */
611
+ async function publishCKBFS(signer, options) {
612
+ const tx = await createPublishTransaction(signer, options);
613
+ return signer.signTransaction(tx);
614
+ }
615
+ /**
616
+ * Creates a complete transaction for appending content to a CKBFS file
617
+ * @param signer The signer to use for the transaction
618
+ * @param options Options for appending content
619
+ * @returns Promise resolving to the signed transaction
620
+ */
621
+ async function appendCKBFS(signer, options) {
622
+ const tx = await createAppendTransaction(signer, options);
623
+ return signer.signTransaction(tx);
624
+ }