@ckbfs/api 1.5.1 → 2.0.1
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 +31 -6
- package/RFC.v3.md +210 -0
- package/dist/index.d.ts +72 -7
- package/dist/index.js +437 -75
- package/dist/utils/checksum.d.ts +16 -0
- package/dist/utils/checksum.js +74 -8
- package/dist/utils/constants.d.ts +2 -1
- package/dist/utils/constants.js +12 -2
- package/dist/utils/file.d.ts +44 -0
- package/dist/utils/file.js +303 -30
- package/dist/utils/molecule.d.ts +13 -1
- package/dist/utils/molecule.js +32 -5
- package/dist/utils/transaction-backup.d.ts +117 -0
- package/dist/utils/transaction-backup.js +624 -0
- package/dist/utils/transaction.d.ts +7 -115
- package/dist/utils/transaction.js +45 -622
- package/dist/utils/transactions/index.d.ts +8 -0
- package/dist/utils/transactions/index.js +31 -0
- package/dist/utils/transactions/shared.d.ts +57 -0
- package/dist/utils/transactions/shared.js +17 -0
- package/dist/utils/transactions/v1v2.d.ts +80 -0
- package/dist/utils/transactions/v1v2.js +592 -0
- package/dist/utils/transactions/v3.d.ts +124 -0
- package/dist/utils/transactions/v3.js +369 -0
- package/dist/utils/witness.d.ts +45 -0
- package/dist/utils/witness.js +145 -3
- package/examples/append-v3.ts +310 -0
- package/examples/chunked-publish.ts +307 -0
- package/examples/publish-v3.ts +152 -0
- package/examples/publish.ts +4 -4
- package/examples/retrieve-v3.ts +222 -0
- package/package.json +6 -2
- package/small-example.txt +1 -0
- package/src/index.ts +568 -87
- package/src/utils/checksum.ts +90 -9
- package/src/utils/constants.ts +19 -2
- package/src/utils/file.ts +386 -35
- package/src/utils/molecule.ts +43 -6
- package/src/utils/transaction-backup.ts +849 -0
- package/src/utils/transaction.ts +39 -848
- package/src/utils/transactions/index.ts +16 -0
- package/src/utils/transactions/shared.ts +64 -0
- package/src/utils/transactions/v1v2.ts +791 -0
- package/src/utils/transactions/v3.ts +564 -0
- package/src/utils/witness.ts +193 -0
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ensureHexPrefix = ensureHexPrefix;
|
|
4
|
+
exports.createCKBFSCell = createCKBFSCell;
|
|
5
|
+
exports.preparePublishTransaction = preparePublishTransaction;
|
|
6
|
+
exports.createPublishTransaction = createPublishTransaction;
|
|
7
|
+
exports.prepareAppendTransaction = prepareAppendTransaction;
|
|
8
|
+
exports.createAppendTransaction = createAppendTransaction;
|
|
9
|
+
exports.createAppendTransactionDry = createAppendTransactionDry;
|
|
10
|
+
exports.publishCKBFS = publishCKBFS;
|
|
11
|
+
exports.appendCKBFS = appendCKBFS;
|
|
12
|
+
const core_1 = require("@ckb-ccc/core");
|
|
13
|
+
const checksum_1 = require("./checksum");
|
|
14
|
+
const molecule_1 = require("./molecule");
|
|
15
|
+
const witness_1 = require("./witness");
|
|
16
|
+
const constants_1 = require("./constants");
|
|
17
|
+
/**
|
|
18
|
+
* Ensures a string is prefixed with '0x'
|
|
19
|
+
* @param value The string to ensure is hex prefixed
|
|
20
|
+
* @returns A hex prefixed string
|
|
21
|
+
*/
|
|
22
|
+
function ensureHexPrefix(value) {
|
|
23
|
+
if (value.startsWith("0x")) {
|
|
24
|
+
return value;
|
|
25
|
+
}
|
|
26
|
+
return `0x${value}`;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Creates a CKBFS cell
|
|
30
|
+
* @param options Options for creating the CKBFS cell
|
|
31
|
+
* @returns The created cell output
|
|
32
|
+
*/
|
|
33
|
+
function createCKBFSCell(options) {
|
|
34
|
+
const { contentType, filename, capacity, lock, network = constants_1.DEFAULT_NETWORK, version = constants_1.DEFAULT_VERSION, useTypeID = false, } = options;
|
|
35
|
+
// Get CKBFS script config
|
|
36
|
+
const config = (0, constants_1.getCKBFSScriptConfig)(network, version, useTypeID);
|
|
37
|
+
// Create pre CKBFS type script
|
|
38
|
+
const preCkbfsTypeScript = new core_1.Script(ensureHexPrefix(config.codeHash), config.hashType, "0x0000000000000000000000000000000000000000000000000000000000000000");
|
|
39
|
+
// Return the cell output
|
|
40
|
+
return {
|
|
41
|
+
lock,
|
|
42
|
+
type: preCkbfsTypeScript,
|
|
43
|
+
capacity: capacity || 200n * 100000000n, // Default 200 CKB
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Prepares a transaction for publishing a file to CKBFS without fee and change handling
|
|
48
|
+
* You will need to manually set the typeID if you did not provide inputs, or just check is return value emptyTypeID is true
|
|
49
|
+
* @param options Options for publishing the file
|
|
50
|
+
* @returns Promise resolving to the prepared transaction and the output index of CKBFS Cell
|
|
51
|
+
*/
|
|
52
|
+
async function preparePublishTransaction(options) {
|
|
53
|
+
const { from, contentChunks, contentType, filename, lock, capacity, network = constants_1.DEFAULT_NETWORK, version = constants_1.DEFAULT_VERSION, useTypeID = false, } = options;
|
|
54
|
+
// Calculate checksum for the combined content
|
|
55
|
+
const combinedContent = Buffer.concat(contentChunks);
|
|
56
|
+
const checksum = await (0, checksum_1.calculateChecksum)(combinedContent);
|
|
57
|
+
// Create CKBFS witnesses - each chunk already includes the CKBFS header
|
|
58
|
+
// Pass 0 as version byte - this is the protocol version byte in the witness header
|
|
59
|
+
// not to be confused with the Protocol Version (V1 vs V2)
|
|
60
|
+
const ckbfsWitnesses = (0, witness_1.createChunkedCKBFSWitnesses)(contentChunks);
|
|
61
|
+
// Calculate the actual witness indices where our content is placed
|
|
62
|
+
const contentStartIndex = from?.witnesses.length || 1;
|
|
63
|
+
const witnessIndices = Array.from({ length: contentChunks.length }, (_, i) => contentStartIndex + i);
|
|
64
|
+
// Create CKBFS cell output data based on version
|
|
65
|
+
let outputData;
|
|
66
|
+
if (version === constants_1.ProtocolVersion.V1) {
|
|
67
|
+
// V1 format: Single index field (a single number, not an array)
|
|
68
|
+
// For V1, use the first index where content is placed
|
|
69
|
+
outputData = molecule_1.CKBFSData.pack({
|
|
70
|
+
index: contentStartIndex,
|
|
71
|
+
checksum,
|
|
72
|
+
contentType: contentType,
|
|
73
|
+
filename: filename,
|
|
74
|
+
backLinks: [],
|
|
75
|
+
}, version);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// V2 format: Multiple indexes (array of numbers)
|
|
79
|
+
// For V2, use all the indices where content is placed
|
|
80
|
+
outputData = molecule_1.CKBFSData.pack({
|
|
81
|
+
indexes: witnessIndices,
|
|
82
|
+
checksum,
|
|
83
|
+
contentType,
|
|
84
|
+
filename,
|
|
85
|
+
backLinks: [],
|
|
86
|
+
}, version);
|
|
87
|
+
}
|
|
88
|
+
// Get CKBFS script config
|
|
89
|
+
const config = (0, constants_1.getCKBFSScriptConfig)(network, version, useTypeID);
|
|
90
|
+
const preCkbfsTypeScript = new core_1.Script(ensureHexPrefix(config.codeHash), config.hashType, "0x0000000000000000000000000000000000000000000000000000000000000000");
|
|
91
|
+
const ckbfsCellSize = BigInt(outputData.length +
|
|
92
|
+
preCkbfsTypeScript.occupiedSize +
|
|
93
|
+
lock.occupiedSize +
|
|
94
|
+
8) * 100000000n;
|
|
95
|
+
// Create pre transaction without cell deps initially
|
|
96
|
+
let preTx;
|
|
97
|
+
if (from) {
|
|
98
|
+
// If from is not empty, inject/merge the fields
|
|
99
|
+
preTx = core_1.Transaction.from({
|
|
100
|
+
...from,
|
|
101
|
+
outputs: from.outputs.length === 0
|
|
102
|
+
? [
|
|
103
|
+
createCKBFSCell({
|
|
104
|
+
contentType,
|
|
105
|
+
filename,
|
|
106
|
+
lock,
|
|
107
|
+
network,
|
|
108
|
+
version,
|
|
109
|
+
useTypeID,
|
|
110
|
+
capacity: ckbfsCellSize || capacity,
|
|
111
|
+
}),
|
|
112
|
+
]
|
|
113
|
+
: [
|
|
114
|
+
...from.outputs,
|
|
115
|
+
createCKBFSCell({
|
|
116
|
+
contentType,
|
|
117
|
+
filename,
|
|
118
|
+
lock,
|
|
119
|
+
network,
|
|
120
|
+
version,
|
|
121
|
+
useTypeID,
|
|
122
|
+
capacity: ckbfsCellSize || capacity,
|
|
123
|
+
}),
|
|
124
|
+
],
|
|
125
|
+
witnesses: from.witnesses.length === 0
|
|
126
|
+
? [
|
|
127
|
+
[], // Empty secp witness for signing if not provided
|
|
128
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
129
|
+
]
|
|
130
|
+
: [
|
|
131
|
+
...from.witnesses,
|
|
132
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
133
|
+
],
|
|
134
|
+
outputsData: from.outputsData.length === 0
|
|
135
|
+
? [outputData]
|
|
136
|
+
: [
|
|
137
|
+
...from.outputsData,
|
|
138
|
+
outputData,
|
|
139
|
+
],
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
preTx = core_1.Transaction.from({
|
|
144
|
+
outputs: [
|
|
145
|
+
createCKBFSCell({
|
|
146
|
+
contentType,
|
|
147
|
+
filename,
|
|
148
|
+
lock,
|
|
149
|
+
network,
|
|
150
|
+
version,
|
|
151
|
+
useTypeID,
|
|
152
|
+
capacity: ckbfsCellSize || capacity,
|
|
153
|
+
}),
|
|
154
|
+
],
|
|
155
|
+
witnesses: [
|
|
156
|
+
[], // Empty secp witness for signing
|
|
157
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
158
|
+
],
|
|
159
|
+
outputsData: [outputData],
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
// Add the CKBFS dep group cell dependency
|
|
163
|
+
preTx.addCellDeps({
|
|
164
|
+
outPoint: {
|
|
165
|
+
txHash: ensureHexPrefix(config.depTxHash),
|
|
166
|
+
index: config.depIndex || 0,
|
|
167
|
+
},
|
|
168
|
+
depType: "depGroup",
|
|
169
|
+
});
|
|
170
|
+
// Create type ID args
|
|
171
|
+
const outputIndex = from ? from.outputs.length : 0;
|
|
172
|
+
const args = preTx.inputs.length > 0 ? core_1.ccc.hashTypeId(preTx.inputs[0], outputIndex) : "0x0000000000000000000000000000000000000000000000000000000000000000";
|
|
173
|
+
// Create CKBFS type script with type ID
|
|
174
|
+
const ckbfsTypeScript = new core_1.Script(ensureHexPrefix(config.codeHash), config.hashType, args);
|
|
175
|
+
// Create final transaction with same cell deps as preTx
|
|
176
|
+
const tx = core_1.Transaction.from({
|
|
177
|
+
cellDeps: preTx.cellDeps,
|
|
178
|
+
witnesses: preTx.witnesses,
|
|
179
|
+
outputsData: preTx.outputsData,
|
|
180
|
+
inputs: preTx.inputs,
|
|
181
|
+
outputs: outputIndex === 0
|
|
182
|
+
? [
|
|
183
|
+
{
|
|
184
|
+
lock,
|
|
185
|
+
type: ckbfsTypeScript,
|
|
186
|
+
capacity: preTx.outputs[outputIndex].capacity,
|
|
187
|
+
},
|
|
188
|
+
]
|
|
189
|
+
: [
|
|
190
|
+
...preTx.outputs.slice(0, outputIndex), // Include rest of outputs (e.g., change)
|
|
191
|
+
{
|
|
192
|
+
lock,
|
|
193
|
+
type: ckbfsTypeScript,
|
|
194
|
+
capacity: preTx.outputs[outputIndex].capacity,
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
});
|
|
198
|
+
return { tx, outputIndex, emptyTypeID: args === "0x0000000000000000000000000000000000000000000000000000000000000000" };
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Creates a transaction for publishing a file to CKBFS
|
|
202
|
+
* @param signer The signer to use for the transaction
|
|
203
|
+
* @param options Options for publishing the file
|
|
204
|
+
* @returns Promise resolving to the created transaction
|
|
205
|
+
*/
|
|
206
|
+
async function createPublishTransaction(signer, options) {
|
|
207
|
+
const { feeRate, lock, } = options;
|
|
208
|
+
// Use preparePublishTransaction to create the base transaction
|
|
209
|
+
const { tx: preTx, outputIndex, emptyTypeID } = await preparePublishTransaction(options);
|
|
210
|
+
// Complete inputs by capacity
|
|
211
|
+
await preTx.completeInputsByCapacity(signer);
|
|
212
|
+
// Complete fee change to lock
|
|
213
|
+
await preTx.completeFeeChangeToLock(signer, lock, feeRate || 2000);
|
|
214
|
+
// If emptyTypeID is true, we need to create the proper type ID args
|
|
215
|
+
if (emptyTypeID) {
|
|
216
|
+
// Get CKBFS script config
|
|
217
|
+
const config = (0, constants_1.getCKBFSScriptConfig)(options.network || constants_1.DEFAULT_NETWORK, options.version || constants_1.DEFAULT_VERSION, options.useTypeID || false);
|
|
218
|
+
// Create type ID args
|
|
219
|
+
const args = core_1.ccc.hashTypeId(preTx.inputs[0], outputIndex);
|
|
220
|
+
// Create CKBFS type script with type ID
|
|
221
|
+
const ckbfsTypeScript = new core_1.Script(ensureHexPrefix(config.codeHash), config.hashType, args);
|
|
222
|
+
// Create final transaction with updated type script
|
|
223
|
+
const tx = core_1.Transaction.from({
|
|
224
|
+
cellDeps: preTx.cellDeps,
|
|
225
|
+
witnesses: preTx.witnesses,
|
|
226
|
+
outputsData: preTx.outputsData,
|
|
227
|
+
inputs: preTx.inputs,
|
|
228
|
+
outputs: preTx.outputs.map((output, index) => index === outputIndex
|
|
229
|
+
? {
|
|
230
|
+
lock,
|
|
231
|
+
type: ckbfsTypeScript,
|
|
232
|
+
capacity: output.capacity,
|
|
233
|
+
}
|
|
234
|
+
: output),
|
|
235
|
+
});
|
|
236
|
+
return tx;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
// If typeID was already set properly, just reset the first witness for signing
|
|
240
|
+
const tx = core_1.Transaction.from({
|
|
241
|
+
cellDeps: preTx.cellDeps,
|
|
242
|
+
witnesses: preTx.witnesses,
|
|
243
|
+
outputsData: preTx.outputsData,
|
|
244
|
+
inputs: preTx.inputs,
|
|
245
|
+
outputs: preTx.outputs,
|
|
246
|
+
});
|
|
247
|
+
return tx;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Prepares a transaction for appending content to a CKBFS file without fee and change handling
|
|
252
|
+
* @param options Options for appending content
|
|
253
|
+
* @returns Promise resolving to the prepared transaction and the output index of CKBFS Cell
|
|
254
|
+
*/
|
|
255
|
+
async function prepareAppendTransaction(options) {
|
|
256
|
+
const { from, ckbfsCell, contentChunks, network = constants_1.DEFAULT_NETWORK, version = constants_1.DEFAULT_VERSION, } = options;
|
|
257
|
+
const { outPoint, data, type, lock, capacity } = ckbfsCell;
|
|
258
|
+
// Get CKBFS script config early to use version info
|
|
259
|
+
const config = (0, constants_1.getCKBFSScriptConfig)(network, version);
|
|
260
|
+
// Create CKBFS witnesses - each chunk already includes the CKBFS header
|
|
261
|
+
// Pass 0 as version byte - this is the protocol version byte in the witness header
|
|
262
|
+
// not to be confused with the Protocol Version (V1 vs V2)
|
|
263
|
+
const ckbfsWitnesses = (0, witness_1.createChunkedCKBFSWitnesses)(contentChunks);
|
|
264
|
+
// Combine the new content chunks for checksum calculation
|
|
265
|
+
const combinedContent = Buffer.concat(contentChunks);
|
|
266
|
+
// Update the existing checksum with the new content - this matches Adler32's
|
|
267
|
+
// cumulative nature as required by Rule 11 in the RFC
|
|
268
|
+
const contentChecksum = await (0, checksum_1.updateChecksum)(data.checksum, combinedContent);
|
|
269
|
+
// Calculate the actual witness indices where our content is placed
|
|
270
|
+
const contentStartIndex = from?.witnesses.length || 1;
|
|
271
|
+
const witnessIndices = Array.from({ length: contentChunks.length }, (_, i) => contentStartIndex + i);
|
|
272
|
+
// Create backlink for the current state based on version
|
|
273
|
+
let newBackLink;
|
|
274
|
+
if (version === constants_1.ProtocolVersion.V1) {
|
|
275
|
+
// V1 format: Use index field (single number)
|
|
276
|
+
newBackLink = {
|
|
277
|
+
// In V1, field order is index, checksum, txHash
|
|
278
|
+
// and index is a single number value, not an array
|
|
279
|
+
index: data.index ||
|
|
280
|
+
(data.indexes && data.indexes.length > 0 ? data.indexes[0] : 0),
|
|
281
|
+
checksum: data.checksum,
|
|
282
|
+
txHash: outPoint.txHash,
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// V2 format: Use indexes field (array of numbers)
|
|
287
|
+
newBackLink = {
|
|
288
|
+
// In V2, field order is indexes, checksum, txHash
|
|
289
|
+
// and indexes is an array of numbers
|
|
290
|
+
indexes: data.indexes || (data.index ? [data.index] : []),
|
|
291
|
+
checksum: data.checksum,
|
|
292
|
+
txHash: outPoint.txHash,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
// Update backlinks - add the new one to the existing backlinks array
|
|
296
|
+
const backLinks = [...(data.backLinks || []), newBackLink];
|
|
297
|
+
// Define output data based on version
|
|
298
|
+
let outputData;
|
|
299
|
+
if (version === constants_1.ProtocolVersion.V1) {
|
|
300
|
+
// In V1, index is a single number, not an array
|
|
301
|
+
// The first witness index is used (V1 can only reference one witness)
|
|
302
|
+
outputData = molecule_1.CKBFSData.pack({
|
|
303
|
+
index: witnessIndices[0], // Use only the first index as a number
|
|
304
|
+
checksum: contentChecksum,
|
|
305
|
+
contentType: data.contentType,
|
|
306
|
+
filename: data.filename,
|
|
307
|
+
backLinks,
|
|
308
|
+
}, constants_1.ProtocolVersion.V1); // Explicitly use V1 for packing
|
|
309
|
+
}
|
|
310
|
+
else {
|
|
311
|
+
// In V2, indexes is an array of witness indices
|
|
312
|
+
outputData = molecule_1.CKBFSData.pack({
|
|
313
|
+
indexes: witnessIndices,
|
|
314
|
+
checksum: contentChecksum,
|
|
315
|
+
contentType: data.contentType,
|
|
316
|
+
filename: data.filename,
|
|
317
|
+
backLinks,
|
|
318
|
+
}, constants_1.ProtocolVersion.V2); // Explicitly use V2 for packing
|
|
319
|
+
}
|
|
320
|
+
// Calculate the required capacity for the output cell
|
|
321
|
+
// This accounts for:
|
|
322
|
+
// 1. The output data size
|
|
323
|
+
// 2. The type script's occupied size
|
|
324
|
+
// 3. The lock script's occupied size
|
|
325
|
+
// 4. A constant of 8 bytes (for header overhead)
|
|
326
|
+
const ckbfsCellSize = BigInt(outputData.length + type.occupiedSize + lock.occupiedSize + 8) *
|
|
327
|
+
100000000n;
|
|
328
|
+
// Use the maximum value between calculated size and original capacity
|
|
329
|
+
// to ensure we have enough capacity but don't decrease capacity unnecessarily
|
|
330
|
+
const outputCapacity = ckbfsCellSize > capacity ? ckbfsCellSize : capacity;
|
|
331
|
+
// Create initial transaction with the CKBFS cell input
|
|
332
|
+
let preTx;
|
|
333
|
+
if (from) {
|
|
334
|
+
// If from is not empty, inject/merge the fields
|
|
335
|
+
preTx = core_1.Transaction.from({
|
|
336
|
+
...from,
|
|
337
|
+
inputs: from.inputs.length === 0
|
|
338
|
+
? [
|
|
339
|
+
{
|
|
340
|
+
previousOutput: {
|
|
341
|
+
txHash: outPoint.txHash,
|
|
342
|
+
index: outPoint.index,
|
|
343
|
+
},
|
|
344
|
+
since: "0x0",
|
|
345
|
+
},
|
|
346
|
+
]
|
|
347
|
+
: [
|
|
348
|
+
...from.inputs,
|
|
349
|
+
{
|
|
350
|
+
previousOutput: {
|
|
351
|
+
txHash: outPoint.txHash,
|
|
352
|
+
index: outPoint.index,
|
|
353
|
+
},
|
|
354
|
+
since: "0x0",
|
|
355
|
+
},
|
|
356
|
+
],
|
|
357
|
+
outputs: from.outputs.length === 0
|
|
358
|
+
? [
|
|
359
|
+
{
|
|
360
|
+
lock,
|
|
361
|
+
type,
|
|
362
|
+
capacity: outputCapacity,
|
|
363
|
+
},
|
|
364
|
+
]
|
|
365
|
+
: [
|
|
366
|
+
...from.outputs,
|
|
367
|
+
{
|
|
368
|
+
lock,
|
|
369
|
+
type,
|
|
370
|
+
capacity: outputCapacity,
|
|
371
|
+
},
|
|
372
|
+
],
|
|
373
|
+
outputsData: from.outputsData.length === 0
|
|
374
|
+
? [outputData]
|
|
375
|
+
: [
|
|
376
|
+
...from.outputsData,
|
|
377
|
+
outputData,
|
|
378
|
+
],
|
|
379
|
+
witnesses: from.witnesses.length === 0
|
|
380
|
+
? [
|
|
381
|
+
[], // Empty secp witness for signing if not provided
|
|
382
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
383
|
+
]
|
|
384
|
+
: [
|
|
385
|
+
...from.witnesses,
|
|
386
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
387
|
+
],
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
else {
|
|
391
|
+
preTx = core_1.Transaction.from({
|
|
392
|
+
inputs: [
|
|
393
|
+
{
|
|
394
|
+
previousOutput: {
|
|
395
|
+
txHash: outPoint.txHash,
|
|
396
|
+
index: outPoint.index,
|
|
397
|
+
},
|
|
398
|
+
since: "0x0",
|
|
399
|
+
},
|
|
400
|
+
],
|
|
401
|
+
outputs: [
|
|
402
|
+
{
|
|
403
|
+
lock,
|
|
404
|
+
type,
|
|
405
|
+
capacity: outputCapacity,
|
|
406
|
+
},
|
|
407
|
+
],
|
|
408
|
+
outputsData: [outputData],
|
|
409
|
+
witnesses: [
|
|
410
|
+
[], // Empty secp witness for signing
|
|
411
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
412
|
+
],
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
// Add the CKBFS dep group cell dependency
|
|
416
|
+
preTx.addCellDeps({
|
|
417
|
+
outPoint: {
|
|
418
|
+
txHash: ensureHexPrefix(config.depTxHash),
|
|
419
|
+
index: config.depIndex || 0,
|
|
420
|
+
},
|
|
421
|
+
depType: "depGroup",
|
|
422
|
+
});
|
|
423
|
+
const outputIndex = from ? from.outputs.length : 0;
|
|
424
|
+
return { tx: preTx, outputIndex };
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Creates a transaction for appending content to a CKBFS file
|
|
428
|
+
* @param signer The signer to use for the transaction
|
|
429
|
+
* @param options Options for appending content
|
|
430
|
+
* @returns Promise resolving to the created transaction
|
|
431
|
+
*/
|
|
432
|
+
async function createAppendTransaction(signer, options) {
|
|
433
|
+
const { ckbfsCell, feeRate, } = options;
|
|
434
|
+
const { lock } = ckbfsCell;
|
|
435
|
+
// Use prepareAppendTransaction to create the base transaction
|
|
436
|
+
const { tx: preTx, outputIndex } = await prepareAppendTransaction(options);
|
|
437
|
+
// Get the recommended address to ensure lock script cell deps are included
|
|
438
|
+
const address = await signer.getRecommendedAddressObj();
|
|
439
|
+
const inputsBefore = preTx.inputs.length;
|
|
440
|
+
// If we need more capacity than the original cell had, add additional inputs
|
|
441
|
+
if (preTx.outputs[outputIndex].capacity > ckbfsCell.capacity) {
|
|
442
|
+
console.log(`Need additional capacity: ${preTx.outputs[outputIndex].capacity - ckbfsCell.capacity} shannons`);
|
|
443
|
+
// Add more inputs to cover the increased capacity
|
|
444
|
+
await preTx.completeInputsByCapacity(signer);
|
|
445
|
+
}
|
|
446
|
+
const witnesses = [];
|
|
447
|
+
// add empty witness for signer if ckbfs's lock is the same as signer's lock
|
|
448
|
+
if (address.script.hash() === lock.hash()) {
|
|
449
|
+
witnesses.push("0x");
|
|
450
|
+
}
|
|
451
|
+
// add ckbfs witnesses (skip the first witness which is for signing)
|
|
452
|
+
witnesses.push(...preTx.witnesses.slice(1));
|
|
453
|
+
// Add empty witnesses for additional signer inputs
|
|
454
|
+
// This is to ensure that the transaction is valid and can be signed
|
|
455
|
+
for (let i = inputsBefore; i < preTx.inputs.length; i++) {
|
|
456
|
+
witnesses.push("0x");
|
|
457
|
+
}
|
|
458
|
+
preTx.witnesses = witnesses;
|
|
459
|
+
// Complete fee
|
|
460
|
+
await preTx.completeFeeChangeToLock(signer, address.script, feeRate || 2000);
|
|
461
|
+
return preTx;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Creates a transaction for appending content to a CKBFS file
|
|
465
|
+
* @param signer The signer to use for the transaction
|
|
466
|
+
* @param options Options for appending content
|
|
467
|
+
* @returns Promise resolving to the created transaction
|
|
468
|
+
*/
|
|
469
|
+
async function createAppendTransactionDry(signer, options) {
|
|
470
|
+
const { ckbfsCell, contentChunks, network = constants_1.DEFAULT_NETWORK, version = constants_1.DEFAULT_VERSION, } = options;
|
|
471
|
+
const { outPoint, data, type, lock, capacity } = ckbfsCell;
|
|
472
|
+
// Get CKBFS script config early to use version info
|
|
473
|
+
const config = (0, constants_1.getCKBFSScriptConfig)(network, version);
|
|
474
|
+
// Create CKBFS witnesses - each chunk already includes the CKBFS header
|
|
475
|
+
// Pass 0 as version byte - this is the protocol version byte in the witness header
|
|
476
|
+
// not to be confused with the Protocol Version (V1 vs V2)
|
|
477
|
+
const ckbfsWitnesses = (0, witness_1.createChunkedCKBFSWitnesses)(contentChunks);
|
|
478
|
+
// Combine the new content chunks for checksum calculation
|
|
479
|
+
const combinedContent = Buffer.concat(contentChunks);
|
|
480
|
+
// Update the existing checksum with the new content - this matches Adler32's
|
|
481
|
+
// cumulative nature as required by Rule 11 in the RFC
|
|
482
|
+
const contentChecksum = await (0, checksum_1.updateChecksum)(data.checksum, combinedContent);
|
|
483
|
+
console.log(`Updated checksum from ${data.checksum} to ${contentChecksum} for appended content`);
|
|
484
|
+
// Get the recommended address to ensure lock script cell deps are included
|
|
485
|
+
const address = await signer.getRecommendedAddressObj();
|
|
486
|
+
// Calculate the actual witness indices where our content is placed
|
|
487
|
+
// CKBFS data starts at index 1 if signer's lock script is the same as ckbfs's lock script
|
|
488
|
+
// else CKBFS data starts at index 0
|
|
489
|
+
const contentStartIndex = address.script.hash() === lock.hash() ? 1 : 0;
|
|
490
|
+
const witnessIndices = Array.from({ length: contentChunks.length }, (_, i) => contentStartIndex + i);
|
|
491
|
+
// Create backlink for the current state based on version
|
|
492
|
+
let newBackLink;
|
|
493
|
+
if (version === constants_1.ProtocolVersion.V1) {
|
|
494
|
+
// V1 format: Use index field (single number)
|
|
495
|
+
newBackLink = {
|
|
496
|
+
// In V1, field order is index, checksum, txHash
|
|
497
|
+
// and index is a single number value, not an array
|
|
498
|
+
index: data.index ||
|
|
499
|
+
(data.indexes && data.indexes.length > 0 ? data.indexes[0] : 0),
|
|
500
|
+
checksum: data.checksum,
|
|
501
|
+
txHash: outPoint.txHash,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
// V2 format: Use indexes field (array of numbers)
|
|
506
|
+
newBackLink = {
|
|
507
|
+
// In V2, field order is indexes, checksum, txHash
|
|
508
|
+
// and indexes is an array of numbers
|
|
509
|
+
indexes: data.indexes || (data.index ? [data.index] : []),
|
|
510
|
+
checksum: data.checksum,
|
|
511
|
+
txHash: outPoint.txHash,
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
// Update backlinks - add the new one to the existing backlinks array
|
|
515
|
+
const backLinks = [...(data.backLinks || []), newBackLink];
|
|
516
|
+
// Define output data based on version
|
|
517
|
+
let outputData;
|
|
518
|
+
if (version === constants_1.ProtocolVersion.V1) {
|
|
519
|
+
// In V1, index is a single number, not an array
|
|
520
|
+
// The first witness index is used (V1 can only reference one witness)
|
|
521
|
+
outputData = molecule_1.CKBFSData.pack({
|
|
522
|
+
index: witnessIndices[0], // Use only the first index as a number
|
|
523
|
+
checksum: contentChecksum,
|
|
524
|
+
contentType: data.contentType,
|
|
525
|
+
filename: data.filename,
|
|
526
|
+
backLinks,
|
|
527
|
+
}, constants_1.ProtocolVersion.V1); // Explicitly use V1 for packing
|
|
528
|
+
}
|
|
529
|
+
else {
|
|
530
|
+
// In V2, indexes is an array of witness indices
|
|
531
|
+
outputData = molecule_1.CKBFSData.pack({
|
|
532
|
+
indexes: witnessIndices,
|
|
533
|
+
checksum: contentChecksum,
|
|
534
|
+
contentType: data.contentType,
|
|
535
|
+
filename: data.filename,
|
|
536
|
+
backLinks,
|
|
537
|
+
}, constants_1.ProtocolVersion.V2); // Explicitly use V2 for packing
|
|
538
|
+
}
|
|
539
|
+
// Calculate the required capacity for the output cell
|
|
540
|
+
// This accounts for:
|
|
541
|
+
// 1. The output data size
|
|
542
|
+
// 2. The type script's occupied size
|
|
543
|
+
// 3. The lock script's occupied size
|
|
544
|
+
// 4. A constant of 8 bytes (for header overhead)
|
|
545
|
+
const ckbfsCellSize = BigInt(outputData.length + type.occupiedSize + lock.occupiedSize + 8) *
|
|
546
|
+
100000000n;
|
|
547
|
+
console.log(`Original capacity: ${capacity}, Calculated size: ${ckbfsCellSize}, Data size: ${outputData.length}`);
|
|
548
|
+
// Use the maximum value between calculated size and original capacity
|
|
549
|
+
// to ensure we have enough capacity but don't decrease capacity unnecessarily
|
|
550
|
+
const outputCapacity = ckbfsCellSize > capacity ? ckbfsCellSize : capacity;
|
|
551
|
+
// Create initial transaction with the CKBFS cell input
|
|
552
|
+
const tx = core_1.Transaction.from({
|
|
553
|
+
inputs: [
|
|
554
|
+
{
|
|
555
|
+
previousOutput: {
|
|
556
|
+
txHash: outPoint.txHash,
|
|
557
|
+
index: outPoint.index,
|
|
558
|
+
},
|
|
559
|
+
since: "0x0",
|
|
560
|
+
},
|
|
561
|
+
],
|
|
562
|
+
outputs: [
|
|
563
|
+
{
|
|
564
|
+
lock,
|
|
565
|
+
type,
|
|
566
|
+
capacity: outputCapacity,
|
|
567
|
+
},
|
|
568
|
+
],
|
|
569
|
+
outputsData: [outputData],
|
|
570
|
+
});
|
|
571
|
+
// Add the CKBFS dep group cell dependency
|
|
572
|
+
tx.addCellDeps({
|
|
573
|
+
outPoint: {
|
|
574
|
+
txHash: ensureHexPrefix(config.depTxHash),
|
|
575
|
+
index: config.depIndex || 0,
|
|
576
|
+
},
|
|
577
|
+
depType: "depGroup",
|
|
578
|
+
});
|
|
579
|
+
const inputsBefore = tx.inputs.length;
|
|
580
|
+
// // If we need more capacity than the original cell had, add additional inputs
|
|
581
|
+
// if (outputCapacity > capacity) {
|
|
582
|
+
// console.log(
|
|
583
|
+
// `Need additional capacity: ${outputCapacity - capacity} shannons`,
|
|
584
|
+
// );
|
|
585
|
+
// // Add more inputs to cover the increased capacity
|
|
586
|
+
// await tx.completeInputsByCapacity(signer);
|
|
587
|
+
// }
|
|
588
|
+
const witnesses = [];
|
|
589
|
+
// add empty witness for signer if ckbfs's lock is the same as signer's lock
|
|
590
|
+
if (address.script.hash() === lock.hash()) {
|
|
591
|
+
witnesses.push("0x");
|
|
592
|
+
}
|
|
593
|
+
// add ckbfs witnesses
|
|
594
|
+
witnesses.push(...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`));
|
|
595
|
+
// Add empty witnesses for signer's input
|
|
596
|
+
// This is to ensure that the transaction is valid and can be signed
|
|
597
|
+
for (let i = inputsBefore; i < tx.inputs.length; i++) {
|
|
598
|
+
witnesses.push("0x");
|
|
599
|
+
}
|
|
600
|
+
tx.witnesses = witnesses;
|
|
601
|
+
// Complete fee
|
|
602
|
+
//await tx.completeFeeChangeToLock(signer, address.script, feeRate || 2000);
|
|
603
|
+
return tx;
|
|
604
|
+
}
|
|
605
|
+
/**
|
|
606
|
+
* Creates a complete transaction for publishing a file to CKBFS
|
|
607
|
+
* @param signer The signer to use for the transaction
|
|
608
|
+
* @param options Options for publishing the file
|
|
609
|
+
* @returns Promise resolving to the signed transaction
|
|
610
|
+
*/
|
|
611
|
+
async function publishCKBFS(signer, options) {
|
|
612
|
+
const tx = await createPublishTransaction(signer, options);
|
|
613
|
+
return signer.signTransaction(tx);
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Creates a complete transaction for appending content to a CKBFS file
|
|
617
|
+
* @param signer The signer to use for the transaction
|
|
618
|
+
* @param options Options for appending content
|
|
619
|
+
* @returns Promise resolving to the signed transaction
|
|
620
|
+
*/
|
|
621
|
+
async function appendCKBFS(signer, options) {
|
|
622
|
+
const tx = await createAppendTransaction(signer, options);
|
|
623
|
+
return signer.signTransaction(tx);
|
|
624
|
+
}
|