@ckbfs/api 1.5.0 → 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 +440 -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 -105
  16. package/dist/utils/transaction.js +45 -565
  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
@@ -4,11 +4,12 @@ exports.createCKBFSWitness = createCKBFSWitness;
4
4
  exports.createTextCKBFSWitness = createTextCKBFSWitness;
5
5
  exports.extractCKBFSWitnessContent = extractCKBFSWitnessContent;
6
6
  exports.isCKBFSWitness = isCKBFSWitness;
7
+ exports.createCKBFSV3Witness = createCKBFSV3Witness;
8
+ exports.createChunkedCKBFSV3Witnesses = createChunkedCKBFSV3Witnesses;
9
+ exports.extractCKBFSV3WitnessContent = extractCKBFSV3WitnessContent;
10
+ exports.isCKBFSV3Witness = isCKBFSV3Witness;
7
11
  exports.createChunkedCKBFSWitnesses = createChunkedCKBFSWitnesses;
8
12
  const molecule_1 = require("./molecule");
9
- /**
10
- * Utility functions for creating and handling CKBFS witnesses
11
- */
12
13
  /**
13
14
  * Creates a CKBFS witness with content
14
15
  * @param content The content to include in the witness
@@ -62,6 +63,147 @@ function isCKBFSWitness(witness) {
62
63
  const headerString = new TextDecoder().decode(header);
63
64
  return headerString === 'CKBFS';
64
65
  }
66
+ /**
67
+ * Creates a CKBFS v3 witness with structured format
68
+ * @param options Witness options including backlink data and content
69
+ * @returns Uint8Array containing the v3 witness data
70
+ */
71
+ function createCKBFSV3Witness(options) {
72
+ const { previousTxHash = '0x' + '00'.repeat(32), previousWitnessIndex = 0, previousChecksum = 0, nextIndex = 0, content } = options;
73
+ // Create witness components
74
+ const header = molecule_1.CKBFS_HEADER; // "CKBFS" (5 bytes)
75
+ const version = new Uint8Array([0x03]); // Version 3 (1 byte)
76
+ // Previous position: txHash (32 bytes) + witnessIndex (4 bytes)
77
+ const prevTxHashBytes = new Uint8Array(32);
78
+ if (previousTxHash !== '0x' + '00'.repeat(32)) {
79
+ const hexStr = previousTxHash.startsWith('0x') ? previousTxHash.slice(2) : previousTxHash;
80
+ for (let i = 0; i < 32; i++) {
81
+ prevTxHashBytes[i] = parseInt(hexStr.substr(i * 2, 2), 16);
82
+ }
83
+ }
84
+ const prevWitnessIndexBytes = new Uint8Array(4);
85
+ new DataView(prevWitnessIndexBytes.buffer).setUint32(0, previousWitnessIndex, true); // little-endian
86
+ // Previous checksum (4 bytes)
87
+ const prevChecksumBytes = new Uint8Array(4);
88
+ new DataView(prevChecksumBytes.buffer).setUint32(0, previousChecksum, true); // little-endian
89
+ // Next index (4 bytes)
90
+ const nextIndexBytes = new Uint8Array(4);
91
+ new DataView(nextIndexBytes.buffer).setUint32(0, nextIndex, true); // little-endian
92
+ // Combine all parts
93
+ return Buffer.concat([
94
+ header, // 5 bytes: "CKBFS"
95
+ version, // 1 byte: 0x03
96
+ prevTxHashBytes, // 32 bytes: previous tx hash
97
+ prevWitnessIndexBytes, // 4 bytes: previous witness index
98
+ prevChecksumBytes, // 4 bytes: previous checksum
99
+ nextIndexBytes, // 4 bytes: next index
100
+ content // variable: content bytes
101
+ ]);
102
+ }
103
+ /**
104
+ * Creates an array of v3 witnesses for chunked content
105
+ * @param contentChunks Array of content chunks
106
+ * @param options Backlink options for head witness and start index
107
+ * @returns Array of Uint8Array witnesses
108
+ */
109
+ function createChunkedCKBFSV3Witnesses(contentChunks, options = {}) {
110
+ if (contentChunks.length === 0) {
111
+ return [];
112
+ }
113
+ // Default start index to 1 if not provided (witness 0 is typically for signing)
114
+ const startIndex = options.startIndex || 1;
115
+ const witnesses = [];
116
+ for (let i = 0; i < contentChunks.length; i++) {
117
+ const isHead = i === 0;
118
+ const isTail = i === contentChunks.length - 1;
119
+ if (isHead) {
120
+ // Head witness with backlink info
121
+ witnesses.push(createCKBFSV3Witness({
122
+ ...options,
123
+ nextIndex: isTail ? 0 : startIndex + i + 1, // Correct next witness index calculation
124
+ content: contentChunks[i]
125
+ }));
126
+ }
127
+ else {
128
+ // Middle/tail witness with minimal header
129
+ const nextIndex = isTail ? 0 : startIndex + i + 1;
130
+ const nextIndexBytes = new Uint8Array(4);
131
+ new DataView(nextIndexBytes.buffer).setUint32(0, nextIndex, true);
132
+ witnesses.push(Buffer.concat([
133
+ nextIndexBytes, // 4 bytes: next index
134
+ contentChunks[i] // variable: content bytes
135
+ ]));
136
+ }
137
+ }
138
+ return witnesses;
139
+ }
140
+ /**
141
+ * Extracts content from a CKBFS v3 witness
142
+ * @param witness The CKBFS v3 witness data
143
+ * @param isHeadWitness Whether this is the head witness or a continuation witness
144
+ * @returns Object containing the extracted data
145
+ */
146
+ function extractCKBFSV3WitnessContent(witness, isHeadWitness = true) {
147
+ if (isHeadWitness) {
148
+ // Head witness format: CKBFS(5) + version(1) + prevTxHash(32) + prevWitnessIndex(4) + prevChecksum(4) + nextIndex(4) + content
149
+ if (witness.length < 50) {
150
+ throw new Error('Invalid CKBFS v3 head witness: too short');
151
+ }
152
+ const header = witness.slice(0, 5);
153
+ const headerString = new TextDecoder().decode(header);
154
+ if (headerString !== 'CKBFS') {
155
+ throw new Error('Invalid CKBFS v3 head witness: missing CKBFS header');
156
+ }
157
+ const version = witness[5];
158
+ if (version !== 0x03) {
159
+ throw new Error(`Invalid CKBFS v3 head witness: expected version 0x03, got 0x${version.toString(16)}`);
160
+ }
161
+ // Extract previous position
162
+ const prevTxHashBytes = witness.slice(6, 38);
163
+ const previousTxHash = '0x' + Array.from(prevTxHashBytes).map(b => b.toString(16).padStart(2, '0')).join('');
164
+ const previousWitnessIndex = new DataView(witness.slice(38, 42).buffer).getUint32(0, true);
165
+ const previousChecksum = new DataView(witness.slice(42, 46).buffer).getUint32(0, true);
166
+ const nextIndex = new DataView(witness.slice(46, 50).buffer).getUint32(0, true);
167
+ const content = witness.slice(50);
168
+ return {
169
+ version,
170
+ previousTxHash,
171
+ previousWitnessIndex,
172
+ previousChecksum,
173
+ nextIndex,
174
+ content
175
+ };
176
+ }
177
+ else {
178
+ // Continuation witness format: nextIndex(4) + content
179
+ if (witness.length < 4) {
180
+ throw new Error('Invalid CKBFS v3 continuation witness: too short');
181
+ }
182
+ const nextIndex = new DataView(witness.slice(0, 4).buffer).getUint32(0, true);
183
+ const content = witness.slice(4);
184
+ return {
185
+ nextIndex,
186
+ content
187
+ };
188
+ }
189
+ }
190
+ /**
191
+ * Checks if a witness is a valid CKBFS v3 witness
192
+ * @param witness The witness data to check
193
+ * @returns Boolean indicating whether the witness is a valid CKBFS v3 witness
194
+ */
195
+ function isCKBFSV3Witness(witness) {
196
+ if (witness.length < 50) {
197
+ return false;
198
+ }
199
+ const header = witness.slice(0, 5);
200
+ const headerString = new TextDecoder().decode(header);
201
+ if (headerString !== 'CKBFS') {
202
+ return false;
203
+ }
204
+ const version = witness[5];
205
+ return version === 0x03;
206
+ }
65
207
  /**
66
208
  * Creates an array of witnesses for a CKBFS transaction from content chunks
67
209
  * @param contentChunks Array of content chunks
@@ -0,0 +1,310 @@
1
+ import { CKBFS, NetworkType, ProtocolVersion, CKBFSDataType, extractCKBFSV3WitnessContent, isCKBFSV3Witness, CKBFSData } from '../src/index';
2
+ import { Script, ClientPublicTestnet, Transaction, ccc } from "@ckb-ccc/core";
3
+
4
+ // Replace with your actual private key
5
+ const privateKey = process.env.CKB_PRIVATE_KEY || 'your-private-key-here';
6
+
7
+ // Parse command line arguments for transaction hash
8
+ const txHashArg = process.argv.find(arg => arg.startsWith('--txhash='));
9
+ const publishTxHash = txHashArg ? txHashArg.split('=')[1] : process.env.PUBLISH_TX_HASH || '0x0000000000000000000000000000000000000000000000000000000000000000';
10
+
11
+ /**
12
+ * Ensures a string is prefixed with '0x'
13
+ * @param value The string to ensure is hex prefixed
14
+ * @returns A hex prefixed string
15
+ */
16
+ function ensureHexPrefix(value: string): `0x${string}` {
17
+ if (value.startsWith('0x')) {
18
+ return value as `0x${string}`;
19
+ }
20
+ return `0x${value}` as `0x${string}`;
21
+ }
22
+
23
+ // Initialize the SDK for CKBFS v3
24
+ const ckbfs = new CKBFS(
25
+ privateKey,
26
+ NetworkType.Testnet, // Use testnet
27
+ {
28
+ version: ProtocolVersion.V3, // Use v3
29
+ chunkSize: 20 * 1024, // 20KB chunks
30
+ useTypeID: false, // Use code hash instead of type ID
31
+ }
32
+ );
33
+
34
+ // Initialize CKB client for testnet
35
+ const client = new ClientPublicTestnet();
36
+
37
+ /**
38
+ * Get cell information from a v3 transaction using CKB client
39
+ * @param txHash The transaction hash to get cell information from
40
+ * @returns Promise resolving to the cell information including v3 backlink data
41
+ */
42
+ async function getCellInfoFromV3Transaction(txHash: string): Promise<{
43
+ outPoint: { txHash: string; index: number };
44
+ type: Script;
45
+ data: CKBFSDataType;
46
+ lock: Script;
47
+ capacity: bigint;
48
+ previousTxHash: string;
49
+ previousWitnessIndex: number;
50
+ previousChecksum: number;
51
+ }> {
52
+ console.log(`Retrieving v3 transaction data for: ${txHash}`);
53
+
54
+ try {
55
+ // Get transaction from RPC
56
+ const txWithStatus = await client.getTransaction(txHash);
57
+ if (!txWithStatus || !txWithStatus.transaction) {
58
+ throw new Error(`Transaction ${txHash} not found`);
59
+ }
60
+
61
+ const tx = Transaction.from(txWithStatus.transaction);
62
+ console.log(`Transaction found with ${tx.outputs.length} outputs`);
63
+
64
+ // Find the CKBFS cell output (first output with type script)
65
+ let ckbfsCellIndex = 0;
66
+ const output = tx.outputs[ckbfsCellIndex];
67
+ if (!output || !output.type) {
68
+ throw new Error('No CKBFS cell found in the transaction');
69
+ }
70
+
71
+ console.log(`Found CKBFS v3 cell at index ${ckbfsCellIndex}`);
72
+ console.log(`Cell type script hash: ${output.type.hash()}`);
73
+
74
+ // Get output data
75
+ const outputData = tx.outputsData[ckbfsCellIndex];
76
+ if (!outputData) {
77
+ throw new Error('Output data not found');
78
+ }
79
+
80
+ // Parse the output data as CKBFS v3 data
81
+ const rawData = outputData.startsWith('0x')
82
+ ? ccc.bytesFrom(outputData.slice(2), 'hex')
83
+ : Buffer.from(outputData, 'hex');
84
+
85
+ // Unpack the raw data using v3 format
86
+ const version = ProtocolVersion.V3;
87
+ console.log(`Using protocol version ${version} for unpacking v3 cell data`);
88
+
89
+ let ckbfsData: CKBFSDataType;
90
+ try {
91
+ ckbfsData = CKBFSData.unpack(rawData, version);
92
+
93
+ console.log('Successfully unpacked CKBFS v3 cell data:');
94
+ console.log(`- Checksum: ${ckbfsData.checksum}`);
95
+ console.log(`- File: ${ckbfsData.filename}`);
96
+ console.log(`- Content Type: ${ckbfsData.contentType}`);
97
+ console.log(`- Index: ${ckbfsData.index}`);
98
+ } catch (error) {
99
+ console.error('Error unpacking CKBFS v3 data:', error);
100
+ throw new Error(`Failed to unpack CKBFS v3 data: ${error}`);
101
+ }
102
+
103
+ // Extract backlink information from v3 witness
104
+ let previousTxHash = '0x' + '00'.repeat(32);
105
+ let previousWitnessIndex = 0;
106
+ let previousChecksum = 0;
107
+
108
+ if (ckbfsData.index !== undefined && ckbfsData.index < tx.witnesses.length) {
109
+ const witnessHex = tx.witnesses[ckbfsData.index];
110
+ const witness = Buffer.from(witnessHex.slice(2), 'hex');
111
+
112
+ if (isCKBFSV3Witness(witness)) {
113
+ try {
114
+ const witnessData = extractCKBFSV3WitnessContent(witness, true);
115
+ previousTxHash = witnessData.previousTxHash || previousTxHash;
116
+ previousWitnessIndex = witnessData.previousWitnessIndex || 0;
117
+ previousChecksum = witnessData.previousChecksum || 0;
118
+
119
+ console.log('Extracted v3 backlink information:');
120
+ console.log(`- Previous TX Hash: ${previousTxHash}`);
121
+ console.log(`- Previous Witness Index: ${previousWitnessIndex}`);
122
+ console.log(`- Previous Checksum: ${previousChecksum}`);
123
+ } catch (error) {
124
+ console.warn('Could not extract backlink info from witness:', error);
125
+ }
126
+ }
127
+ }
128
+
129
+ return {
130
+ outPoint: {
131
+ txHash,
132
+ index: ckbfsCellIndex
133
+ },
134
+ type: output.type,
135
+ lock: output.lock,
136
+ capacity: output.capacity,
137
+ data: ckbfsData,
138
+ previousTxHash,
139
+ previousWitnessIndex,
140
+ previousChecksum
141
+ };
142
+ } catch (error) {
143
+ console.error('Error retrieving v3 transaction data:', error);
144
+ throw new Error(`Failed to retrieve or parse v3 cell data: ${error}`);
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Example of appending content to an existing CKBFS v3 file
150
+ */
151
+ async function appendContentV3Example() {
152
+ try {
153
+ // Validate transaction hash
154
+ if (publishTxHash === '0x0000000000000000000000000000000000000000000000000000000000000000') {
155
+ throw new Error('Please provide a valid transaction hash by setting the PUBLISH_TX_HASH environment variable or using --txhash=0x... argument');
156
+ }
157
+
158
+ // Get address info
159
+ const address = await ckbfs.getAddress();
160
+ console.log(`Using address: ${address.toString()}`);
161
+
162
+ // Get the cell information from the v3 transaction
163
+ console.log(`Getting v3 cell info from transaction: ${publishTxHash}`);
164
+ const cellInfo = await getCellInfoFromV3Transaction(publishTxHash);
165
+
166
+ // Create the CKBFS v3 cell structure
167
+ const ckbfsCell = {
168
+ outPoint: cellInfo.outPoint,
169
+ data: cellInfo.data,
170
+ type: cellInfo.type,
171
+ lock: cellInfo.lock,
172
+ capacity: cellInfo.capacity
173
+ };
174
+
175
+ // Content to append
176
+ const contentToAppend = "\n\nThis content was appended using CKBFS v3!";
177
+ console.log(`Appending content to CKBFS v3 file: "${contentToAppend}"`);
178
+
179
+ console.log('Note: CKBFS v3 append requires backlink information from the previous transaction.');
180
+ console.log('The SDK will create witnesses with structured backlinks.');
181
+ console.log('Make sure your account has enough CKB to cover the fees.');
182
+
183
+ const txHash = await ckbfs.appendContentV3(
184
+ contentToAppend,
185
+ ckbfsCell,
186
+ publishTxHash,
187
+ cellInfo.data.index!,
188
+ cellInfo.data.checksum!,
189
+ {
190
+ feeRate: 2000,
191
+ }
192
+ );
193
+
194
+ console.log(`Content appended successfully to CKBFS v3!`);
195
+ console.log(`Transaction Hash: ${txHash}`);
196
+ console.log(`View at: https://pudge.explorer.nervos.org/transaction/${txHash}`);
197
+
198
+ return txHash;
199
+ } catch (error) {
200
+ console.error('Error appending content to v3:', error);
201
+ throw error;
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Example of transferring ownership of a CKBFS v3 file
207
+ */
208
+ async function transferFileV3Example() {
209
+ try {
210
+ // Example CKBFS v3 cell (you need to replace with actual values)
211
+ const ckbfsCell = {
212
+ outPoint: {
213
+ txHash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
214
+ index: 0
215
+ },
216
+ data: {
217
+ index: 1,
218
+ checksum: 12345678,
219
+ contentType: "text/plain",
220
+ filename: "v3-example.txt"
221
+ },
222
+ type: Script.from({
223
+ codeHash: "0x25a6d8a4017d675e457b76e9228bfc3942ddbf8227f8624db4fcf315e49a6b07",
224
+ hashType: "data1",
225
+ args: "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
226
+ }),
227
+ lock: Script.from({
228
+ codeHash: "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8",
229
+ hashType: "type",
230
+ args: "0x1234567890abcdef1234567890abcdef12345678"
231
+ }),
232
+ capacity: 200n * 100000000n
233
+ };
234
+
235
+ // New lock script for the transferred file
236
+ const newLock = Script.from({
237
+ codeHash: "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8",
238
+ hashType: "type",
239
+ args: "0xfedcba0987654321fedcba0987654321fedcba09" // New owner's lock args
240
+ });
241
+
242
+ // V3 backlink information (from previous transaction)
243
+ const previousTxHash = "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890";
244
+ const previousWitnessIndex = 1;
245
+ const previousChecksum = 12345678; // Same checksum (transfer doesn't change content)
246
+
247
+ console.log(`Transferring CKBFS v3 file ownership...`);
248
+
249
+ const txHash = await ckbfs.transferFileV3(
250
+ ckbfsCell,
251
+ newLock,
252
+ previousTxHash,
253
+ previousWitnessIndex,
254
+ previousChecksum,
255
+ {
256
+ feeRate: 2000,
257
+ }
258
+ );
259
+
260
+ console.log(`File ownership transferred successfully in CKBFS v3!`);
261
+ console.log(`Transaction Hash: ${txHash}`);
262
+ console.log(`View at: https://pudge.explorer.nervos.org/transaction/${txHash}`);
263
+
264
+ return txHash;
265
+ } catch (error) {
266
+ console.error('Error transferring v3 file:', error);
267
+ throw error;
268
+ }
269
+ }
270
+
271
+ /**
272
+ * Main function to run the v3 append/transfer example
273
+ */
274
+ async function main() {
275
+ console.log('Running CKBFS v3 append/transfer example...');
276
+ console.log('==========================================');
277
+ console.log(`Using CKBFS protocol version: ${ProtocolVersion.V3}`);
278
+ console.log('Key v3 operations:');
279
+ console.log('- Append: Add content with backlink chain');
280
+ console.log('- Transfer: Change ownership with backlink');
281
+ console.log('- Backlinks stored in witnesses, not cell data');
282
+ console.log('==========================================');
283
+
284
+ try {
285
+ console.log('Note: This example requires an existing CKBFS v3 cell.');
286
+ console.log('Please run publish-v3.ts first to create a v3 file,');
287
+ console.log('then update the cell details in this example.');
288
+ console.log('');
289
+ console.log('Uncomment the desired operation below:');
290
+ console.log('');
291
+
292
+ // Uncomment to test append:
293
+ await appendContentV3Example();
294
+
295
+ // Uncomment to test transfer:
296
+ // await transferFileV3Example();
297
+
298
+ console.log('Example structure completed successfully!');
299
+ console.log('Update the cell details and uncomment operations to test.');
300
+ process.exit(0);
301
+ } catch (error) {
302
+ console.error('CKBFS v3 append/transfer example failed:', error);
303
+ process.exit(1);
304
+ }
305
+ }
306
+
307
+ // Run the example if this file is executed directly
308
+ if (require.main === module) {
309
+ main().catch(console.error);
310
+ }