@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,564 @@
1
+ import { ccc, Transaction, Script, Signer } from "@ckb-ccc/core";
2
+ import { calculateChecksum, updateChecksum } from "../checksum";
3
+ import { CKBFSData, CKBFSDataType } from "../molecule";
4
+ import { createChunkedCKBFSV3Witnesses, CKBFSV3WitnessOptions } from "../witness";
5
+ import {
6
+ getCKBFSScriptConfig,
7
+ NetworkType,
8
+ ProtocolVersion,
9
+ DEFAULT_NETWORK,
10
+ } from "../constants";
11
+ import {
12
+ ensureHexPrefix,
13
+ BaseCKBFSCellOptions,
14
+ BasePublishOptions,
15
+ BaseAppendOptions
16
+ } from "./shared";
17
+
18
+ /**
19
+ * V3 CKBFS transaction utilities - witnesses-based storage with backlinks
20
+ */
21
+
22
+ /**
23
+ * V3-specific Options for publishing a file to CKBFS
24
+ */
25
+ export interface PublishV3Options extends Omit<BaseCKBFSCellOptions, 'version'> {
26
+ contentChunks: Uint8Array[];
27
+ feeRate?: number;
28
+ from?: Transaction;
29
+ version: typeof ProtocolVersion.V3;
30
+ }
31
+
32
+ /**
33
+ * V3-specific Options for appending content to a CKBFS file
34
+ */
35
+ export interface AppendV3Options {
36
+ ckbfsCell: {
37
+ outPoint: { txHash: string; index: number };
38
+ data: CKBFSDataType;
39
+ type: Script;
40
+ lock: Script;
41
+ capacity: bigint;
42
+ };
43
+ contentChunks: Uint8Array[];
44
+ feeRate?: number;
45
+ network?: NetworkType;
46
+ version: typeof ProtocolVersion.V3;
47
+ from?: Transaction;
48
+ previousTxHash: string;
49
+ previousWitnessIndex: number;
50
+ previousChecksum: number;
51
+ }
52
+
53
+ /**
54
+ * V3-specific Options for transferring a CKBFS file
55
+ */
56
+ export interface TransferV3Options {
57
+ ckbfsCell: {
58
+ outPoint: { txHash: string; index: number };
59
+ data: CKBFSDataType;
60
+ type: Script;
61
+ lock: Script;
62
+ capacity: bigint;
63
+ };
64
+ newLock: Script;
65
+ feeRate?: number;
66
+ network?: NetworkType;
67
+ version: typeof ProtocolVersion.V3;
68
+ from?: Transaction;
69
+ previousTxHash: string;
70
+ previousWitnessIndex: number;
71
+ previousChecksum: number;
72
+ }
73
+
74
+ /**
75
+ * Creates a CKBFS v3 cell
76
+ * @param options Options for creating the CKBFS cell
77
+ * @returns The created cell output
78
+ */
79
+ export function createCKBFSV3Cell(options: BaseCKBFSCellOptions) {
80
+ const {
81
+ contentType,
82
+ filename,
83
+ capacity,
84
+ lock,
85
+ network = DEFAULT_NETWORK,
86
+ useTypeID = false,
87
+ } = options;
88
+
89
+ // Get CKBFS script config for v3
90
+ const config = getCKBFSScriptConfig(network, ProtocolVersion.V3, useTypeID);
91
+
92
+ // Create pre CKBFS type script
93
+ const preCkbfsTypeScript = new Script(
94
+ ensureHexPrefix(config.codeHash),
95
+ config.hashType as any,
96
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
97
+ );
98
+
99
+ // Return the cell output
100
+ return {
101
+ lock,
102
+ type: preCkbfsTypeScript,
103
+ capacity: capacity || 200n * 100000000n, // Default 200 CKB
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Prepares a transaction for publishing a file to CKBFS v3 without fee and change handling
109
+ * @param options Options for publishing the file
110
+ * @returns Promise resolving to the prepared transaction and the output index of CKBFS Cell
111
+ */
112
+ export async function preparePublishV3Transaction(
113
+ options: PublishV3Options,
114
+ ): Promise<{tx: Transaction, outputIndex: number, emptyTypeID: boolean}> {
115
+ const {
116
+ from,
117
+ contentChunks,
118
+ contentType,
119
+ filename,
120
+ lock,
121
+ capacity,
122
+ network = DEFAULT_NETWORK,
123
+ useTypeID = false,
124
+ } = options;
125
+
126
+ // Calculate checksum for the combined content
127
+ const combinedContent = Buffer.concat(contentChunks);
128
+ const checksum = await calculateChecksum(combinedContent);
129
+
130
+ // V3 format uses single index (first witness containing content)
131
+ const contentStartIndex = from?.witnesses.length || 1;
132
+
133
+ // Create CKBFS v3 witnesses (no backlinks for publish operation)
134
+ const ckbfsWitnesses = createChunkedCKBFSV3Witnesses(contentChunks, {
135
+ // For publish operation, all previous fields are zero
136
+ previousTxHash: '0x' + '00'.repeat(32),
137
+ previousWitnessIndex: 0,
138
+ previousChecksum: 0,
139
+ startIndex: contentStartIndex,
140
+ });
141
+
142
+ // Create CKBFS v3 cell output data
143
+ const outputData = CKBFSData.pack(
144
+ {
145
+ index: contentStartIndex,
146
+ checksum,
147
+ contentType,
148
+ filename,
149
+ },
150
+ ProtocolVersion.V3,
151
+ );
152
+
153
+ // Get CKBFS script config
154
+ const config = getCKBFSScriptConfig(network, ProtocolVersion.V3, useTypeID);
155
+
156
+ // Create pre CKBFS type script
157
+ const preCkbfsTypeScript = new Script(
158
+ ensureHexPrefix(config.codeHash),
159
+ config.hashType as any,
160
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
161
+ );
162
+
163
+ // Calculate the required capacity for the output cell
164
+ const ckbfsCellSize =
165
+ BigInt(
166
+ outputData.length +
167
+ preCkbfsTypeScript.occupiedSize +
168
+ lock.occupiedSize +
169
+ 8,
170
+ ) * 100000000n;
171
+
172
+ // Create pre transaction without cell deps initially
173
+ let preTx: Transaction;
174
+ if(from) {
175
+ // If from is not empty, inject/merge the fields
176
+ preTx = Transaction.from({
177
+ ...from,
178
+ outputs: from.outputs.length === 0
179
+ ? [
180
+ createCKBFSV3Cell({
181
+ contentType,
182
+ filename,
183
+ lock,
184
+ network,
185
+ useTypeID,
186
+ capacity: ckbfsCellSize || capacity,
187
+ }),
188
+ ]
189
+ : [
190
+ ...from.outputs,
191
+ createCKBFSV3Cell({
192
+ contentType,
193
+ filename,
194
+ lock,
195
+ network,
196
+ useTypeID,
197
+ capacity: ckbfsCellSize || capacity,
198
+ }),
199
+ ],
200
+ witnesses: from.witnesses.length === 0
201
+ ? [
202
+ [], // Empty secp witness for signing if not provided
203
+ ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
204
+ ]
205
+ : [
206
+ ...from.witnesses,
207
+ ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
208
+ ],
209
+ outputsData: from.outputsData.length === 0
210
+ ? [outputData]
211
+ : [
212
+ ...from.outputsData,
213
+ outputData,
214
+ ],
215
+ });
216
+ } else {
217
+ preTx = Transaction.from({
218
+ outputs: [
219
+ createCKBFSV3Cell({
220
+ contentType,
221
+ filename,
222
+ lock,
223
+ network,
224
+ useTypeID,
225
+ capacity: ckbfsCellSize || capacity,
226
+ }),
227
+ ],
228
+ witnesses: [
229
+ [], // Empty secp witness for signing
230
+ ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
231
+ ],
232
+ outputsData: [outputData],
233
+ });
234
+ }
235
+
236
+ // Add the CKBFS dep group cell dependency
237
+ preTx.addCellDeps({
238
+ outPoint: {
239
+ txHash: ensureHexPrefix(config.depTxHash),
240
+ index: config.depIndex || 0,
241
+ },
242
+ depType: "depGroup",
243
+ });
244
+
245
+ // Create type ID args
246
+ const outputIndex = from ? from.outputs.length : 0;
247
+ const args = preTx.inputs.length > 0 ? ccc.hashTypeId(preTx.inputs[0], outputIndex) : "0x0000000000000000000000000000000000000000000000000000000000000000";
248
+
249
+ // Create CKBFS type script with type ID
250
+ const ckbfsTypeScript = new Script(
251
+ ensureHexPrefix(config.codeHash),
252
+ config.hashType as any,
253
+ args,
254
+ );
255
+
256
+ // Create final transaction with same cell deps as preTx
257
+ const tx = Transaction.from({
258
+ cellDeps: preTx.cellDeps,
259
+ witnesses: preTx.witnesses,
260
+ outputsData: preTx.outputsData,
261
+ inputs: preTx.inputs,
262
+ outputs: outputIndex === 0
263
+ ? [
264
+ {
265
+ lock,
266
+ type: ckbfsTypeScript,
267
+ capacity: preTx.outputs[outputIndex].capacity,
268
+ },
269
+ ]
270
+ : [
271
+ ...preTx.outputs.slice(0, outputIndex), // Include rest of outputs (e.g., change)
272
+ {
273
+ lock,
274
+ type: ckbfsTypeScript,
275
+ capacity: preTx.outputs[outputIndex].capacity,
276
+ },
277
+ ],
278
+ });
279
+
280
+ return {tx, outputIndex, emptyTypeID: args === "0x0000000000000000000000000000000000000000000000000000000000000000"};
281
+ }
282
+
283
+ /**
284
+ * Creates a transaction for publishing a file to CKBFS v3
285
+ * @param signer The signer to use for the transaction
286
+ * @param options Options for publishing the file
287
+ * @returns Promise resolving to the created transaction
288
+ */
289
+ export async function createPublishV3Transaction(
290
+ signer: Signer,
291
+ options: PublishV3Options,
292
+ ): Promise<Transaction> {
293
+ const {
294
+ feeRate,
295
+ lock,
296
+ } = options;
297
+
298
+ // Use preparePublishV3Transaction to create the base transaction
299
+ const { tx: preTx, outputIndex, emptyTypeID } = await preparePublishV3Transaction(options);
300
+
301
+ // Complete inputs by capacity
302
+ await preTx.completeInputsByCapacity(signer);
303
+
304
+ // Complete fee change to lock
305
+ await preTx.completeFeeChangeToLock(signer, lock, feeRate || 2000);
306
+
307
+ // If emptyTypeID is true, we need to create the proper type ID args
308
+ if (emptyTypeID) {
309
+ // Get CKBFS script config
310
+ const config = getCKBFSScriptConfig(options.network || DEFAULT_NETWORK, ProtocolVersion.V3, options.useTypeID || false);
311
+
312
+ // Create type ID args
313
+ const args = ccc.hashTypeId(preTx.inputs[0], outputIndex);
314
+
315
+ // Create CKBFS type script with type ID
316
+ const ckbfsTypeScript = new Script(
317
+ ensureHexPrefix(config.codeHash),
318
+ config.hashType as any,
319
+ args,
320
+ );
321
+
322
+ // Create final transaction with updated type script
323
+ const tx = Transaction.from({
324
+ cellDeps: preTx.cellDeps,
325
+ witnesses: preTx.witnesses,
326
+ outputsData: preTx.outputsData,
327
+ inputs: preTx.inputs,
328
+ outputs: preTx.outputs.map((output, index) =>
329
+ index === outputIndex
330
+ ? {
331
+ ...output,
332
+ type: ckbfsTypeScript,
333
+ }
334
+ : output
335
+ ),
336
+ });
337
+
338
+ return tx;
339
+ }
340
+
341
+ return preTx;
342
+ }
343
+
344
+ /**
345
+ * Creates a transaction for appending content to a CKBFS v3 file
346
+ * @param signer The signer to use for the transaction
347
+ * @param options Options for appending content
348
+ * @returns Promise resolving to the created transaction
349
+ */
350
+ export async function createAppendV3Transaction(
351
+ signer: Signer,
352
+ options: AppendV3Options,
353
+ ): Promise<Transaction> {
354
+ const {
355
+ ckbfsCell,
356
+ contentChunks,
357
+ feeRate,
358
+ network = DEFAULT_NETWORK,
359
+ previousTxHash,
360
+ previousWitnessIndex,
361
+ previousChecksum,
362
+ } = options;
363
+
364
+ // Calculate new checksum by updating from previous checksum
365
+ const combinedContent = Buffer.concat(contentChunks);
366
+ const newChecksum = await updateChecksum(previousChecksum, combinedContent);
367
+
368
+ // Create CKBFS v3 witnesses with backlink info
369
+ const ckbfsWitnesses = createChunkedCKBFSV3Witnesses(contentChunks, {
370
+ previousTxHash,
371
+ previousWitnessIndex,
372
+ previousChecksum,
373
+ });
374
+
375
+ // V3 format uses single index (first witness containing content)
376
+ const contentStartIndex = 1; // First witness is for signer, content starts at index 1
377
+
378
+ // Create updated CKBFS v3 cell output data
379
+ const outputData = CKBFSData.pack(
380
+ {
381
+ index: contentStartIndex,
382
+ checksum: newChecksum,
383
+ contentType: ckbfsCell.data.contentType,
384
+ filename: ckbfsCell.data.filename,
385
+ },
386
+ ProtocolVersion.V3,
387
+ );
388
+
389
+ // Get CKBFS script config
390
+ const config = getCKBFSScriptConfig(network, ProtocolVersion.V3, false);
391
+
392
+ // Calculate the required capacity for the output cell
393
+ const ckbfsCellSize =
394
+ BigInt(outputData.length + ckbfsCell.type.occupiedSize + ckbfsCell.lock.occupiedSize + 8) *
395
+ 100000000n;
396
+
397
+ // Use the maximum value between calculated size and original capacity
398
+ const outputCapacity = ckbfsCellSize > ckbfsCell.capacity ? ckbfsCellSize : ckbfsCell.capacity;
399
+
400
+ // Create transaction
401
+ const tx = Transaction.from({
402
+ inputs: [
403
+ {
404
+ previousOutput: ckbfsCell.outPoint,
405
+ since: "0x0",
406
+ },
407
+ ],
408
+ outputs: [
409
+ {
410
+ lock: ckbfsCell.lock,
411
+ type: ckbfsCell.type,
412
+ capacity: outputCapacity,
413
+ },
414
+ ],
415
+ witnesses: [
416
+ [], // Empty secp witness for signing
417
+ ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
418
+ ],
419
+ outputsData: [outputData],
420
+ });
421
+
422
+ // Add the CKBFS dep group cell dependency
423
+ tx.addCellDeps({
424
+ outPoint: {
425
+ txHash: ensureHexPrefix(config.depTxHash),
426
+ index: config.depIndex || 0,
427
+ },
428
+ depType: "depGroup",
429
+ });
430
+
431
+ // Complete inputs by capacity for fees
432
+ await tx.completeInputsByCapacity(signer);
433
+
434
+ // Complete fee change to lock
435
+ await tx.completeFeeChangeToLock(signer, ckbfsCell.lock, feeRate || 2000);
436
+
437
+ return tx;
438
+ }
439
+
440
+ /**
441
+ * Creates a transaction for transferring ownership of a CKBFS v3 file
442
+ * @param signer The signer to use for the transaction
443
+ * @param options Options for transferring the file
444
+ * @returns Promise resolving to the created transaction
445
+ */
446
+ export async function createTransferV3Transaction(
447
+ signer: Signer,
448
+ options: TransferV3Options,
449
+ ): Promise<Transaction> {
450
+ const {
451
+ ckbfsCell,
452
+ newLock,
453
+ feeRate,
454
+ network = DEFAULT_NETWORK,
455
+ previousTxHash,
456
+ previousWitnessIndex,
457
+ previousChecksum,
458
+ } = options;
459
+
460
+ // Create CKBFS v3 witness with backlink info but no content (transfer only)
461
+ const ckbfsWitnesses = createChunkedCKBFSV3Witnesses([new Uint8Array(0)], {
462
+ previousTxHash,
463
+ previousWitnessIndex,
464
+ previousChecksum,
465
+ });
466
+
467
+ // V3 format uses single index (first witness containing backlink)
468
+ const contentStartIndex = 1; // First witness is for signer, backlink at index 1
469
+
470
+ // Create CKBFS v3 cell output data (unchanged for transfer)
471
+ const outputData = CKBFSData.pack(
472
+ {
473
+ index: contentStartIndex,
474
+ checksum: ckbfsCell.data.checksum, // Checksum unchanged in transfer
475
+ contentType: ckbfsCell.data.contentType,
476
+ filename: ckbfsCell.data.filename,
477
+ },
478
+ ProtocolVersion.V3,
479
+ );
480
+
481
+ // Get CKBFS script config
482
+ const config = getCKBFSScriptConfig(network, ProtocolVersion.V3, false);
483
+
484
+ // Create transaction
485
+ const tx = Transaction.from({
486
+ inputs: [
487
+ {
488
+ previousOutput: ckbfsCell.outPoint,
489
+ since: "0x0",
490
+ },
491
+ ],
492
+ outputs: [
493
+ {
494
+ lock: newLock,
495
+ type: ckbfsCell.type,
496
+ capacity: ckbfsCell.capacity,
497
+ },
498
+ ],
499
+ witnesses: [
500
+ [], // Empty secp witness for signing
501
+ ...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
502
+ ],
503
+ outputsData: [outputData],
504
+ });
505
+
506
+ // Add the CKBFS dep group cell dependency
507
+ tx.addCellDeps({
508
+ outPoint: {
509
+ txHash: ensureHexPrefix(config.depTxHash),
510
+ index: config.depIndex || 0,
511
+ },
512
+ depType: "depGroup",
513
+ });
514
+
515
+ // Complete inputs by capacity for fees
516
+ await tx.completeInputsByCapacity(signer);
517
+
518
+ // Complete fee change to lock
519
+ await tx.completeFeeChangeToLock(signer, newLock, feeRate || 2000);
520
+
521
+ return tx;
522
+ }
523
+
524
+ /**
525
+ * Creates a complete transaction for publishing a file to CKBFS v3
526
+ * @param signer The signer to use for the transaction
527
+ * @param options Options for publishing the file
528
+ * @returns Promise resolving to the signed transaction
529
+ */
530
+ export async function publishCKBFSV3(
531
+ signer: Signer,
532
+ options: PublishV3Options,
533
+ ): Promise<Transaction> {
534
+ const tx = await createPublishV3Transaction(signer, options);
535
+ return signer.signTransaction(tx);
536
+ }
537
+
538
+ /**
539
+ * Creates a complete transaction for appending content to a CKBFS v3 file
540
+ * @param signer The signer to use for the transaction
541
+ * @param options Options for appending content
542
+ * @returns Promise resolving to the signed transaction
543
+ */
544
+ export async function appendCKBFSV3(
545
+ signer: Signer,
546
+ options: AppendV3Options,
547
+ ): Promise<Transaction> {
548
+ const tx = await createAppendV3Transaction(signer, options);
549
+ return signer.signTransaction(tx);
550
+ }
551
+
552
+ /**
553
+ * Creates a complete transaction for transferring ownership of a CKBFS v3 file
554
+ * @param signer The signer to use for the transaction
555
+ * @param options Options for transferring the file
556
+ * @returns Promise resolving to the signed transaction
557
+ */
558
+ export async function transferCKBFSV3(
559
+ signer: Signer,
560
+ options: TransferV3Options,
561
+ ): Promise<Transaction> {
562
+ const tx = await createTransferV3Transaction(signer, options);
563
+ return signer.signTransaction(tx);
564
+ }