@ckbfs/api 1.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.
- package/README.md +132 -0
- package/RFC.v2.md +341 -0
- package/append.txt +1 -0
- package/example.txt +1 -0
- package/examples/append.ts +222 -0
- package/examples/index.ts +43 -0
- package/examples/publish.ts +76 -0
- package/package.json +39 -0
- package/src/index.ts +363 -0
- package/src/utils/checksum.ts +74 -0
- package/src/utils/constants.ts +123 -0
- package/src/utils/file.ts +109 -0
- package/src/utils/molecule.ts +190 -0
- package/src/utils/transaction.ts +420 -0
- package/src/utils/witness.ts +76 -0
- package/tsconfig.json +15 -0
package/README.md
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
# CKBFS API SDK
|
2
|
+
|
3
|
+
A JavaScript/TypeScript SDK for the CKBFS (Cell Knowledge Base File System) protocol on Nervos CKB.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
CKBFS is a protocol designed to describe a witnesses-based file storage system on Nervos CKB Network. With CKBFS, you can:
|
8
|
+
|
9
|
+
- Store files on Nervos CKB Network
|
10
|
+
- Publish large files that may exceed block size limitation (~500kb), across multiple blocks
|
11
|
+
- Maintain simple indexing and retrieval
|
12
|
+
|
13
|
+
This SDK provides a simple way to interact with the CKBFS protocol, allowing you to:
|
14
|
+
|
15
|
+
- Read and write files to CKB
|
16
|
+
- Calculate and verify Adler32 checksums
|
17
|
+
- Create, manage, and update CKBFS cells
|
18
|
+
- Handle witnesses creation and verification
|
19
|
+
- Create and send CKB transactions for file operations
|
20
|
+
|
21
|
+
## Installation
|
22
|
+
|
23
|
+
```bash
|
24
|
+
npm install ckbfs-api
|
25
|
+
```
|
26
|
+
|
27
|
+
## Basic Usage
|
28
|
+
|
29
|
+
```typescript
|
30
|
+
import { CKBFS } from 'ckbfs-api';
|
31
|
+
|
32
|
+
// Initialize the SDK with your private key
|
33
|
+
const privateKey = process.env.CKB_PRIVATE_KEY;
|
34
|
+
const ckbfs = new CKBFS(privateKey);
|
35
|
+
|
36
|
+
// Publish a file to CKBFS
|
37
|
+
async function publishExample() {
|
38
|
+
const txHash = await ckbfs.publishFile('path/to/your/file.txt');
|
39
|
+
console.log(`File published with transaction: ${txHash}`);
|
40
|
+
}
|
41
|
+
|
42
|
+
// Append content to an existing CKBFS file
|
43
|
+
async function appendExample(ckbfsCell) {
|
44
|
+
const txHash = await ckbfs.appendFile('path/to/more/content.txt', ckbfsCell);
|
45
|
+
console.log(`Content appended with transaction: ${txHash}`);
|
46
|
+
}
|
47
|
+
```
|
48
|
+
|
49
|
+
## Advanced Usage
|
50
|
+
|
51
|
+
The SDK provides utility functions for more granular control:
|
52
|
+
|
53
|
+
### Working with Files
|
54
|
+
|
55
|
+
```typescript
|
56
|
+
import { readFileAsUint8Array, splitFileIntoChunks } from 'ckbfs-api';
|
57
|
+
|
58
|
+
// Read a file
|
59
|
+
const fileContent = readFileAsUint8Array('path/to/file.txt');
|
60
|
+
|
61
|
+
// Split a file into chunks (e.g., for large files)
|
62
|
+
const chunks = splitFileIntoChunks('path/to/large/file.bin', 30 * 1024); // 30KB chunks
|
63
|
+
```
|
64
|
+
|
65
|
+
### Checksum Operations
|
66
|
+
|
67
|
+
```typescript
|
68
|
+
import { calculateChecksum, verifyChecksum } from 'ckbfs-api';
|
69
|
+
|
70
|
+
// Calculate Adler32 checksum
|
71
|
+
const content = new TextEncoder().encode('Hello CKBFS');
|
72
|
+
const checksum = await calculateChecksum(content);
|
73
|
+
|
74
|
+
// Verify checksum
|
75
|
+
const isValid = await verifyChecksum(content, checksum);
|
76
|
+
```
|
77
|
+
|
78
|
+
### Transaction Utilities
|
79
|
+
|
80
|
+
```typescript
|
81
|
+
import { createPublishTransaction, createAppendTransaction } from 'ckbfs-api';
|
82
|
+
|
83
|
+
// Create a transaction without signing or sending
|
84
|
+
const tx = await ckbfs.createPublishTransaction('path/to/file.txt');
|
85
|
+
|
86
|
+
// Customize the transaction before signing
|
87
|
+
// ... modify tx ...
|
88
|
+
|
89
|
+
// Sign and send manually
|
90
|
+
const signedTx = await signer.signTransaction(tx);
|
91
|
+
const txHash = await client.sendTransaction(signedTx);
|
92
|
+
```
|
93
|
+
|
94
|
+
## Examples
|
95
|
+
|
96
|
+
The SDK comes with several examples demonstrating how to use it:
|
97
|
+
|
98
|
+
### Running Examples
|
99
|
+
|
100
|
+
```bash
|
101
|
+
# Show available examples
|
102
|
+
npm run example
|
103
|
+
|
104
|
+
# Run the publish file example
|
105
|
+
npm run example:publish
|
106
|
+
|
107
|
+
# Run the append file example (requires a transaction hash)
|
108
|
+
npm run example:append -- --txhash=0x123456...
|
109
|
+
# OR
|
110
|
+
PUBLISH_TX_HASH=0x123456... npm run example:append
|
111
|
+
```
|
112
|
+
|
113
|
+
### Example Files
|
114
|
+
|
115
|
+
- `examples/publish.ts` - Shows how to publish a file to CKBFS
|
116
|
+
- `examples/append.ts` - Shows how to append to a previously published file
|
117
|
+
|
118
|
+
To run the examples, first set your CKB private key:
|
119
|
+
|
120
|
+
```bash
|
121
|
+
export CKB_PRIVATE_KEY=your_private_key_here
|
122
|
+
```
|
123
|
+
|
124
|
+
Note: The append example requires a valid transaction hash from a previously published file. You can either provide it via command line argument or environment variable.
|
125
|
+
|
126
|
+
## Contributing
|
127
|
+
|
128
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
129
|
+
|
130
|
+
## License
|
131
|
+
|
132
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
package/RFC.v2.md
ADDED
@@ -0,0 +1,341 @@
|
|
1
|
+
# CKBFS Protocol
|
2
|
+
|
3
|
+
`CKBFS` is a protocol defined in order to describe a witnesses based file storage system. With CKBFS, one can:
|
4
|
+
|
5
|
+
- Store files on Nervos CKB Network
|
6
|
+
- Publish large files that may exceed block size limitation(~500kb), across multiple blocks, while still keep the index and retrieve action simple and straight
|
7
|
+
|
8
|
+
It contains:
|
9
|
+
|
10
|
+
- A hasher binary that uses [Adler-32 checksum algorithm](https://en.wikipedia.org/wiki/Adler-32)
|
11
|
+
- A type script contract that applies restrictions to avoid invalid file publication
|
12
|
+
|
13
|
+
## Core Concepts
|
14
|
+
|
15
|
+
Core concepts of CKBFS is:
|
16
|
+
|
17
|
+
- One single cell(called CKBFS cell) to index one file, even multi-part file that split across blocks.
|
18
|
+
- **Permanent** storage. Deletion is **forbidden** through contract, which means the file metadata will never lost. Which also means you will need to lock some CKB capacity **FOREVER** in order to create it.
|
19
|
+
- Simple cell structure, encoded with molecule.
|
20
|
+
- Built-in checksum provided, both on-chain and off-chain side.
|
21
|
+
|
22
|
+
# The Protocol - or, the standard
|
23
|
+
|
24
|
+
## Data Structure
|
25
|
+
|
26
|
+
### CKBFS Cell
|
27
|
+
|
28
|
+
CKBFS Cell is a cell that stores:
|
29
|
+
|
30
|
+
A CKBFS cell in should looks like following:
|
31
|
+
|
32
|
+
```yaml
|
33
|
+
Data:
|
34
|
+
content_type: Bytes # String Bytes
|
35
|
+
filename: Bytes # String Bytes
|
36
|
+
indexes: Vec<Uint32> # referenced witnesses index.
|
37
|
+
checksum: Uint32 # Adler32 checksum
|
38
|
+
backlinks: Vec<BackLink>
|
39
|
+
|
40
|
+
Type:
|
41
|
+
hash_type: "data1"
|
42
|
+
code_hash: CKBFS_TYPE_DATA_HASH
|
43
|
+
args: <TypeID, 32 bytes>,[<hasher_code_hash>, optional]
|
44
|
+
Lock:
|
45
|
+
<user_defined>
|
46
|
+
```
|
47
|
+
|
48
|
+
The following rules should be met in a CKBFS cell:
|
49
|
+
|
50
|
+
- Rule 1: data structure of a CKBFS cell is molecule encoded. See [Molecule](https://github.com/nervosnetwork/molecule) definitions below.
|
51
|
+
- Rule 2: checksum must match with specified witnesses. Default checksum algorithm will be Alder32 if not specify `hasher_code_hash` in Type script args.
|
52
|
+
- Rule 3: if backlinks(see definition below) of a CKBFS cell is not empty, it means file was stored across blocks.
|
53
|
+
- Rule 4: if `hasher_code_hash` is specified, then it will use hasher binary from CellDeps that matches `code_hash`, with same input parameter.
|
54
|
+
- Rule 5: Once created, a CKBFS cell can only be updated/transfered, which means it can not be destroyed.
|
55
|
+
- UPDATES IN VERSION 2: `indexes` is a vector of witness indexes, stored CKBFS structured contents in splited witnesses.
|
56
|
+
|
57
|
+
---
|
58
|
+
|
59
|
+
### BackLink
|
60
|
+
|
61
|
+
BackLink stands for the prefix part of a living CKBFS cell. The strategy of CKBFS is similar to a linked list:
|
62
|
+
|
63
|
+
[backlink]←[backlink]←[…]←[CKBFS cell]
|
64
|
+
|
65
|
+
```yaml
|
66
|
+
BackLink:
|
67
|
+
tx_hash: Bytes,
|
68
|
+
indexes: Vec<Uint32>,
|
69
|
+
checksum: Uint32,
|
70
|
+
```
|
71
|
+
|
72
|
+
---
|
73
|
+
|
74
|
+
### Witnesses
|
75
|
+
|
76
|
+
File contents are stored in witnesses. In a single transaction, witnesses can be splitted into multiple parts and concat together while verification.
|
77
|
+
|
78
|
+
```yaml
|
79
|
+
Witnesses:
|
80
|
+
<"CKBFS"><0x00><CONTENT_BYTES>
|
81
|
+
```
|
82
|
+
|
83
|
+
The following rules should be met for witnesses used in CKBFS:
|
84
|
+
|
85
|
+
- Rule 6: The first 5 bytes must be UTF8 coded string bytes of `CKBFS`, which should be: `0x434b424653`
|
86
|
+
- Rule 7: The 6th byte of witnesses must be the version of CKBFS protocol, which should be: `0x00`.
|
87
|
+
- Rule 8: File contents bytes are stored from 7th byte. Checksum hasher should also take bytes from `[7…]`.
|
88
|
+
- UPDATES IN VERSION 2: Every parts of the splitted content stored in witnesses must follow Rule 6 to Rule 8.
|
89
|
+
|
90
|
+
---
|
91
|
+
|
92
|
+
## Operations
|
93
|
+
|
94
|
+
This section describes operations and restrictions in CKBFS implementation
|
95
|
+
|
96
|
+
### Publish
|
97
|
+
|
98
|
+
Publish operation creates one or more new CKBFS cell.
|
99
|
+
|
100
|
+
```yaml
|
101
|
+
// Publish
|
102
|
+
Witnesses:
|
103
|
+
<...>
|
104
|
+
<0x434b424653, 0x0, CKBFS_CONTENT_BYTES>
|
105
|
+
<...>
|
106
|
+
Inputs:
|
107
|
+
<...>
|
108
|
+
Outputs:
|
109
|
+
<vec> CKBFS_CELL
|
110
|
+
Data:
|
111
|
+
content-type: string
|
112
|
+
filename: string
|
113
|
+
indexes: vec<uint32>
|
114
|
+
checksum: uint32
|
115
|
+
backlinks: empty_vec
|
116
|
+
Type:
|
117
|
+
code_hash: ckbfs type script
|
118
|
+
args: 32 bytes type_id, (...)
|
119
|
+
<...>
|
120
|
+
```
|
121
|
+
|
122
|
+
Publish operation must satisfy following rule:
|
123
|
+
|
124
|
+
- Rule 9: in a publish operation, checksum must be equal with `hash(Witnesses[index])`.
|
125
|
+
|
126
|
+
---
|
127
|
+
|
128
|
+
### Append
|
129
|
+
|
130
|
+
Append operation updates exist live CKBFS cell, validates the latest checksum.
|
131
|
+
|
132
|
+
```yaml
|
133
|
+
// Append
|
134
|
+
Witnesses:
|
135
|
+
<...>
|
136
|
+
<CKBFS_CONTENT_BYTES>
|
137
|
+
<...>
|
138
|
+
Inputs:
|
139
|
+
<...>
|
140
|
+
CKBFS_CELL
|
141
|
+
Data:
|
142
|
+
content-type: string
|
143
|
+
filename: string
|
144
|
+
indexes: vec<uint32>
|
145
|
+
checksum: uint32
|
146
|
+
backlinks: empty_vec
|
147
|
+
Type:
|
148
|
+
code_hash: ckbfs type script
|
149
|
+
args: 32 bytes type_id, (...)
|
150
|
+
<...>
|
151
|
+
Outputs:
|
152
|
+
<...>
|
153
|
+
CKBFS_CELL:
|
154
|
+
Data:
|
155
|
+
content-type: string
|
156
|
+
filename: string
|
157
|
+
indexes: vec<uint32>
|
158
|
+
checksum: uint32 # updated checksum
|
159
|
+
backlinks: vec<BackLink>
|
160
|
+
Type:
|
161
|
+
code_hash: ckbfs type script
|
162
|
+
args: 32 bytes type_id, (...)
|
163
|
+
```
|
164
|
+
|
165
|
+
- Rule 10: backlinks field of a CKBFS cell can only be appended. Once allocated, all records in the vector can not be modified.
|
166
|
+
- Rule 11: new checksum of updated CKBFS cell should be equal to: `hasher.recover_from(old_checksum).update(new_content_bytes)`
|
167
|
+
- Rule 12: `content-type`, `filename`, and Type args of a CKBFS cell CAN NOT be updated in ANY condition
|
168
|
+
- Rule 13: in an append operation, Output CKBFS Cell’s `indexes` can not be `empty`
|
169
|
+
|
170
|
+
---
|
171
|
+
|
172
|
+
### Transfer
|
173
|
+
|
174
|
+
Transfer operation transfers ownership of a CKBFS cell, and ensure it did not lost tracking of backlinks.
|
175
|
+
|
176
|
+
```yaml
|
177
|
+
// Transfer
|
178
|
+
Witnesses:
|
179
|
+
<...>
|
180
|
+
Inputs:
|
181
|
+
<...>
|
182
|
+
CKBFS_CELL
|
183
|
+
Data:
|
184
|
+
content-type: string
|
185
|
+
filename: string
|
186
|
+
index: uint32
|
187
|
+
checksum: uint32
|
188
|
+
backlinks: empty_vec
|
189
|
+
Type:
|
190
|
+
code_hash: ckbfs type script
|
191
|
+
args: 32 bytes type_id, (...)
|
192
|
+
Lock:
|
193
|
+
<USER_DEFINED>
|
194
|
+
<...>
|
195
|
+
Outputs:
|
196
|
+
<...>
|
197
|
+
CKBFS_CELL:
|
198
|
+
Data:
|
199
|
+
content-type: string
|
200
|
+
filename: string
|
201
|
+
index: null
|
202
|
+
checksum: uint32 # updated checksum
|
203
|
+
backlinks: vec<BackLink>
|
204
|
+
Type:
|
205
|
+
code_hash: ckbfs type script
|
206
|
+
args: 32 bytes type_id, (...)
|
207
|
+
Lock:
|
208
|
+
<USER_DEFINED>
|
209
|
+
```
|
210
|
+
|
211
|
+
- Rule 14: in a transfer operation, Output CKBFS Cell’s `indexes` must be empty
|
212
|
+
- Rule 15: if Input CKBFS Cell’s backlinks is empty, then output’s backlink should be append following Rule 10. Otherwise, the backlinks should not be updated
|
213
|
+
- Rule 16: in a transfer operation, `checksum` CAN NOT be updated
|
214
|
+
|
215
|
+
---
|
216
|
+
|
217
|
+
## Other Notes
|
218
|
+
|
219
|
+
### Molecule Definitions:
|
220
|
+
|
221
|
+
Here’s molecule definitions of CKBFS data structures
|
222
|
+
|
223
|
+
```jsx
|
224
|
+
vector Bytes <byte>;
|
225
|
+
option BytesOpt (Bytes);
|
226
|
+
option Uint32Opt (Uint32);
|
227
|
+
vector Indexes <index>;
|
228
|
+
|
229
|
+
table BackLink {
|
230
|
+
tx_hash: Bytes,
|
231
|
+
index: Indexes,
|
232
|
+
checksum: Uint32,
|
233
|
+
}
|
234
|
+
|
235
|
+
vector BackLinks <BackLink>;
|
236
|
+
|
237
|
+
table CKBFSData {
|
238
|
+
index: Indexes,
|
239
|
+
checksum: Uint32,
|
240
|
+
content_type: Bytes,
|
241
|
+
filename: Bytes,
|
242
|
+
backlinks: BackLinks,
|
243
|
+
}
|
244
|
+
```
|
245
|
+
|
246
|
+
### Checksum Validator Procedure:
|
247
|
+
|
248
|
+
Bellow is pseudocodes shows how one can validates the checksum:
|
249
|
+
|
250
|
+
```pascal
|
251
|
+
function validate_checksum(witness, expected_checksum, backlinks);
|
252
|
+
var
|
253
|
+
hasher: thasher;
|
254
|
+
computed_checksum: uint32;
|
255
|
+
content_bytes: bytes;
|
256
|
+
last_backlink: backlink;
|
257
|
+
begin
|
258
|
+
// If backlinks is not empty, recover hasher state from the last backlink's checksum
|
259
|
+
if length(backlinks) > 0 then
|
260
|
+
begin
|
261
|
+
last_backlink := backlinks[length(backlinks) - 1];
|
262
|
+
hasher.recover(last_backlink.checksum);
|
263
|
+
end;
|
264
|
+
|
265
|
+
// Extract the content bytes from the witness starting from the 7th byte
|
266
|
+
content_bytes := copy(witness, 7, length(witness) - 6);
|
267
|
+
|
268
|
+
// Update the hasher with the content bytes
|
269
|
+
hasher.update(content_bytes);
|
270
|
+
|
271
|
+
// Finalize and compute the checksum
|
272
|
+
computed_checksum := hasher.finalize;
|
273
|
+
|
274
|
+
// Compare the computed checksum with the expected checksum
|
275
|
+
if computed_checksum = expected_checksum then
|
276
|
+
validate_checksum := true
|
277
|
+
else
|
278
|
+
validate_checksum := false;
|
279
|
+
end;
|
280
|
+
|
281
|
+
```
|
282
|
+
|
283
|
+
### Advanced Usage - Branch Forking File Appendix
|
284
|
+
|
285
|
+
Assuming that we have created a CKBFS Cell:
|
286
|
+
|
287
|
+
```yaml
|
288
|
+
CKBFS_CELL:
|
289
|
+
Data:
|
290
|
+
content-type: string
|
291
|
+
filename: string
|
292
|
+
indexes: [0x0]
|
293
|
+
checksum: 0xFE02EA11
|
294
|
+
backlinks: [BACKLINK_1, BACKLINK_2, ...]
|
295
|
+
Type:
|
296
|
+
code_hash: CKBFS_CODE_HASH
|
297
|
+
args: TYPE_ID_A
|
298
|
+
Lock:
|
299
|
+
<USER_DEFINED>
|
300
|
+
```
|
301
|
+
|
302
|
+
It is able to creating a forking of this CKBFS by a special publish, similar to append but put the referenced CKBFS Cell in CellDeps:
|
303
|
+
|
304
|
+
```yaml
|
305
|
+
CellDeps:
|
306
|
+
<...>
|
307
|
+
CKBFS_CELL:
|
308
|
+
Data:
|
309
|
+
content-type: string
|
310
|
+
filename: string
|
311
|
+
indexes: [0x0]
|
312
|
+
checksum: 0xFE02EA11
|
313
|
+
backlinks: [BACKLINK_1, BACKLINK_2, ...]
|
314
|
+
Type:
|
315
|
+
code_hash: CKBFS_CODE_HASH
|
316
|
+
args: TYPE_ID_A
|
317
|
+
Lock:
|
318
|
+
<USER_DEFINED>
|
319
|
+
<...>
|
320
|
+
Witnesses:
|
321
|
+
<...>
|
322
|
+
<CKBFS_CONTENT_BYTES>
|
323
|
+
<...>
|
324
|
+
Inputs:
|
325
|
+
<...>
|
326
|
+
Outputs:
|
327
|
+
<...>
|
328
|
+
CKBFS_CELL
|
329
|
+
Data:
|
330
|
+
content-type: string
|
331
|
+
filename: string
|
332
|
+
indexes: [uint32]
|
333
|
+
checksum: UPDATED_CHECKSUM
|
334
|
+
backlinks: [BACKLINK_1, BACKLINK_2, ...]
|
335
|
+
Type:
|
336
|
+
code_hash: ckbfs type script
|
337
|
+
args: TYPE_ID_B
|
338
|
+
<...>
|
339
|
+
```
|
340
|
+
|
341
|
+
And we are able to create a variant versions from a same reference data, allowing us to achieve something like git branching, shared-header data, etc.
|
package/append.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
This is content to append to the previously published file.
|
package/example.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
This is a sample file for testing CKBFS.
|
@@ -0,0 +1,222 @@
|
|
1
|
+
import { CKBFS, NetworkType, ProtocolVersion, CKBFSDataType, extractCKBFSWitnessContent, isCKBFSWitness, CKBFSData } from '../src/index';
|
2
|
+
import { Script, ClientPublicTestnet, Transaction } 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 with network and version options
|
24
|
+
const ckbfs = new CKBFS(
|
25
|
+
privateKey,
|
26
|
+
NetworkType.Testnet, // Use testnet
|
27
|
+
{
|
28
|
+
version: ProtocolVersion.V2, // Use the latest version (V2)
|
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 transaction using CKB client
|
39
|
+
* @param txHash The transaction hash to get cell information from
|
40
|
+
* @returns Promise resolving to the cell information
|
41
|
+
*/
|
42
|
+
async function getCellInfoFromTransaction(txHash: string): Promise<{
|
43
|
+
outPoint: { txHash: string; index: number };
|
44
|
+
type: Script;
|
45
|
+
data: CKBFSDataType;
|
46
|
+
lock: Script;
|
47
|
+
capacity: bigint;
|
48
|
+
}> {
|
49
|
+
console.log(`Retrieving transaction data for: ${txHash}`);
|
50
|
+
|
51
|
+
try {
|
52
|
+
// Get transaction from RPC
|
53
|
+
const txWithStatus = await client.getTransaction(txHash);
|
54
|
+
if (!txWithStatus || !txWithStatus.transaction) {
|
55
|
+
throw new Error(`Transaction ${txHash} not found`);
|
56
|
+
}
|
57
|
+
|
58
|
+
const tx = Transaction.from(txWithStatus.transaction);
|
59
|
+
console.log(`Transaction found with ${tx.outputs.length} outputs`);
|
60
|
+
|
61
|
+
// Find the CKBFS cell output (first output with type script)
|
62
|
+
let ckbfsCellIndex = 0;
|
63
|
+
const output = tx.outputs[ckbfsCellIndex];
|
64
|
+
if (!output || !output.type) {
|
65
|
+
throw new Error('No CKBFS cell found in the transaction');
|
66
|
+
}
|
67
|
+
|
68
|
+
console.log(`Found CKBFS cell at index ${ckbfsCellIndex}`);
|
69
|
+
console.log(`Cell type script hash: ${output.type.hash()}`);
|
70
|
+
|
71
|
+
// Get output data
|
72
|
+
const outputData = tx.outputsData[ckbfsCellIndex];
|
73
|
+
if (!outputData) {
|
74
|
+
throw new Error('Output data not found');
|
75
|
+
}
|
76
|
+
|
77
|
+
// Parse the output data as CKBFS data
|
78
|
+
// First remove 0x prefix if present
|
79
|
+
const rawData = outputData.startsWith('0x')
|
80
|
+
? Buffer.from(outputData.slice(2), 'hex')
|
81
|
+
: Buffer.from(outputData, 'hex');
|
82
|
+
|
83
|
+
// For demonstration purposes, we'll manually create a CKBFSDataType object
|
84
|
+
// In a real app, you would properly decode the rawData from molecule format
|
85
|
+
const sampleDataFields = {
|
86
|
+
index: [1],
|
87
|
+
checksum: 12345,
|
88
|
+
contentType: new TextEncoder().encode('text/plain'),
|
89
|
+
filename: new TextEncoder().encode('example.txt'),
|
90
|
+
backLinks: []
|
91
|
+
};
|
92
|
+
|
93
|
+
// Use this as our data
|
94
|
+
const ckbfsData: CKBFSDataType = sampleDataFields;
|
95
|
+
|
96
|
+
console.log(`CKBFS data processed`);
|
97
|
+
console.log(`Using filename: ${new TextDecoder().decode(ckbfsData.filename)}`);
|
98
|
+
console.log(`Using content type: ${new TextDecoder().decode(ckbfsData.contentType)}`);
|
99
|
+
|
100
|
+
return {
|
101
|
+
outPoint: {
|
102
|
+
txHash,
|
103
|
+
index: ckbfsCellIndex
|
104
|
+
},
|
105
|
+
type: output.type,
|
106
|
+
lock: output.lock,
|
107
|
+
capacity: output.capacity,
|
108
|
+
data: ckbfsData
|
109
|
+
};
|
110
|
+
} catch (error) {
|
111
|
+
console.error('Error retrieving transaction data:', error);
|
112
|
+
|
113
|
+
// Fallback to simulated data for demonstration purposes
|
114
|
+
console.warn('FALLBACK: Using simulated data for demonstration');
|
115
|
+
|
116
|
+
// Get lock script for this account
|
117
|
+
const lock = await ckbfs.getLock();
|
118
|
+
|
119
|
+
// Get CKBFS script config
|
120
|
+
const config = ckbfs.getCKBFSConfig();
|
121
|
+
|
122
|
+
// Create sample data for demonstration
|
123
|
+
const sampleData: CKBFSDataType = {
|
124
|
+
index: [1],
|
125
|
+
checksum: 12345, // Sample checksum number
|
126
|
+
contentType: new TextEncoder().encode('text/plain'),
|
127
|
+
filename: new TextEncoder().encode('example.txt'),
|
128
|
+
backLinks: []
|
129
|
+
};
|
130
|
+
|
131
|
+
// Create the type script
|
132
|
+
const typeArgs = '0x3cc03661013140855e756c032ce83bc270a7ca3f1f3b76ec21a8ea0155ac3a7c';
|
133
|
+
const typeScript = new Script(
|
134
|
+
ensureHexPrefix(config.codeHash),
|
135
|
+
config.hashType as any,
|
136
|
+
typeArgs
|
137
|
+
);
|
138
|
+
|
139
|
+
return {
|
140
|
+
outPoint: {
|
141
|
+
txHash,
|
142
|
+
index: 0
|
143
|
+
},
|
144
|
+
type: typeScript,
|
145
|
+
lock,
|
146
|
+
capacity: 200n * 100000000n,
|
147
|
+
data: sampleData
|
148
|
+
};
|
149
|
+
}
|
150
|
+
}
|
151
|
+
|
152
|
+
/**
|
153
|
+
* Example of appending content to an existing CKBFS file
|
154
|
+
*/
|
155
|
+
async function appendExample() {
|
156
|
+
try {
|
157
|
+
// Validate transaction hash
|
158
|
+
if (publishTxHash === '0x0000000000000000000000000000000000000000000000000000000000000000') {
|
159
|
+
throw new Error('Please provide a valid transaction hash by setting the PUBLISH_TX_HASH environment variable or using --txhash=0x... argument');
|
160
|
+
}
|
161
|
+
|
162
|
+
// Get address info
|
163
|
+
const address = await ckbfs.getAddress();
|
164
|
+
console.log(`Using address: ${address.toString()}`);
|
165
|
+
|
166
|
+
// Get lock script
|
167
|
+
const lock = await ckbfs.getLock();
|
168
|
+
|
169
|
+
// Get the cell information from the transaction
|
170
|
+
console.log(`Getting cell info from transaction: ${publishTxHash}`);
|
171
|
+
const ckbfsCell = await getCellInfoFromTransaction(publishTxHash);
|
172
|
+
|
173
|
+
// Append content from a file
|
174
|
+
const filePath = './append.txt';
|
175
|
+
console.log(`Appending file: ${filePath}`);
|
176
|
+
|
177
|
+
// Create a sample append.txt file for the demonstration
|
178
|
+
const fs = require('fs');
|
179
|
+
if (!fs.existsSync(filePath)) {
|
180
|
+
fs.writeFileSync(filePath, 'This is content to append to the previously published file.');
|
181
|
+
console.log(`Created sample file: ${filePath}`);
|
182
|
+
}
|
183
|
+
|
184
|
+
console.log('Note: When appending data, the capacity of the cell may need to increase.');
|
185
|
+
console.log('The SDK will automatically handle this by adding additional inputs if needed.');
|
186
|
+
console.log('Make sure your account has enough CKB to cover the increased capacity and fees.');
|
187
|
+
|
188
|
+
const txHash = await ckbfs.appendFile(filePath, ckbfsCell);
|
189
|
+
|
190
|
+
console.log(`File appended successfully!`);
|
191
|
+
console.log(`Transaction Hash: ${txHash}`);
|
192
|
+
console.log(`View at: https://pudge.explorer.nervos.org/transaction/${txHash}`);
|
193
|
+
|
194
|
+
return txHash;
|
195
|
+
} catch (error) {
|
196
|
+
console.error('Error appending file:', error);
|
197
|
+
throw error;
|
198
|
+
}
|
199
|
+
}
|
200
|
+
|
201
|
+
/**
|
202
|
+
* Main function to run the example
|
203
|
+
*/
|
204
|
+
async function main() {
|
205
|
+
console.log('Running CKBFS append example...');
|
206
|
+
console.log('-------------------------------');
|
207
|
+
console.log(`Using CKBFS protocol version: ${ProtocolVersion.V2}`);
|
208
|
+
|
209
|
+
try {
|
210
|
+
await appendExample();
|
211
|
+
console.log('Example completed successfully!');
|
212
|
+
process.exit(0);
|
213
|
+
} catch (error) {
|
214
|
+
console.error('Example failed:', error);
|
215
|
+
process.exit(1);
|
216
|
+
}
|
217
|
+
}
|
218
|
+
|
219
|
+
// Run the example if this file is executed directly
|
220
|
+
if (require.main === module) {
|
221
|
+
main().catch(console.error);
|
222
|
+
}
|