@ckbfs/api 1.3.0 → 1.5.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.
@@ -1,14 +1,15 @@
1
1
  import { ccc, Transaction, Script, Signer } from "@ckb-ccc/core";
2
- import { calculateChecksum, updateChecksum } from './checksum';
3
- import { CKBFSData, BackLinkType, CKBFSDataType } from './molecule';
4
- import { createChunkedCKBFSWitnesses } from './witness';
5
- import {
6
- getCKBFSScriptConfig,
7
- NetworkType,
8
- ProtocolVersion,
9
- DEFAULT_NETWORK,
10
- DEFAULT_VERSION
11
- } from './constants';
2
+ import { calculateChecksum, updateChecksum } from "./checksum";
3
+ import { CKBFSData, BackLinkType, CKBFSDataType } from "./molecule";
4
+ import { createChunkedCKBFSWitnesses } from "./witness";
5
+ import {
6
+ getCKBFSScriptConfig,
7
+ NetworkType,
8
+ ProtocolVersion,
9
+ ProtocolVersionType,
10
+ DEFAULT_NETWORK,
11
+ DEFAULT_VERSION,
12
+ } from "./constants";
12
13
 
13
14
  /**
14
15
  * Utility functions for CKB transaction creation and handling
@@ -23,7 +24,7 @@ export interface CKBFSCellOptions {
23
24
  capacity?: bigint;
24
25
  lock: Script;
25
26
  network?: NetworkType;
26
- version?: string;
27
+ version?: ProtocolVersionType;
27
28
  useTypeID?: boolean;
28
29
  }
29
30
 
@@ -33,6 +34,7 @@ export interface CKBFSCellOptions {
33
34
  export interface PublishOptions extends CKBFSCellOptions {
34
35
  contentChunks: Uint8Array[];
35
36
  feeRate?: number;
37
+ from?: Transaction;
36
38
  }
37
39
 
38
40
  /**
@@ -49,7 +51,8 @@ export interface AppendOptions {
49
51
  contentChunks: Uint8Array[];
50
52
  feeRate?: number;
51
53
  network?: NetworkType;
52
- version?: string;
54
+ version?: ProtocolVersionType;
55
+ from?: Transaction;
53
56
  }
54
57
 
55
58
  /**
@@ -58,7 +61,7 @@ export interface AppendOptions {
58
61
  * @returns A hex prefixed string
59
62
  */
