@ckbfs/api 1.0.0 → 1.0.2

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.
@@ -0,0 +1,289 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createCKBFSCell = createCKBFSCell;
4
+ exports.createPublishTransaction = createPublishTransaction;
5
+ exports.createAppendTransaction = createAppendTransaction;
6
+ exports.publishCKBFS = publishCKBFS;
7
+ exports.appendCKBFS = appendCKBFS;
8
+ const core_1 = require("@ckb-ccc/core");
9
+ const checksum_1 = require("./checksum");
10
+ const molecule_1 = require("./molecule");
11
+ const witness_1 = require("./witness");
12
+ const constants_1 = require("./constants");
13
+ /**
14
+ * Ensures a string is prefixed with '0x'
15
+ * @param value The string to ensure is hex prefixed
16
+ * @returns A hex prefixed string
17
+ */
18
+ function ensureHexPrefix(value) {
19
+ if (value.startsWith('0x')) {
20
+ return value;
21
+ }
22
+ return `0x${value}`;
23
+ }
24
+ /**
25
+ * Creates a CKBFS cell
26
+ * @param options Options for creating the CKBFS cell
27
+ * @returns The created cell output
28
+ */
29
+ function createCKBFSCell(options) {
30
+ const { contentType, filename, capacity, lock, network = constants_1.DEFAULT_NETWORK, version = constants_1.DEFAULT_VERSION, useTypeID = false } = options;
31
+ // Get CKBFS script config
32
+ const config = (0, constants_1.getCKBFSScriptConfig)(network, version, useTypeID);
33
+ // Create pre CKBFS type script
34
+ const preCkbfsTypeScript = new core_1.Script(ensureHexPrefix(config.codeHash), config.hashType, "0x0000000000000000000000000000000000000000000000000000000000000000");
35
+ // Return the cell output
36
+ return {
37
+ lock,
38
+ type: preCkbfsTypeScript,
39
+ capacity: capacity || 200n * 100000000n, // Default 200 CKB
40
+ };
41
+ }
42
+ /**
43
+ * Creates a transaction for publishing a file to CKBFS
44
+ * @param signer The signer to use for the transaction
45
+ * @param options Options for publishing the file
46
+ * @returns Promise resolving to the created transaction
47
+ */
48
+ async function createPublishTransaction(signer, options) {
49
+ const { contentChunks, contentType, filename, lock, feeRate, network = constants_1.DEFAULT_NETWORK, version = constants_1.DEFAULT_VERSION, useTypeID = false } = options;
50
+ // Calculate checksum for the combined content
51
+ const textEncoder = new TextEncoder();
52
+ const combinedContent = Buffer.concat(contentChunks);
53
+ const checksum = await (0, checksum_1.calculateChecksum)(combinedContent);
54
+ // Create CKBFS witnesses
55
+ const ckbfsWitnesses = (0, witness_1.createChunkedCKBFSWitnesses)(contentChunks);
56
+ // Calculate the actual witness indices where our content is placed
57
+ // Index 0 is reserved for the secp256k1 witness for signing
58
+ // So our CKBFS data starts at index 1
59
+ const contentStartIndex = 1;
60
+ const witnessIndices = Array.from({ length: contentChunks.length }, (_, i) => contentStartIndex + i);
61
+ // Create CKBFS cell output data based on version
62
+ let outputData;
63
+ if (version === constants_1.ProtocolVersion.V1) {
64
+ // V1 format: Single index field
65
+ // For V1, use the first index where content is placed
66
+ outputData = molecule_1.CKBFSData.pack({
67
+ index: [contentStartIndex],
68
+ checksum,
69
+ contentType: textEncoder.encode(contentType),
70
+ filename: textEncoder.encode(filename),
71
+ backLinks: [],
72
+ }, version);
73
+ }
74
+ else {
75
+ // V2 format: Multiple indexes
76
+ // For V2, use all the indices where content is placed
77
+ outputData = molecule_1.CKBFSData.pack({
78
+ indexes: witnessIndices,
79
+ checksum,
80
+ contentType: textEncoder.encode(contentType),
81
+ filename: textEncoder.encode(filename),
82
+ backLinks: [],
83
+ }, version);
84
+ }
85
+ // Get CKBFS script config
86
+ const config = (0, constants_1.getCKBFSScriptConfig)(network, version, useTypeID);
87
+ // Create pre transaction without cell deps initially
88
+ const preTx = core_1.Transaction.from({
89
+ outputs: [
90
+ createCKBFSCell({
91
+ contentType,
92
+ filename,
93
+ lock,
94
+ network,
95
+ version,
96
+ useTypeID
97
+ })
98
+ ],
99
+ witnesses: [
100
+ [], // Empty secp witness for signing
101
+ ...ckbfsWitnesses.map(w => `0x${Buffer.from(w).toString('hex')}`),
102
+ ],
103
+ outputsData: [
104
+ outputData,
105
+ ]
106
+ });
107
+ // Add the CKBFS dep group cell dependency
108
+ preTx.addCellDeps({
109
+ outPoint: {
110
+ txHash: ensureHexPrefix(config.depTxHash),
111
+ index: config.depIndex || 0,
112
+ },
113
+ depType: "depGroup"
114
+ });
115
+ // Get the recommended address to ensure lock script cell deps are included
116
+ const address = await signer.getRecommendedAddressObj();
117
+ // Complete inputs by capacity
118
+ await preTx.completeInputsByCapacity(signer);
119
+ // Complete fee change to lock
120
+ await preTx.completeFeeChangeToLock(signer, lock, feeRate || 2000);
121
+ // Create type ID args
122
+ const args = core_1.ccc.hashTypeId(preTx.inputs[0], 0x0);
123
+ // Create CKBFS type script with type ID
124
+ const ckbfsTypeScript = new core_1.Script(ensureHexPrefix(config.codeHash), config.hashType, args);
125
+ // Create final transaction with same cell deps as preTx
126
+ const tx = core_1.Transaction.from({
127
+ cellDeps: preTx.cellDeps,
128
+ witnesses: [
129
+ [], // Reset first witness for signing
130
+ ...preTx.witnesses.slice(1)
131
+ ],
132
+ outputsData: preTx.outputsData,
133
+ inputs: preTx.inputs,
134
+ outputs: [
135
+ {
136
+ lock,
137
+ type: ckbfsTypeScript,
138
+ capacity: preTx.outputs[0].capacity,
139
+ },
140
+ ...preTx.outputs.slice(1) // Include rest of outputs (e.g., change)
141
+ ]
142
+ });
143
+ return tx;
144
+ }
145
+ /**
146
+ * Creates a transaction for appending content to a CKBFS file
147
+ * @param signer The signer to use for the transaction
148
+ * @param options Options for appending content
149
+ * @returns Promise resolving to the created transaction
150
+ */
151
+ async function createAppendTransaction(signer, options) {
152
+ const { ckbfsCell, contentChunks, feeRate, network = constants_1.DEFAULT_NETWORK, version = constants_1.DEFAULT_VERSION } = options;
153
+ const { outPoint, data, type, lock, capacity } = ckbfsCell;
154
+ // Get CKBFS script config early to use version info
155
+ const config = (0, constants_1.getCKBFSScriptConfig)(network, version);
156
+ // Create CKBFS witnesses - this may vary between V1 and V2
157
+ const ckbfsWitnesses = (0, witness_1.createChunkedCKBFSWitnesses)(contentChunks);
158
+ // Combine the new content chunks
159
+ const combinedContent = Buffer.concat(contentChunks);
160
+ // Instead of calculating a new checksum from scratch, update the existing checksum
161
+ // with the new content - this is more efficient and matches the Adler32 algorithm's
162
+ // cumulative nature
163
+ const contentChecksum = await (0, checksum_1.updateChecksum)(data.checksum, combinedContent);
164
+ console.log(`Updated checksum from ${data.checksum} to ${contentChecksum} for appended content`);
165
+ // Create backlink for the current state based on version
166
+ let newBackLink;
167
+ if (version === constants_1.ProtocolVersion.V1) {
168
+ // V1 format: Use index field (single number)
169
+ newBackLink = {
170
+ txHash: outPoint.txHash,
171
+ index: data.index && data.index.length > 0 ? data.index[0] : 0,
172
+ checksum: data.checksum,
173
+ };
174
+ }
175
+ else {
176
+ // V2 format: Use indexes field (array of numbers)
177
+ newBackLink = {
178
+ txHash: outPoint.txHash,
179
+ indexes: data.indexes || data.index || [],
180
+ checksum: data.checksum,
181
+ };
182
+ }
183
+ // Update backlinks
184
+ const backLinks = [...(data.backLinks || []), newBackLink];
185
+ // Define indices based on version
186
+ let outputData;
187
+ // Calculate the actual witness indices where our content is placed
188
+ // Index 0 is reserved for the secp256k1 witness for signing
189
+ // So our CKBFS data starts at index 1
190
+ const contentStartIndex = 1;
191
+ const witnessIndices = Array.from({ length: contentChunks.length }, (_, i) => contentStartIndex + i);
192
+ if (version === constants_1.ProtocolVersion.V1) {
193
+ // In V1, use the first index where content is placed
194
+ // (even if we have multiple witnesses, V1 only supports a single index)
195
+ outputData = molecule_1.CKBFSData.pack({
196
+ index: [contentStartIndex],
197
+ checksum: contentChecksum,
198
+ contentType: data.contentType,
199
+ filename: data.filename,
200
+ backLinks,
201
+ }, version);
202
+ }
203
+ else {
204
+ // In V2, use all the indices where content is placed
205
+ outputData = molecule_1.CKBFSData.pack({
206
+ indexes: witnessIndices,
207
+ checksum: contentChecksum,
208
+ contentType: data.contentType,
209
+ filename: data.filename,
210
+ backLinks,
211
+ }, version);
212
+ }
213
+ // Pack the original data to get its size - use the appropriate version
214
+ const originalData = molecule_1.CKBFSData.pack(data, version);
215
+ const originalDataSize = originalData.length;
216
+ // Get sizes
217
+ const newDataSize = outputData.length;
218
+ const dataSizeDiff = newDataSize - originalDataSize;
219
+ // Calculate the additional capacity needed (in shannons)
220
+ // CKB requires 1 shannon per byte of data
221
+ const additionalCapacity = BigInt(Math.max(0, dataSizeDiff)) * 100000000n;
222
+ // Add the additional capacity to the original cell capacity
223
+ console.log(`Original capacity: ${capacity}, Additional needed: ${additionalCapacity}, Data size diff: ${dataSizeDiff}, Version: ${version}`);
224
+ const outputCapacity = capacity + additionalCapacity;
225
+ // Create initial transaction with the CKBFS cell input
226
+ const tx = core_1.Transaction.from({
227
+ inputs: [
228
+ {
229
+ previousOutput: {
230
+ txHash: outPoint.txHash,
231
+ index: outPoint.index,
232
+ },
233
+ since: "0x0",
234
+ }
235
+ ],
236
+ outputs: [
237
+ {
238
+ lock,
239
+ type,
240
+ capacity: outputCapacity,
241
+ }
242
+ ],
243
+ witnesses: [
244
+ [], // Empty secp witness for signing
245
+ ...ckbfsWitnesses.map(w => `0x${Buffer.from(w).toString('hex')}`),
246
+ ],
247
+ outputsData: [
248
+ outputData,
249
+ ]
250
+ });
251
+ // Add the CKBFS dep group cell dependency
252
+ tx.addCellDeps({
253
+ outPoint: {
254
+ txHash: ensureHexPrefix(config.depTxHash),
255
+ index: config.depIndex || 0,
256
+ },
257
+ depType: "depGroup"
258
+ });
259
+ // Get the recommended address to ensure lock script cell deps are included
260
+ const address = await signer.getRecommendedAddressObj();
261
+ // If we need more capacity than the original cell had, add additional inputs
262
+ if (additionalCapacity > 0n) {
263
+ // Add more inputs to cover the increased capacity
264
+ await tx.completeInputsByCapacity(signer);
265
+ }
266
+ // Complete fee
267
+ await tx.completeFeeChangeToLock(signer, lock || address.script, feeRate || 2000);
268
+ return tx;
269
+ }
270
+ /**
271
+ * Creates a complete transaction for publishing a file to CKBFS
272
+ * @param signer The signer to use for the transaction
273
+ * @param options Options for publishing the file
274
+ * @returns Promise resolving to the signed transaction
275
+ */
276
+ async function publishCKBFS(signer, options) {
277
+ const tx = await createPublishTransaction(signer, options);
278
+ return signer.signTransaction(tx);
279
+ }
280
+ /**
281
+ * Creates a complete transaction for appending content to a CKBFS file
282
+ * @param signer The signer to use for the transaction
283
+ * @param options Options for appending content
284
+ * @returns Promise resolving to the signed transaction
285
+ */
286
+ async function appendCKBFS(signer, options) {
287
+ const tx = await createAppendTransaction(signer, options);
288
+ return signer.signTransaction(tx);
289
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Utility functions for creating and handling CKBFS witnesses
3
+ */
4
+ /**
5
+ * Creates a CKBFS witness with content
6
+ * @param content The content to include in the witness
7
+ * @param version Optional version byte (default is 0)
8
+ * @returns Uint8Array containing the witness data
9
+ */
10
+ export declare function createCKBFSWitness(content: Uint8Array, version?: number): Uint8Array;
11
+ /**
12
+ * Creates a CKBFS witness with text content
13
+ * @param text The text content to include in the witness
14
+ * @param version Optional version byte (default is 0)
15
+ * @returns Uint8Array containing the witness data
16
+ */
17
+ export declare function createTextCKBFSWitness(text: string, version?: number): Uint8Array;
18
+ /**
19
+ * Extracts content from a CKBFS witness
20
+ * @param witness The CKBFS witness data
21
+ * @returns Object containing the extracted version and content bytes
22
+ */
23
+ export declare function extractCKBFSWitnessContent(witness: Uint8Array): {
24
+ version: number;
25
+ content: Uint8Array;
26
+ };
27
+ /**
28
+ * Checks if a witness is a valid CKBFS witness
29
+ * @param witness The witness data to check
30
+ * @returns Boolean indicating whether the witness is a valid CKBFS witness
31
+ */
32
+ export declare function isCKBFSWitness(witness: Uint8Array): boolean;
33
+ /**
34
+ * Creates an array of witnesses for a CKBFS transaction from content chunks
35
+ * @param contentChunks Array of content chunks
36
+ * @param version Optional version byte (default is 0)
37
+ * @returns Array of Uint8Array witnesses
38
+ */
39
+ export declare function createChunkedCKBFSWitnesses(contentChunks: Uint8Array[], version?: number): Uint8Array[];
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createCKBFSWitness = createCKBFSWitness;
4
+ exports.createTextCKBFSWitness = createTextCKBFSWitness;
5
+ exports.extractCKBFSWitnessContent = extractCKBFSWitnessContent;
6
+ exports.isCKBFSWitness = isCKBFSWitness;
7
+ exports.createChunkedCKBFSWitnesses = createChunkedCKBFSWitnesses;
8
+ const molecule_1 = require("./molecule");
9
+ /**
10
+ * Utility functions for creating and handling CKBFS witnesses
11
+ */
12
+ /**
13
+ * Creates a CKBFS witness with content
14
+ * @param content The content to include in the witness
15
+ * @param version Optional version byte (default is 0)
16
+ * @returns Uint8Array containing the witness data
17
+ */
18
+ function createCKBFSWitness(content, version = 0) {
19
+ // Create witness with CKBFS header, version byte, and content
20
+ const versionByte = new Uint8Array([version]);
21
+ return Buffer.concat([molecule_1.CKBFS_HEADER, versionByte, content]);
22
+ }
23
+ /**
24
+ * Creates a CKBFS witness with text content
25
+ * @param text The text content to include in the witness
26
+ * @param version Optional version byte (default is 0)
27
+ * @returns Uint8Array containing the witness data
28
+ */
29
+ function createTextCKBFSWitness(text, version = 0) {
30
+ const textEncoder = new TextEncoder();
31
+ const contentBytes = textEncoder.encode(text);
32
+ return createCKBFSWitness(contentBytes, version);
33
+ }
34
+ /**
35
+ * Extracts content from a CKBFS witness
36
+ * @param witness The CKBFS witness data
37
+ * @returns Object containing the extracted version and content bytes
38
+ */
39
+ function extractCKBFSWitnessContent(witness) {
40
+ // Ensure the witness has the CKBFS header
41
+ const header = witness.slice(0, 5);
42
+ const headerString = new TextDecoder().decode(header);
43
+ if (headerString !== 'CKBFS') {
44
+ throw new Error('Invalid CKBFS witness: missing CKBFS header');
45
+ }
46
+ // Extract version byte and content
47
+ const version = witness[5];
48
+ const content = witness.slice(6);
49
+ return { version, content };
50
+ }
51
+ /**
52
+ * Checks if a witness is a valid CKBFS witness
53
+ * @param witness The witness data to check
54
+ * @returns Boolean indicating whether the witness is a valid CKBFS witness
55
+ */
56
+ function isCKBFSWitness(witness) {
57
+ if (witness.length < 6) {
58
+ return false;
59
+ }
60
+ const header = witness.slice(0, 5);
61
+ const headerString = new TextDecoder().decode(header);
62
+ return headerString === 'CKBFS';
63
+ }
64
+ /**
65
+ * Creates an array of witnesses for a CKBFS transaction from content chunks
66
+ * @param contentChunks Array of content chunks
67
+ * @param version Optional version byte (default is 0)
68
+ * @returns Array of Uint8Array witnesses
69
+ */
70
+ function createChunkedCKBFSWitnesses(contentChunks, version = 0) {
71
+ return contentChunks.map(chunk => createCKBFSWitness(chunk, version));
72
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckbfs/api",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "SDK for CKBFS protocol on CKB",
5
5
  "license": "MIT",
6
6
  "author": "Code Monad<code@lab-11.org>",
@@ -113,6 +113,7 @@ export async function createPublishTransaction(
113
113
  contentType,
114
114
  filename,
115
115
  lock,
116
+ capacity,
116
117
  feeRate,
117
118
  network = DEFAULT_NETWORK,
118
119
  version = DEFAULT_VERSION,
@@ -164,6 +165,12 @@ export async function createPublishTransaction(
164
165
  // Get CKBFS script config
165
166
  const config = getCKBFSScriptConfig(network, version, useTypeID);
166
167
 
168
+ const preCkbfsTypeScript = new Script(
169
+ ensureHexPrefix(config.codeHash),
170
+ config.hashType as any,
171
+ "0x0000000000000000000000000000000000000000000000000000000000000000"
172
+ );
173
+ const ckbfsCellSize = BigInt(outputData.length + preCkbfsTypeScript.occupiedSize + lock.occupiedSize + 8) * 100000000n
167
174
  // Create pre transaction without cell deps initially
168
175
  const preTx = Transaction.from({
169
176
  outputs: [
@@ -173,7 +180,8 @@ export async function createPublishTransaction(
173
180
  lock,
174
181
  network,
175
182
  version,
176
- useTypeID
183
+ useTypeID,
184
+ capacity: ckbfsCellSize || capacity
177
185
  })
178
186
  ],
179
187
  witnesses: [
@@ -289,7 +297,7 @@ export async function createAppendTransaction(
289
297
  }
290
298
 
291
299
  // Update backlinks
292
- const backLinks = [newBackLink];
300
+ const backLinks = [...(data.backLinks || []), newBackLink];
293
301
 
294
302
  // Define indices based on version
295
303
  let outputData: Uint8Array;
@@ -339,7 +347,7 @@ export async function createAppendTransaction(
339
347
  // Add the additional capacity to the original cell capacity
340
348
  console.log(`Original capacity: ${capacity}, Additional needed: ${additionalCapacity}, Data size diff: ${dataSizeDiff}, Version: ${version}`);
341
349
  const outputCapacity = capacity + additionalCapacity;
342
-
350
+
343
351
  // Create initial transaction with the CKBFS cell input
344
352
  const tx = Transaction.from({
345
353
  inputs: [