@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
@@ -1,849 +1,40 @@
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
- ProtocolVersionType,
10
- DEFAULT_NETWORK,
11
- DEFAULT_VERSION,
12
- } from "./constants";
13
-
14
- /**
15
- * Utility functions for CKB transaction creation and handling
16
- */
17
-
18
- /**
19
- * Options for creating a CKBFS cell
20
- */
21
- export interface CKBFSCellOptions {
22
- contentType: string;
23
- filename: string;
24
- capacity?: bigint;
25
- lock: Script;
26
- network?: NetworkType;
27
- version?: ProtocolVersionType;
28
- useTypeID?: boolean;
29
- }
30
-
31
- /**
32
- * Options for publishing a file to CKBFS
33
- */
34
- export interface PublishOptions extends CKBFSCellOptions {
35
- contentChunks: Uint8Array[];
36
- feeRate?: number;
37
- from?: Transaction;
38
- }
39
-
40
- /**
41
- * Options for appending content to a CKBFS file
42
- */
43
- export interface AppendOptions {
44
- ckbfsCell: {
45
- outPoint: { txHash: string; index: number };
46
- data: CKBFSDataType;
47
- type: Script;
48
- lock: Script;
49
- capacity: bigint;
50
- };
51
- contentChunks: Uint8Array[];
52
- feeRate?: number;
53
- network?: NetworkType;
54
- version?: ProtocolVersionType;
55
- from?: Transaction;
56
- }
57
-
58
- /**
59
- * Ensures a string is prefixed with '0x'
60
- * @param value The string to ensure is hex prefixed
61
- * @returns A hex prefixed string
62
- */
63
- export function ensureHexPrefix(value: string): `0x${string}` {
64
- if (value.startsWith("0x")) {
65
- return value as `0x${string}`;
66
- }
67
- return `0x${value}` as `0x${string}`;
68
- }
69
-
70
- /**
71
- * Creates a CKBFS cell
72
- * @param options Options for creating the CKBFS cell
73
- * @returns The created cell output
74
- */
75
- export function createCKBFSCell(options: CKBFSCellOptions) {
76
- const {
77
- contentType,
78
- filename,
79
- capacity,
80
- lock,
81
- network = DEFAULT_NETWORK,
82
- version = DEFAULT_VERSION,
83
- useTypeID = false,
84
- } = options;
85
-
86
- // Get CKBFS script config
87
- const config = getCKBFSScriptConfig(network, version, useTypeID);
88
-
89
- // Create pre CKBFS type script
90
- const preCkbfsTypeScript = new Script(
91
- ensureHexPrefix(config.codeHash),
92
- config.hashType as any,
93
- "0x0000000000000000000000000000000000000000000000000000000000000000",
94
- );
95
-
96
- // Return the cell output
97
- return {
98
- lock,
99
- type: preCkbfsTypeScript,
100
- capacity: capacity || 200n * 100000000n, // Default 200 CKB
101
- };
102
- }
103
-
104
- /**
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
107
- * @param options Options for publishing the file
108
- * @returns Promise resolving to the prepared transaction and the output index of CKBFS Cell
109
- */
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,
119
- capacity,
120
- network = DEFAULT_NETWORK,
121
- version = DEFAULT_VERSION,
122
- useTypeID = false,
123
- } = options;
124
-
125
- // Calculate checksum for the combined content
126
- const combinedContent = Buffer.concat(contentChunks);
127
- const checksum = await calculateChecksum(combinedContent);
128
-
129
- // Create CKBFS witnesses - each chunk already includes the CKBFS header
130
- // Pass 0 as version byte - this is the protocol version byte in the witness header
131
- // not to be confused with the Protocol Version (V1 vs V2)
132
- const ckbfsWitnesses = createChunkedCKBFSWitnesses(contentChunks);
133
-
134
- // Calculate the actual witness indices where our content is placed
135
-
136
- const contentStartIndex = from?.witnesses.length || 1;
137
- const witnessIndices = Array.from(
138
- { length: contentChunks.length },
139
- (_, i) => contentStartIndex + i,
140
- );
141
-
142
- // Create CKBFS cell output data based on version
143
- let outputData: Uint8Array;
144
-
145
- if (version === ProtocolVersion.V1) {
146
- // V1 format: Single index field (a single number, not an array)
147
- // For V1, use the first index where content is placed
148
- outputData = CKBFSData.pack(
149
- {
150
- index: contentStartIndex,
151
- checksum,
152
- contentType: contentType,
153
- filename: filename,
154
- backLinks: [],
155
- },
156
- version,
157
- );
158
- } else {
159
- // V2 format: Multiple indexes (array of numbers)
160
- // For V2, use all the indices where content is placed
161
- outputData = CKBFSData.pack(
162
- {
163
- indexes: witnessIndices,
164
- checksum,
165
- contentType,
166
- filename,
167
- backLinks: [],
168
- },
169
- version,
170
- );
171
- }
172
-
173
- // Get CKBFS script config
174
- const config = getCKBFSScriptConfig(network, version, useTypeID);
175
-
176
- const preCkbfsTypeScript = new Script(
177
- ensureHexPrefix(config.codeHash),
178
- config.hashType as any,
179
- "0x0000000000000000000000000000000000000000000000000000000000000000",
180
- );
181
- const ckbfsCellSize =
182
- BigInt(
183
- outputData.length +
184
- preCkbfsTypeScript.occupiedSize +
185
- lock.occupiedSize +
186
- 8,
187
- ) * 100000000n;
188
- // Create pre transaction without cell deps initially
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
-
255
- // Add the CKBFS dep group cell dependency
256
- preTx.addCellDeps({
257
- outPoint: {
258
- txHash: ensureHexPrefix(config.depTxHash),
259
- index: config.depIndex || 0,
260
- },
261
- depType: "depGroup",
262
- });
263
-
264
- // Create type ID args
265
- const outputIndex = from ? from.outputs.length : 0;
266
- const args = preTx.inputs.length > 0 ? ccc.hashTypeId(preTx.inputs[0], outputIndex) : "0x0000000000000000000000000000000000000000000000000000000000000000";
267
-
268
- // Create CKBFS type script with type ID
269
- const ckbfsTypeScript = new Script(
270
- ensureHexPrefix(config.codeHash),
271
- config.hashType as any,
272
- args,
273
- );
274
-
275
- // Create final transaction with same cell deps as preTx
276
- const tx = Transaction.from({
277
- cellDeps: preTx.cellDeps,
278
- witnesses: preTx.witnesses,
279
- outputsData: preTx.outputsData,
280
- inputs: preTx.inputs,
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
1
  /**
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(
447
- {
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,
453
- },
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",
575
- });
576
-
577
- const outputIndex = from ? from.outputs.length : 0;
578
-
579
- return {tx: preTx, outputIndex};
580
- }
581
-
582
- /**
583
- * Creates a transaction for appending content to a CKBFS file
584
- * @param signer The signer to use for the transaction
585
- * @param options Options for appending content
586
- * @returns Promise resolving to the created transaction
587
- */
588
- export async function createAppendTransaction(
589
- signer: Signer,
590
- options: AppendOptions,
591
- ): Promise<Transaction> {
592
- const {
593
- ckbfsCell,
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,
648
- network = DEFAULT_NETWORK,
649
- version = DEFAULT_VERSION,
650
- } = options;
651
- const { outPoint, data, type, lock, capacity } = ckbfsCell;
652
-
653
- // Get CKBFS script config early to use version info
654
- const config = getCKBFSScriptConfig(network, version);
655
-
656
- // Create CKBFS witnesses - each chunk already includes the CKBFS header
657
- // Pass 0 as version byte - this is the protocol version byte in the witness header
658
- // not to be confused with the Protocol Version (V1 vs V2)
659
- const ckbfsWitnesses = createChunkedCKBFSWitnesses(contentChunks);
660
-
661
- // Combine the new content chunks for checksum calculation
662
- const combinedContent = Buffer.concat(contentChunks);
663
-
664
- // Update the existing checksum with the new content - this matches Adler32's
665
- // cumulative nature as required by Rule 11 in the RFC
666
- const contentChecksum = await updateChecksum(data.checksum, combinedContent);
667
- console.log(
668
- `Updated checksum from ${data.checksum} to ${contentChecksum} for appended content`,
669
- );
670
-
671
- // Get the recommended address to ensure lock script cell deps are included
672
- const address = await signer.getRecommendedAddressObj();
673
-
674
- // Calculate the actual witness indices where our content is placed
675
- // CKBFS data starts at index 1 if signer's lock script is the same as ckbfs's lock script
676
- // else CKBFS data starts at index 0
677
- const contentStartIndex = address.script.hash() === lock.hash() ? 1 : 0;
678
- const witnessIndices = Array.from(
679
- { length: contentChunks.length },
680
- (_, i) => contentStartIndex + i,
681
- );
682
-
683
- // Create backlink for the current state based on version
684
- let newBackLink: any;
685
-
686
- if (version === ProtocolVersion.V1) {
687
- // V1 format: Use index field (single number)
688
- newBackLink = {
689
- // In V1, field order is index, checksum, txHash
690
- // and index is a single number value, not an array
691
- index:
692
- data.index ||
693
- (data.indexes && data.indexes.length > 0 ? data.indexes[0] : 0),
694
- checksum: data.checksum,
695
- txHash: outPoint.txHash,
696
- };
697
- } else {
698
- // V2 format: Use indexes field (array of numbers)
699
- newBackLink = {
700
- // In V2, field order is indexes, checksum, txHash
701
- // and indexes is an array of numbers
702
- indexes: data.indexes || (data.index ? [data.index] : []),
703
- checksum: data.checksum,
704
- txHash: outPoint.txHash,
705
- };
706
- }
707
-
708
- // Update backlinks - add the new one to the existing backlinks array
709
- const backLinks = [...(data.backLinks || []), newBackLink];
710
-
711
- // Define output data based on version
712
- let outputData: Uint8Array;
713
-
714
- if (version === ProtocolVersion.V1) {
715
- // In V1, index is a single number, not an array
716
- // The first witness index is used (V1 can only reference one witness)
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
727
- } else {
728
- // In V2, indexes is an array of witness indices
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
739
- }
740
-
741
-
742
- // Calculate the required capacity for the output cell
743
- // This accounts for:
744
- // 1. The output data size
745
- // 2. The type script's occupied size
746
- // 3. The lock script's occupied size
747
- // 4. A constant of 8 bytes (for header overhead)
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
-
756
- // Use the maximum value between calculated size and original capacity
757
- // to ensure we have enough capacity but don't decrease capacity unnecessarily
758
- const outputCapacity = ckbfsCellSize > capacity ? ckbfsCellSize : capacity;
759
-
760
- // Create initial transaction with the CKBFS cell input
761
- const tx = Transaction.from({
762
- inputs: [
763
- {
764
- previousOutput: {
765
- txHash: outPoint.txHash,
766
- index: outPoint.index,
767
- },
768
- since: "0x0",
769
- },
770
- ],
771
- outputs: [
772
- {
773
- lock,
774
- type,
775
- capacity: outputCapacity,
776
- },
777
- ],
778
- outputsData: [outputData],
779
- });
780
-
781
- // Add the CKBFS dep group cell dependency
782
- tx.addCellDeps({
783
- outPoint: {
784
- txHash: ensureHexPrefix(config.depTxHash),
785
- index: config.depIndex || 0,
786
- },
787
- depType: "depGroup",
788
- });
789
-
790
- const inputsBefore = tx.inputs.length;
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
- // }
799
-
800
- const witnesses: any = [];
801
- // add empty witness for signer if ckbfs's lock is the same as signer's lock
802
- if (address.script.hash() === lock.hash()) {
803
- witnesses.push("0x");
804
- }
805
- // add ckbfs witnesses
806
- witnesses.push(
807
- ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
808
- );
809
-
810
- // Add empty witnesses for signer's input
811
- // This is to ensure that the transaction is valid and can be signed
812
- for (let i = inputsBefore; i < tx.inputs.length; i++) {
813
- witnesses.push("0x");
814
- }
815
- tx.witnesses = witnesses;
816
-
817
- // Complete fee
818
- //await tx.completeFeeChangeToLock(signer, address.script, feeRate || 2000);
819
-
820
- return tx;
821
- }
822
-
823
- /**
824
- * Creates a complete transaction for publishing a file to CKBFS
825
- * @param signer The signer to use for the transaction
826
- * @param options Options for publishing the file
827
- * @returns Promise resolving to the signed transaction
828
- */
829
- export async function publishCKBFS(
830
- signer: Signer,
831
- options: PublishOptions,
832
- ): Promise<Transaction> {
833
- const tx = await createPublishTransaction(signer, options);
834
- return signer.signTransaction(tx);
835
- }
836
-
837
- /**
838
- * Creates a complete transaction for appending content to a CKBFS file
839
- * @param signer The signer to use for the transaction
840
- * @param options Options for appending content
841
- * @returns Promise resolving to the signed transaction
842
- */
843
- export async function appendCKBFS(
844
- signer: Signer,
845
- options: AppendOptions,
846
- ): Promise<Transaction> {
847
- const tx = await createAppendTransaction(signer, options);
848
- return signer.signTransaction(tx);
849
- }
2
+ * Legacy transaction utilities - re-exports from version-specific modules
3
+ * This file maintains backward compatibility while the actual implementation
4
+ * has been moved to version-specific files in the transactions/ directory
5
+ */
6
+
7
+ // Re-export shared utilities
8
+ export { ensureHexPrefix, CKBFSCellOptions } from "./transactions/shared";
9
+
10
+ // Re-export V1/V2 utilities for backward compatibility
11
+ export {
12
+ PublishOptions,
13
+ AppendOptions,
14
+ createCKBFSCell,
15
+ preparePublishTransaction,
16
+ createPublishTransaction,
17
+ prepareAppendTransaction,
18
+ createAppendTransaction,
19
+ createAppendTransactionDry,
20
+ publishCKBFS,
21
+ appendCKBFS,
22
+ } from "./transactions/v1v2";
23
+
24
+ // Re-export V3 utilities
25
+ export {
26
+ PublishV3Options,
27
+ AppendV3Options,
28
+ TransferV3Options,
29
+ createCKBFSV3Cell,
30
+ preparePublishV3Transaction,
31
+ createPublishV3Transaction,
32
+ createAppendV3Transaction,
33
+ createTransferV3Transaction,
34
+ publishCKBFSV3,
35
+ appendCKBFSV3,
36
+ transferCKBFSV3,
37
+ } from "./transactions/v3";
38
+
39
+ // Re-export all from transactions index
40
+ export * from "./transactions";