60
63
  export function ensureHexPrefix(value: string): `0x${string}` {
61
- if (value.startsWith('0x')) {
64
+ if (value.startsWith("0x")) {
62
65
  return value as `0x${string}`;
63
66
  }
64
67
  return `0x${value}` as `0x${string}`;
@@ -70,26 +73,26 @@ export function ensureHexPrefix(value: string): `0x${string}` {
70
73
  * @returns The created cell output
71
74
  */
72
75
  export function createCKBFSCell(options: CKBFSCellOptions) {
73
- const {
74
- contentType,
75
- filename,
76
- capacity,
77
- lock,
78
- network = DEFAULT_NETWORK,
76
+ const {
77
+ contentType,
78
+ filename,
79
+ capacity,
80
+ lock,
81
+ network = DEFAULT_NETWORK,
79
82
  version = DEFAULT_VERSION,
80
- useTypeID = false
83
+ useTypeID = false,
81
84
  } = options;
82
-
85
+
83
86
  // Get CKBFS script config
84
87
  const config = getCKBFSScriptConfig(network, version, useTypeID);
85
-
88
+
86
89
  // Create pre CKBFS type script
87
90
  const preCkbfsTypeScript = new Script(
88
91
  ensureHexPrefix(config.codeHash),
89
92
  config.hashType as any,
90
- "0x0000000000000000000000000000000000000000000000000000000000000000"
93
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
91
94
  );
92
-
95
+
93
96
  // Return the cell output
94
97
  return {
95
98
  lock,
@@ -99,150 +102,481 @@ export function createCKBFSCell(options: CKBFSCellOptions) {
99
102
  }
100
103
 
101
104
  /**
102
- * Creates a transaction for publishing a file to CKBFS
103
- * @param signer The signer to use for the transaction
105
+ * Prepares a transaction for publishing a file to CKBFS without fee and change handling
106
+ * You will need to manually set the typeID if you did not provide inputs, or just check is return value emptyTypeID is true
104
107
  * @param options Options for publishing the file
105
- * @returns Promise resolving to the created transaction
108
+ * @returns Promise resolving to the prepared transaction and the output index of CKBFS Cell
106
109
  */
107
- export async function createPublishTransaction(
108
- signer: Signer,
109
- options: PublishOptions
110
- ): Promise<Transaction> {
111
- const {
112
- contentChunks,
113
- contentType,
114
- filename,
115
- lock,
110
+ export async function preparePublishTransaction(
111
+ options: PublishOptions,
112
+ ): Promise<{tx: Transaction, outputIndex: number, emptyTypeID: boolean}> { // if emptyTypeID is true, you shall manually set the typeID after
113
+ const {
114
+ from,
115
+ contentChunks,
116
+ contentType,
117
+ filename,
118
+ lock,
116
119
  capacity,
117
- feeRate,
118
120
  network = DEFAULT_NETWORK,
119
121
  version = DEFAULT_VERSION,
120
- useTypeID = false
122
+ useTypeID = false,
121
123
  } = options;
122
-
124
+
123
125
  // Calculate checksum for the combined content
124
- const textEncoder = new TextEncoder();
125
126
  const combinedContent = Buffer.concat(contentChunks);
126
127
  const checksum = await calculateChecksum(combinedContent);
127
-
128
+
128
129
  // Create CKBFS witnesses - each chunk already includes the CKBFS header
129
130
  // Pass 0 as version byte - this is the protocol version byte in the witness header
130
131
  // not to be confused with the Protocol Version (V1 vs V2)
131
132
  const ckbfsWitnesses = createChunkedCKBFSWitnesses(contentChunks);
132
-
133
+
133
134
  // Calculate the actual witness indices where our content is placed
134
- // Index 0 is reserved for the secp256k1 witness for signing
135
- // So our CKBFS data starts at index 1
136
- const contentStartIndex = 1;
135
+
136
+ const contentStartIndex = from?.witnesses.length || 1;
137
137
  const witnessIndices = Array.from(
138
- { length: contentChunks.length },
139
- (_, i) => contentStartIndex + i
138
+ { length: contentChunks.length },
139
+ (_, i) => contentStartIndex + i,
140
140
  );
141
-
141
+
142
142
  // Create CKBFS cell output data based on version
143
143
  let outputData: Uint8Array;
144
-
144
+
145
145
  if (version === ProtocolVersion.V1) {
146
146
  // V1 format: Single index field (a single number, not an array)
147
147
  // For V1, use the first index where content is placed
148
- outputData = CKBFSData.pack({
149
- index: contentStartIndex,
150
- checksum,
151
- contentType: contentType,
152
- filename: filename,
153
- backLinks: [],
154
- }, version);
148
+ outputData = CKBFSData.pack(
149
+ {
150
+ index: contentStartIndex,
151
+ checksum,
152
+ contentType: contentType,
153
+ filename: filename,
154
+ backLinks: [],
155
+ },
156
+ version,
157
+ );
155
158
  } else {
156
159
  // V2 format: Multiple indexes (array of numbers)
157
160
  // For V2, use all the indices where content is placed
158
- outputData = CKBFSData.pack({
159
- indexes: witnessIndices,
160
- checksum,
161
- contentType,
162
- filename,
163
- backLinks: [],
164
- }, version);
161
+ outputData = CKBFSData.pack(
162
+ {
163
+ indexes: witnessIndices,
164
+ checksum,
165
+ contentType,
166
+ filename,
167
+ backLinks: [],
168
+ },
169
+ version,
170
+ );
165
171
  }
166
-
172
+
167
173
  // Get CKBFS script config
168
174
  const config = getCKBFSScriptConfig(network, version, useTypeID);
169
-
175
+
170
176
  const preCkbfsTypeScript = new Script(
171
177
  ensureHexPrefix(config.codeHash),
172
178
  config.hashType as any,
173
- "0x0000000000000000000000000000000000000000000000000000000000000000"
179
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
174
180
  );
175
- const ckbfsCellSize = BigInt(outputData.length + preCkbfsTypeScript.occupiedSize + lock.occupiedSize + 8) * 100000000n
181
+ const ckbfsCellSize =
182
+ BigInt(
183
+ outputData.length +
184
+ preCkbfsTypeScript.occupiedSize +
185
+ lock.occupiedSize +
186
+ 8,
187
+ ) * 100000000n;
176
188
  // Create pre transaction without cell deps initially
177
- const preTx = Transaction.from({
178
- outputs: [
179
- createCKBFSCell({
180
- contentType,
181
- filename,
182
- lock,
183
- network,
184
- version,
185
- useTypeID,
186
- capacity: ckbfsCellSize || capacity
187
- })
188
- ],
189
- witnesses: [
190
- [], // Empty secp witness for signing
191
- ...ckbfsWitnesses.map(w => `0x${Buffer.from(w).toString('hex')}`),
192
- ],
193
- outputsData: [
194
- outputData,
195
- ]
196
- });
197
-
189
+ let preTx: Transaction;
190
+ if(from) {
191
+ // If from is not empty, inject/merge the fields
192
+ preTx = Transaction.from({
193
+ ...from,
194
+ outputs: from.outputs.length === 0
195
+ ? [
196
+ createCKBFSCell({
197
+ contentType,
198
+ filename,
199
+ lock,
200
+ network,
201
+ version,
202
+ useTypeID,
203
+ capacity: ckbfsCellSize || capacity,
204
+ }),
205
+ ]
206
+ : [
207
+ ...from.outputs,
208
+ createCKBFSCell({
209
+ contentType,
210
+ filename,
211
+ lock,
212
+ network,
213
+ version,
214
+ useTypeID,
215
+ capacity: ckbfsCellSize || capacity,
216
+ }),
217
+ ],
218
+ witnesses: from.witnesses.length === 0
219
+ ? [
220
+ [], // Empty secp witness for signing if not provided
221
+ ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
222
+ ]
223
+ : [
224
+ ...from.witnesses,
225
+ ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
226
+ ],
227
+ outputsData: from.outputsData.length === 0
228
+ ? [outputData]
229
+ : [
230
+ ...from.outputsData,
231
+ outputData,
232
+ ],
233
+ });
234
+ } else {
235
+ preTx = Transaction.from({
236
+ outputs: [
237
+ createCKBFSCell({
238
+ contentType,
239
+ filename,
240
+ lock,
241
+ network,
242
+ version,
243
+ useTypeID,
244
+ capacity: ckbfsCellSize || capacity,
245
+ }),
246
+ ],
247
+ witnesses: [
248
+ [], // Empty secp witness for signing
249
+ ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
250
+ ],
251
+ outputsData: [outputData],
252
+ });
253
+ }
254
+
198
255
  // Add the CKBFS dep group cell dependency
199
256
  preTx.addCellDeps({
200
257
  outPoint: {
201
258
  txHash: ensureHexPrefix(config.depTxHash),
202
259
  index: config.depIndex || 0,
203
260
  },
204
- depType: "depGroup"
261
+ depType: "depGroup",
205
262
  });
206
-
207
- // Get the recommended address to ensure lock script cell deps are included
208
- const address = await signer.getRecommendedAddressObj();
209
-
210
- // Complete inputs by capacity
211
- await preTx.completeInputsByCapacity(signer);
212
-
213
- // Complete fee change to lock
214
- await preTx.completeFeeChangeToLock(signer, lock, feeRate || 2000);
215
-
263
+
216
264
  // Create type ID args
217
- const args = ccc.hashTypeId(preTx.inputs[0], 0x0);
218
-
265
+ const outputIndex = from ? from.outputs.length : 0;
266
+ const args = preTx.inputs.length > 0 ? ccc.hashTypeId(preTx.inputs[0], outputIndex) : "0x0000000000000000000000000000000000000000000000000000000000000000";
267
+
219
268
  // Create CKBFS type script with type ID
220
269
  const ckbfsTypeScript = new Script(
221
270
  ensureHexPrefix(config.codeHash),
222
271
  config.hashType as any,
223
- args
272
+ args,
224
273
  );
225
-
274
+
226
275
  // Create final transaction with same cell deps as preTx
227
276
  const tx = Transaction.from({
228
277
  cellDeps: preTx.cellDeps,
229
- witnesses: [
230
- [], // Reset first witness for signing
231
- ...preTx.witnesses.slice(1)
232
- ],
278
+ witnesses: preTx.witnesses,
233
279
  outputsData: preTx.outputsData,
234
280
  inputs: preTx.inputs,
235
- outputs: [
281
+ outputs: outputIndex === 0
282
+ ? [
283
+ {
284
+ lock,
285
+ type: ckbfsTypeScript,
286
+ capacity: preTx.outputs[outputIndex].capacity,
287
+ },
288
+ ]
289
+ : [
290
+ ...preTx.outputs.slice(0, outputIndex), // Include rest of outputs (e.g., change)
291
+ {
292
+ lock,
293
+ type: ckbfsTypeScript,
294
+ capacity: preTx.outputs[outputIndex].capacity,
295
+ },
296
+ ],
297
+ });
298
+
299
+ return {tx, outputIndex, emptyTypeID: args === "0x0000000000000000000000000000000000000000000000000000000000000000"};
300
+ }
301
+
302
+ /**
303
+ * Creates a transaction for publishing a file to CKBFS
304
+ * @param signer The signer to use for the transaction
305
+ * @param options Options for publishing the file
306
+ * @returns Promise resolving to the created transaction
307
+ */
308
+ export async function createPublishTransaction(
309
+ signer: Signer,
310
+ options: PublishOptions,
311
+ ): Promise<Transaction> {
312
+ const {
313
+ feeRate,
314
+ lock,
315
+ } = options;
316
+
317
+ // Use preparePublishTransaction to create the base transaction
318
+ const { tx: preTx, outputIndex, emptyTypeID } = await preparePublishTransaction(options);
319
+
320
+ // Complete inputs by capacity
321
+ await preTx.completeInputsByCapacity(signer);
322
+
323
+ // Complete fee change to lock
324
+ await preTx.completeFeeChangeToLock(signer, lock, feeRate || 2000);
325
+
326
+ // If emptyTypeID is true, we need to create the proper type ID args
327
+ if (emptyTypeID) {
328
+ // Get CKBFS script config
329
+ const config = getCKBFSScriptConfig(options.network || DEFAULT_NETWORK, options.version || DEFAULT_VERSION, options.useTypeID || false);
330
+
331
+ // Create type ID args
332
+ const args = ccc.hashTypeId(preTx.inputs[0], outputIndex);
333
+
334
+ // Create CKBFS type script with type ID
335
+ const ckbfsTypeScript = new Script(
336
+ ensureHexPrefix(config.codeHash),
337
+ config.hashType as any,
338
+ args,
339
+ );
340
+
341
+ // Create final transaction with updated type script
342
+ const tx = Transaction.from({
343
+ cellDeps: preTx.cellDeps,
344
+ witnesses: preTx.witnesses,
345
+ outputsData: preTx.outputsData,
346
+ inputs: preTx.inputs,
347
+ outputs: preTx.outputs.map((output, index) =>
348
+ index === outputIndex
349
+ ? {
350
+ lock,
351
+ type: ckbfsTypeScript,
352
+ capacity: output.capacity,
353
+ }
354
+ : output
355
+ ),
356
+ });
357
+
358
+ return tx;
359
+ } else {
360
+ // If typeID was already set properly, just reset the first witness for signing
361
+ const tx = Transaction.from({
362
+ cellDeps: preTx.cellDeps,
363
+ witnesses: preTx.witnesses,
364
+ outputsData: preTx.outputsData,
365
+ inputs: preTx.inputs,
366
+ outputs: preTx.outputs,
367
+ });
368
+
369
+ return tx;
370
+ }
371
+ }
372
+
373
+ /**
374
+ * Prepares a transaction for appending content to a CKBFS file without fee and change handling
375
+ * @param options Options for appending content
376
+ * @returns Promise resolving to the prepared transaction and the output index of CKBFS Cell
377
+ */
378
+ export async function prepareAppendTransaction(
379
+ options: AppendOptions,
380
+ ): Promise<{tx: Transaction, outputIndex: number}> {
381
+ const {
382
+ from,
383
+ ckbfsCell,
384
+ contentChunks,
385
+ network = DEFAULT_NETWORK,
386
+ version = DEFAULT_VERSION,
387
+ } = options;
388
+ const { outPoint, data, type, lock, capacity } = ckbfsCell;
389
+
390
+ // Get CKBFS script config early to use version info
391
+ const config = getCKBFSScriptConfig(network, version);
392
+
393
+ // Create CKBFS witnesses - each chunk already includes the CKBFS header
394
+ // Pass 0 as version byte - this is the protocol version byte in the witness header
395
+ // not to be confused with the Protocol Version (V1 vs V2)
396
+ const ckbfsWitnesses = createChunkedCKBFSWitnesses(contentChunks);
397
+
398
+ // Combine the new content chunks for checksum calculation
399
+ const combinedContent = Buffer.concat(contentChunks);
400
+
401
+ // Update the existing checksum with the new content - this matches Adler32's
402
+ // cumulative nature as required by Rule 11 in the RFC
403
+ const contentChecksum = await updateChecksum(data.checksum, combinedContent);
404
+
405
+ // Calculate the actual witness indices where our content is placed
406
+ const contentStartIndex = from?.witnesses.length || 1;
407
+ const witnessIndices = Array.from(
408
+ { length: contentChunks.length },
409
+ (_, i) => contentStartIndex + i,
410
+ );
411
+
412
+ // Create backlink for the current state based on version
413
+ let newBackLink: any;
414
+
415
+ if (version === ProtocolVersion.V1) {
416
+ // V1 format: Use index field (single number)
417
+ newBackLink = {
418
+ // In V1, field order is index, checksum, txHash
419
+ // and index is a single number value, not an array
420
+ index:
421
+ data.index ||
422
+ (data.indexes && data.indexes.length > 0 ? data.indexes[0] : 0),
423
+ checksum: data.checksum,
424
+ txHash: outPoint.txHash,
425
+ };
426
+ } else {
427
+ // V2 format: Use indexes field (array of numbers)
428
+ newBackLink = {
429
+ // In V2, field order is indexes, checksum, txHash
430
+ // and indexes is an array of numbers
431
+ indexes: data.indexes || (data.index ? [data.index] : []),
432
+ checksum: data.checksum,
433
+ txHash: outPoint.txHash,
434
+ };
435
+ }
436
+
437
+ // Update backlinks - add the new one to the existing backlinks array
438
+ const backLinks = [...(data.backLinks || []), newBackLink];
439
+
440
+ // Define output data based on version
441
+ let outputData: Uint8Array;
442
+
443
+ if (version === ProtocolVersion.V1) {
444
+ // In V1, index is a single number, not an array
445
+ // The first witness index is used (V1 can only reference one witness)
446
+ outputData = CKBFSData.pack(
236
447
  {
237
- lock,
238
- type: ckbfsTypeScript,
239
- capacity: preTx.outputs[0].capacity,
448
+ index: witnessIndices[0], // Use only the first index as a number
449
+ checksum: contentChecksum,
450
+ contentType: data.contentType,
451
+ filename: data.filename,
452
+ backLinks,
240
453
  },
241
- ...preTx.outputs.slice(1) // Include rest of outputs (e.g., change)
242
- ]
454
+ ProtocolVersion.V1,
455
+ ); // Explicitly use V1 for packing
456
+ } else {
457
+ // In V2, indexes is an array of witness indices
458
+ outputData = CKBFSData.pack(
459
+ {
460
+ indexes: witnessIndices,
461
+ checksum: contentChecksum,
462
+ contentType: data.contentType,
463
+ filename: data.filename,
464
+ backLinks,
465
+ },
466
+ ProtocolVersion.V2,
467
+ ); // Explicitly use V2 for packing
468
+ }
469
+
470
+ // Calculate the required capacity for the output cell
471
+ // This accounts for:
472
+ // 1. The output data size
473
+ // 2. The type script's occupied size
474
+ // 3. The lock script's occupied size
475
+ // 4. A constant of 8 bytes (for header overhead)
476
+ const ckbfsCellSize =
477
+ BigInt(outputData.length + type.occupiedSize + lock.occupiedSize + 8) *
478
+ 100000000n;
479
+
480
+ // Use the maximum value between calculated size and original capacity
481
+ // to ensure we have enough capacity but don't decrease capacity unnecessarily
482
+ const outputCapacity = ckbfsCellSize > capacity ? ckbfsCellSize : capacity;
483
+
484
+ // Create initial transaction with the CKBFS cell input
485
+ let preTx: Transaction;
486
+ if (from) {
487
+ // If from is not empty, inject/merge the fields
488
+ preTx = Transaction.from({
489
+ ...from,
490
+ inputs: from.inputs.length === 0
491
+ ? [
492
+ {
493
+ previousOutput: {
494
+ txHash: outPoint.txHash,
495
+ index: outPoint.index,
496
+ },
497
+ since: "0x0",
498
+ },
499
+ ]
500
+ : [
501
+ ...from.inputs,
502
+ {
503
+ previousOutput: {
504
+ txHash: outPoint.txHash,
505
+ index: outPoint.index,
506
+ },
507
+ since: "0x0",
508
+ },
509
+ ],
510
+ outputs: from.outputs.length === 0
511
+ ? [
512
+ {
513
+ lock,
514
+ type,
515
+ capacity: outputCapacity,
516
+ },
517
+ ]
518
+ : [
519
+ ...from.outputs,
520
+ {
521
+ lock,
522
+ type,
523
+ capacity: outputCapacity,
524
+ },
525
+ ],
526
+ outputsData: from.outputsData.length === 0
527
+ ? [outputData]
528
+ : [
529
+ ...from.outputsData,
530
+ outputData,
531
+ ],
532
+ witnesses: from.witnesses.length === 0
533
+ ? [
534
+ [], // Empty secp witness for signing if not provided
535
+ ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
536
+ ]
537
+ : [
538
+ ...from.witnesses,
539
+ ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
540
+ ],
541
+ });
542
+ } else {
543
+ preTx = Transaction.from({
544
+ inputs: [
545
+ {
546
+ previousOutput: {
547
+ txHash: outPoint.txHash,
548
+ index: outPoint.index,
549
+ },
550
+ since: "0x0",
551
+ },
552
+ ],
553
+ outputs: [
554
+ {
555
+ lock,
556
+ type,
557
+ capacity: outputCapacity,
558
+ },
559
+ ],
560
+ outputsData: [outputData],
561
+ witnesses: [
562
+ [], // Empty secp witness for signing
563
+ ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
564
+ ],
565
+ });
566
+ }
567
+
568
+ // Add the CKBFS dep group cell dependency
569
+ preTx.addCellDeps({
570
+ outPoint: {
571
+ txHash: ensureHexPrefix(config.depTxHash),
572
+ index: config.depIndex || 0,
573
+ },
574
+ depType: "depGroup",
243
575
  });
244
-
245
- return tx;
576
+
577
+ const outputIndex = from ? from.outputs.length : 0;
578
+
579
+ return {tx: preTx, outputIndex};
246
580
  }
247
581
 
248
582
  /**
@@ -252,34 +586,88 @@ export async function createPublishTransaction(
252
586
  * @returns Promise resolving to the created transaction
253
587
  */
254
588
  export async function createAppendTransaction(
255
- signer: Signer,
256
- options: AppendOptions
589
+ signer: Signer,
590
+ options: AppendOptions,
257
591
  ): Promise<Transaction> {
258
- const {
259
- ckbfsCell,
260
- contentChunks,
592
+ const {
593
+ ckbfsCell,
261
594
  feeRate,
595
+ } = options;
596
+ const { lock } = ckbfsCell;
597
+
598
+ // Use prepareAppendTransaction to create the base transaction
599
+ const { tx: preTx, outputIndex } = await prepareAppendTransaction(options);
600
+
601
+ // Get the recommended address to ensure lock script cell deps are included
602
+ const address = await signer.getRecommendedAddressObj();
603
+
604
+ const inputsBefore = preTx.inputs.length;
605
+ // If we need more capacity than the original cell had, add additional inputs
606
+ if (preTx.outputs[outputIndex].capacity > ckbfsCell.capacity) {
607
+ console.log(
608
+ `Need additional capacity: ${preTx.outputs[outputIndex].capacity - ckbfsCell.capacity} shannons`,
609
+ );
610
+ // Add more inputs to cover the increased capacity
611
+ await preTx.completeInputsByCapacity(signer);
612
+ }
613
+
614
+ const witnesses: any = [];
615
+ // add empty witness for signer if ckbfs's lock is the same as signer's lock
616
+ if (address.script.hash() === lock.hash()) {
617
+ witnesses.push("0x");
618
+ }
619
+ // add ckbfs witnesses (skip the first witness which is for signing)
620
+ witnesses.push(...preTx.witnesses.slice(1));
621
+
622
+ // Add empty witnesses for additional signer inputs
623
+ // This is to ensure that the transaction is valid and can be signed
624
+ for (let i = inputsBefore; i < preTx.inputs.length; i++) {
625
+ witnesses.push("0x");
626
+ }
627
+ preTx.witnesses = witnesses;
628
+
629
+ // Complete fee
630
+ await preTx.completeFeeChangeToLock(signer, address.script, feeRate || 2000);
631
+
632
+ return preTx;
633
+ }
634
+
635
+ /**
636
+ * Creates a transaction for appending content to a CKBFS file
637
+ * @param signer The signer to use for the transaction
638
+ * @param options Options for appending content
639
+ * @returns Promise resolving to the created transaction
640
+ */
641
+ export async function createAppendTransactionDry(
642
+ signer: Signer,
643
+ options: AppendOptions,
644
+ ): Promise<Transaction> {
645
+ const {
646
+ ckbfsCell,
647
+ contentChunks,
262
648
  network = DEFAULT_NETWORK,
263
- version = DEFAULT_VERSION
649
+ version = DEFAULT_VERSION,
264
650
  } = options;
265
651
  const { outPoint, data, type, lock, capacity } = ckbfsCell;
266
-
652
+
267
653
  // Get CKBFS script config early to use version info
268
654
  const config = getCKBFSScriptConfig(network, version);
269
-
655
+
270
656
  // Create CKBFS witnesses - each chunk already includes the CKBFS header
271
657
  // Pass 0 as version byte - this is the protocol version byte in the witness header
272
658
  // not to be confused with the Protocol Version (V1 vs V2)
273
659
  const ckbfsWitnesses = createChunkedCKBFSWitnesses(contentChunks);
274
-
660
+
275
661
  // Combine the new content chunks for checksum calculation
276
662
  const combinedContent = Buffer.concat(contentChunks);
277
-
663
+
278
664
  // Update the existing checksum with the new content - this matches Adler32's
279
665
  // cumulative nature as required by Rule 11 in the RFC
280
666
  const contentChecksum = await updateChecksum(data.checksum, combinedContent);
281
- console.log(`Updated checksum from ${data.checksum} to ${contentChecksum} for appended content`);
282
-
667
+ console.log(
668
+ `Updated checksum from ${data.checksum} to ${contentChecksum} for appended content`,
669
+ );
670
+
283
671
  // Get the recommended address to ensure lock script cell deps are included
284
672
  const address = await signer.getRecommendedAddressObj();
285
673
 
@@ -288,19 +676,21 @@ export async function createAppendTransaction(
288
676
  // else CKBFS data starts at index 0
289
677
  const contentStartIndex = address.script.hash() === lock.hash() ? 1 : 0;
290
678
  const witnessIndices = Array.from(
291
- { length: contentChunks.length },
292
- (_, i) => contentStartIndex + i
679
+ { length: contentChunks.length },
680
+ (_, i) => contentStartIndex + i,
293
681
  );
294
-
682
+
295
683
  // Create backlink for the current state based on version
296
684
  let newBackLink: any;
297
-
685
+
298
686
  if (version === ProtocolVersion.V1) {
299
687
  // V1 format: Use index field (single number)
300
688
  newBackLink = {
301
689
  // In V1, field order is index, checksum, txHash
302
690
  // and index is a single number value, not an array
303
- index: data.index || (data.indexes && data.indexes.length > 0 ? data.indexes[0] : 0),
691
+ index:
692
+ data.index ||
693
+ (data.indexes && data.indexes.length > 0 ? data.indexes[0] : 0),
304
694
  checksum: data.checksum,
305
695
  txHash: outPoint.txHash,
306
696
  };
@@ -314,51 +704,55 @@ export async function createAppendTransaction(
314
704
  txHash: outPoint.txHash,
315
705
  };
316
706
  }
317
-
707
+
318
708
  // Update backlinks - add the new one to the existing backlinks array
319
709
  const backLinks = [...(data.backLinks || []), newBackLink];
320
-
710
+
321
711
  // Define output data based on version
322
712
  let outputData: Uint8Array;
323
-
713
+
324
714
  if (version === ProtocolVersion.V1) {
325
715
  // In V1, index is a single number, not an array
326
716
  // The first witness index is used (V1 can only reference one witness)
327
- outputData = CKBFSData.pack({
328
- index: witnessIndices[0], // Use only the first index as a number
329
- checksum: contentChecksum,
330
- contentType: data.contentType,
331
- filename: data.filename,
332
- backLinks,
333
- }, ProtocolVersion.V1); // Explicitly use V1 for packing
717
+ outputData = CKBFSData.pack(
718
+ {
719
+ index: witnessIndices[0], // Use only the first index as a number
720
+ checksum: contentChecksum,
721
+ contentType: data.contentType,
722
+ filename: data.filename,
723
+ backLinks,
724
+ },
725
+ ProtocolVersion.V1,
726
+ ); // Explicitly use V1 for packing
334
727
  } else {
335
728
  // In V2, indexes is an array of witness indices
336
- outputData = CKBFSData.pack({
337
- indexes: witnessIndices,
338
- checksum: contentChecksum,
339
- contentType: data.contentType,
340
- filename: data.filename,
341
- backLinks,
342
- }, ProtocolVersion.V2); // Explicitly use V2 for packing
729
+ outputData = CKBFSData.pack(
730
+ {
731
+ indexes: witnessIndices,
732
+ checksum: contentChecksum,
733
+ contentType: data.contentType,
734
+ filename: data.filename,
735
+ backLinks,
736
+ },
737
+ ProtocolVersion.V2,
738
+ ); // Explicitly use V2 for packing
343
739
  }
344
-
345
- // Pack the original data to get its size - use the appropriate version
346
- const originalData = CKBFSData.pack(data, version);
347
- const originalDataSize = originalData.length;
348
-
349
- // Get sizes and calculate capacity requirements
350
- const newDataSize = outputData.length;
351
-
740
+
741
+
352
742
  // Calculate the required capacity for the output cell
353
- // This accounts for:
743
+ // This accounts for:
354
744
  // 1. The output data size
355
745
  // 2. The type script's occupied size
356
746
  // 3. The lock script's occupied size
357
747
  // 4. A constant of 8 bytes (for header overhead)
358
- const ckbfsCellSize = BigInt(outputData.length + type.occupiedSize + lock.occupiedSize + 8) * 100000000n;
359
-
360
- console.log(`Original capacity: ${capacity}, Calculated size: ${ckbfsCellSize}, Data size: ${outputData.length}`);
361
-
748
+ const ckbfsCellSize =
749
+ BigInt(outputData.length + type.occupiedSize + lock.occupiedSize + 8) *
750
+ 100000000n;
751
+
752
+ console.log(
753
+ `Original capacity: ${capacity}, Calculated size: ${ckbfsCellSize}, Data size: ${outputData.length}`,
754
+ );
755
+
362
756
  // Use the maximum value between calculated size and original capacity
363
757
  // to ensure we have enough capacity but don't decrease capacity unnecessarily
364
758
  const outputCapacity = ckbfsCellSize > capacity ? ckbfsCellSize : capacity;
@@ -372,56 +766,57 @@ export async function createAppendTransaction(
372
766
  index: outPoint.index,
373
767
  },
374
768
  since: "0x0",
375
- }
769
+ },
376
770
  ],
377
771
  outputs: [
378
772
  {
379
773
  lock,
380
774
  type,
381
775
  capacity: outputCapacity,
382
- }
776
+ },
383
777
  ],
384
- outputsData: [
385
- outputData,
386
- ]
778
+ outputsData: [outputData],
387
779
  });
388
780
 
389
-
390
781
  // Add the CKBFS dep group cell dependency
391
782
  tx.addCellDeps({
392
783
  outPoint: {
393
784
  txHash: ensureHexPrefix(config.depTxHash),
394
785
  index: config.depIndex || 0,
395
786
  },
396
- depType: "depGroup"
787
+ depType: "depGroup",
397
788
  });
398
-
789
+
399
790
  const inputsBefore = tx.inputs.length;
400
- // If we need more capacity than the original cell had, add additional inputs
401
- if (outputCapacity > capacity) {
402
- console.log(`Need additional capacity: ${outputCapacity - capacity} shannons`);
403
- // Add more inputs to cover the increased capacity
404
- await tx.completeInputsByCapacity(signer);
405
- }
791
+ // // If we need more capacity than the original cell had, add additional inputs
792
+ // if (outputCapacity > capacity) {
793
+ // console.log(
794
+ // `Need additional capacity: ${outputCapacity - capacity} shannons`,
795
+ // );
796
+ // // Add more inputs to cover the increased capacity
797
+ // await tx.completeInputsByCapacity(signer);
798
+ // }
406
799
 
407
- const witnesses: any = []
800
+ const witnesses: any = [];
408
801
  // add empty witness for signer if ckbfs's lock is the same as signer's lock
409
- if(address.script.hash() === lock.hash()) {
410
- witnesses.push('0x')
802
+ if (address.script.hash() === lock.hash()) {
803
+ witnesses.push("0x");
411
804
  }
412
805
  // add ckbfs witnesses
413
- witnesses.push(...ckbfsWitnesses.map(w => `0x${Buffer.from(w).toString('hex')}`))
806
+ witnesses.push(
807
+ ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
808
+ );
414
809
 
415
810
  // Add empty witnesses for signer's input
416
811
  // This is to ensure that the transaction is valid and can be signed
417
- for(let i = inputsBefore; i < tx.inputs.length; i++) {
418
- witnesses.push('0x')
812
+ for (let i = inputsBefore; i < tx.inputs.length; i++) {
813
+ witnesses.push("0x");
419
814
  }
420
- tx.witnesses = witnesses
815
+ tx.witnesses = witnesses;
421
816
 
422
817
  // Complete fee
423
- await tx.completeFeeChangeToLock(signer, address.script, feeRate || 2000);
424
-
818
+ //await tx.completeFeeChangeToLock(signer, address.script, feeRate || 2000);
819
+
425
820
  return tx;
426
821
  }
427
822
 
@@ -432,8 +827,8 @@ export async function createAppendTransaction(
432
827
  * @returns Promise resolving to the signed transaction
433
828
  */
434
829
  export async function publishCKBFS(
435
- signer: Signer,
436
- options: PublishOptions
830
+ signer: Signer,
831
+ options: PublishOptions,
437
832
  ): Promise<Transaction> {
438
833
  const tx = await createPublishTransaction(signer, options);
439
834
  return signer.signTransaction(tx);
@@ -446,9 +841,9 @@ export async function publishCKBFS(
446
841
  * @returns Promise resolving to the signed transaction
447
842
  */
448
843
  export async function appendCKBFS(
449
- signer: Signer,
450
- options: AppendOptions
844
+ signer: Signer,
845
+ options: AppendOptions,
451
846
  ): Promise<Transaction> {
452
847
  const tx = await createAppendTransaction(signer, options);
453
848
  return signer.signTransaction(tx);
454
- }
849
+ }