@ckbfs/api 2.0.1 → 2.0.3
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/dist/utils/transactions/v3.d.ts +16 -0
- package/dist/utils/transactions/v3.js +183 -12
- package/examples/retrieve-v3.ts +10 -5
- package/package.json +1 -1
- package/src/index.ts +5 -0
- package/src/utils/file.ts +1 -1
- package/src/utils/transactions/v3.ts +229 -13
|
@@ -87,6 +87,15 @@ export declare function preparePublishV3Transaction(options: PublishV3Options):
|
|
|
87
87
|
* @returns Promise resolving to the created transaction
|
|
88
88
|
*/
|
|
89
89
|
export declare function createPublishV3Transaction(signer: Signer, options: PublishV3Options): Promise<Transaction>;
|
|
90
|
+
/**
|
|
91
|
+
* Prepares a transaction for appending content to a CKBFS v3 file without fee and change handling
|
|
92
|
+
* @param options Options for appending content
|
|
93
|
+
* @returns Promise resolving to the prepared transaction and the output index of CKBFS Cell
|
|
94
|
+
*/
|
|
95
|
+
export declare function prepareAppendV3Transaction(options: AppendV3Options): Promise<{
|
|
96
|
+
tx: Transaction;
|
|
97
|
+
outputIndex: number;
|
|
98
|
+
}>;
|
|
90
99
|
/**
|
|
91
100
|
* Creates a transaction for appending content to a CKBFS v3 file
|
|
92
101
|
* @param signer The signer to use for the transaction
|
|
@@ -94,6 +103,13 @@ export declare function createPublishV3Transaction(signer: Signer, options: Publ
|
|
|
94
103
|
* @returns Promise resolving to the created transaction
|
|
95
104
|
*/
|
|
96
105
|
export declare function createAppendV3Transaction(signer: Signer, options: AppendV3Options): Promise<Transaction>;
|
|
106
|
+
/**
|
|
107
|
+
* Creates a transaction for appending content to a CKBFS v3 file (dry run without fee completion)
|
|
108
|
+
* @param signer The signer to use for the transaction
|
|
109
|
+
* @param options Options for appending content
|
|
110
|
+
* @returns Promise resolving to the created transaction
|
|
111
|
+
*/
|
|
112
|
+
export declare function createAppendV3TransactionDry(signer: Signer, options: AppendV3Options): Promise<Transaction>;
|
|
97
113
|
/**
|
|
98
114
|
* Creates a transaction for transferring ownership of a CKBFS v3 file
|
|
99
115
|
* @param signer The signer to use for the transaction
|
|
@@ -3,7 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.createCKBFSV3Cell = createCKBFSV3Cell;
|
|
4
4
|
exports.preparePublishV3Transaction = preparePublishV3Transaction;
|
|
5
5
|
exports.createPublishV3Transaction = createPublishV3Transaction;
|
|
6
|
+
exports.prepareAppendV3Transaction = prepareAppendV3Transaction;
|
|
6
7
|
exports.createAppendV3Transaction = createAppendV3Transaction;
|
|
8
|
+
exports.createAppendV3TransactionDry = createAppendV3TransactionDry;
|
|
7
9
|
exports.createTransferV3Transaction = createTransferV3Transaction;
|
|
8
10
|
exports.publishCKBFSV3 = publishCKBFSV3;
|
|
9
11
|
exports.appendCKBFSV3 = appendCKBFSV3;
|
|
@@ -209,6 +211,125 @@ async function createPublishV3Transaction(signer, options) {
|
|
|
209
211
|
}
|
|
210
212
|
return preTx;
|
|
211
213
|
}
|
|
214
|
+
/**
|
|
215
|
+
* Prepares a transaction for appending content to a CKBFS v3 file without fee and change handling
|
|
216
|
+
* @param options Options for appending content
|
|
217
|
+
* @returns Promise resolving to the prepared transaction and the output index of CKBFS Cell
|
|
218
|
+
*/
|
|
219
|
+
async function prepareAppendV3Transaction(options) {
|
|
220
|
+
const { from, ckbfsCell, contentChunks, network = constants_1.DEFAULT_NETWORK, previousTxHash, previousWitnessIndex, previousChecksum, } = options;
|
|
221
|
+
// Calculate new checksum by updating from previous checksum
|
|
222
|
+
const combinedContent = Buffer.concat(contentChunks);
|
|
223
|
+
const newChecksum = await (0, checksum_1.updateChecksum)(previousChecksum, combinedContent);
|
|
224
|
+
// Calculate the actual witness indices where our content is placed
|
|
225
|
+
const contentStartIndex = from?.witnesses.length || 1;
|
|
226
|
+
// Create CKBFS v3 witnesses with backlink info
|
|
227
|
+
const ckbfsWitnesses = (0, witness_1.createChunkedCKBFSV3Witnesses)(contentChunks, {
|
|
228
|
+
previousTxHash,
|
|
229
|
+
previousWitnessIndex,
|
|
230
|
+
previousChecksum,
|
|
231
|
+
startIndex: contentStartIndex,
|
|
232
|
+
});
|
|
233
|
+
// Create updated CKBFS v3 cell output data
|
|
234
|
+
const outputData = molecule_1.CKBFSData.pack({
|
|
235
|
+
index: contentStartIndex,
|
|
236
|
+
checksum: newChecksum,
|
|
237
|
+
contentType: ckbfsCell.data.contentType,
|
|
238
|
+
filename: ckbfsCell.data.filename,
|
|
239
|
+
}, constants_1.ProtocolVersion.V3);
|
|
240
|
+
// Get CKBFS script config
|
|
241
|
+
const config = (0, constants_1.getCKBFSScriptConfig)(network, constants_1.ProtocolVersion.V3, false);
|
|
242
|
+
// Calculate the required capacity for the output cell
|
|
243
|
+
const ckbfsCellSize = BigInt(outputData.length + ckbfsCell.type.occupiedSize + ckbfsCell.lock.occupiedSize + 8) *
|
|
244
|
+
100000000n;
|
|
245
|
+
// Use the maximum value between calculated size and original capacity
|
|
246
|
+
const outputCapacity = ckbfsCellSize > ckbfsCell.capacity ? ckbfsCellSize : ckbfsCell.capacity;
|
|
247
|
+
// Create initial transaction with the CKBFS cell input
|
|
248
|
+
let preTx;
|
|
249
|
+
if (from) {
|
|
250
|
+
// If from is not empty, inject/merge the fields
|
|
251
|
+
preTx = core_1.Transaction.from({
|
|
252
|
+
...from,
|
|
253
|
+
inputs: from.inputs.length === 0
|
|
254
|
+
? [
|
|
255
|
+
{
|
|
256
|
+
previousOutput: ckbfsCell.outPoint,
|
|
257
|
+
since: "0x0",
|
|
258
|
+
},
|
|
259
|
+
]
|
|
260
|
+
: [
|
|
261
|
+
...from.inputs,
|
|
262
|
+
{
|
|
263
|
+
previousOutput: ckbfsCell.outPoint,
|
|
264
|
+
since: "0x0",
|
|
265
|
+
},
|
|
266
|
+
],
|
|
267
|
+
outputs: from.outputs.length === 0
|
|
268
|
+
? [
|
|
269
|
+
{
|
|
270
|
+
lock: ckbfsCell.lock,
|
|
271
|
+
type: ckbfsCell.type,
|
|
272
|
+
capacity: outputCapacity,
|
|
273
|
+
},
|
|
274
|
+
]
|
|
275
|
+
: [
|
|
276
|
+
...from.outputs,
|
|
277
|
+
{
|
|
278
|
+
lock: ckbfsCell.lock,
|
|
279
|
+
type: ckbfsCell.type,
|
|
280
|
+
capacity: outputCapacity,
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
outputsData: from.outputsData.length === 0
|
|
284
|
+
? [outputData]
|
|
285
|
+
: [
|
|
286
|
+
...from.outputsData,
|
|
287
|
+
outputData,
|
|
288
|
+
],
|
|
289
|
+
witnesses: from.witnesses.length === 0
|
|
290
|
+
? [
|
|
291
|
+
[], // Empty secp witness for signing if not provided
|
|
292
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
293
|
+
]
|
|
294
|
+
: [
|
|
295
|
+
...from.witnesses,
|
|
296
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
297
|
+
],
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
preTx = core_1.Transaction.from({
|
|
302
|
+
inputs: [
|
|
303
|
+
{
|
|
304
|
+
previousOutput: ckbfsCell.outPoint,
|
|
305
|
+
since: "0x0",
|
|
306
|
+
},
|
|
307
|
+
],
|
|
308
|
+
outputs: [
|
|
309
|
+
{
|
|
310
|
+
lock: ckbfsCell.lock,
|
|
311
|
+
type: ckbfsCell.type,
|
|
312
|
+
capacity: outputCapacity,
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
witnesses: [
|
|
316
|
+
[], // Empty secp witness for signing
|
|
317
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
318
|
+
],
|
|
319
|
+
outputsData: [outputData],
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
// Add the CKBFS dep group cell dependency
|
|
323
|
+
preTx.addCellDeps({
|
|
324
|
+
outPoint: {
|
|
325
|
+
txHash: (0, shared_1.ensureHexPrefix)(config.depTxHash),
|
|
326
|
+
index: config.depIndex || 0,
|
|
327
|
+
},
|
|
328
|
+
depType: "depGroup",
|
|
329
|
+
});
|
|
330
|
+
const outputIndex = from ? from.outputs.length : 0;
|
|
331
|
+
return { tx: preTx, outputIndex };
|
|
332
|
+
}
|
|
212
333
|
/**
|
|
213
334
|
* Creates a transaction for appending content to a CKBFS v3 file
|
|
214
335
|
* @param signer The signer to use for the transaction
|
|
@@ -216,18 +337,61 @@ async function createPublishV3Transaction(signer, options) {
|
|
|
216
337
|
* @returns Promise resolving to the created transaction
|
|
217
338
|
*/
|
|
218
339
|
async function createAppendV3Transaction(signer, options) {
|
|
219
|
-
const { ckbfsCell,
|
|
340
|
+
const { ckbfsCell, feeRate, } = options;
|
|
341
|
+
const { lock } = ckbfsCell;
|
|
342
|
+
// Use prepareAppendV3Transaction to create the base transaction
|
|
343
|
+
const { tx: preTx, outputIndex } = await prepareAppendV3Transaction(options);
|
|
344
|
+
// Get the recommended address to ensure lock script cell deps are included
|
|
345
|
+
const address = await signer.getRecommendedAddressObj();
|
|
346
|
+
const inputsBefore = preTx.inputs.length;
|
|
347
|
+
// If we need more capacity than the original cell had, add additional inputs
|
|
348
|
+
if (preTx.outputs[outputIndex].capacity > ckbfsCell.capacity) {
|
|
349
|
+
console.log(`Need additional capacity: ${preTx.outputs[outputIndex].capacity - ckbfsCell.capacity} shannons`);
|
|
350
|
+
// Add more inputs to cover the increased capacity
|
|
351
|
+
await preTx.completeInputsByCapacity(signer);
|
|
352
|
+
}
|
|
353
|
+
const witnesses = [];
|
|
354
|
+
// add empty witness for signer if ckbfs's lock is the same as signer's lock
|
|
355
|
+
if (address.script.hash() === lock.hash()) {
|
|
356
|
+
witnesses.push("0x");
|
|
357
|
+
}
|
|
358
|
+
// add ckbfs witnesses (skip the first witness which is for signing)
|
|
359
|
+
witnesses.push(...preTx.witnesses.slice(1));
|
|
360
|
+
// Add empty witnesses for additional signer inputs
|
|
361
|
+
// This is to ensure that the transaction is valid and can be signed
|
|
362
|
+
for (let i = inputsBefore; i < preTx.inputs.length; i++) {
|
|
363
|
+
witnesses.push("0x");
|
|
364
|
+
}
|
|
365
|
+
preTx.witnesses = witnesses;
|
|
366
|
+
// Complete fee
|
|
367
|
+
await preTx.completeFeeChangeToLock(signer, address.script, feeRate || 2000);
|
|
368
|
+
return preTx;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Creates a transaction for appending content to a CKBFS v3 file (dry run without fee completion)
|
|
372
|
+
* @param signer The signer to use for the transaction
|
|
373
|
+
* @param options Options for appending content
|
|
374
|
+
* @returns Promise resolving to the created transaction
|
|
375
|
+
*/
|
|
376
|
+
async function createAppendV3TransactionDry(signer, options) {
|
|
377
|
+
const { ckbfsCell, contentChunks, network = constants_1.DEFAULT_NETWORK, previousTxHash, previousWitnessIndex, previousChecksum, } = options;
|
|
378
|
+
const { lock } = ckbfsCell;
|
|
220
379
|
// Calculate new checksum by updating from previous checksum
|
|
221
380
|
const combinedContent = Buffer.concat(contentChunks);
|
|
222
381
|
const newChecksum = await (0, checksum_1.updateChecksum)(previousChecksum, combinedContent);
|
|
382
|
+
// Get the recommended address to ensure lock script cell deps are included
|
|
383
|
+
const address = await signer.getRecommendedAddressObj();
|
|
384
|
+
// Calculate the actual witness indices where our content is placed
|
|
385
|
+
// CKBFS data starts at index 1 if signer's lock script is the same as ckbfs's lock script
|
|
386
|
+
// else CKBFS data starts at index 0
|
|
387
|
+
const contentStartIndex = address.script.hash() === lock.hash() ? 1 : 0;
|
|
223
388
|
// Create CKBFS v3 witnesses with backlink info
|
|
224
389
|
const ckbfsWitnesses = (0, witness_1.createChunkedCKBFSV3Witnesses)(contentChunks, {
|
|
225
390
|
previousTxHash,
|
|
226
391
|
previousWitnessIndex,
|
|
227
392
|
previousChecksum,
|
|
393
|
+
startIndex: contentStartIndex,
|
|
228
394
|
});
|
|
229
|
-
// V3 format uses single index (first witness containing content)
|
|
230
|
-
const contentStartIndex = 1; // First witness is for signer, content starts at index 1
|
|
231
395
|
// Create updated CKBFS v3 cell output data
|
|
232
396
|
const outputData = molecule_1.CKBFSData.pack({
|
|
233
397
|
index: contentStartIndex,
|
|
@@ -240,9 +404,10 @@ async function createAppendV3Transaction(signer, options) {
|
|
|
240
404
|
// Calculate the required capacity for the output cell
|
|
241
405
|
const ckbfsCellSize = BigInt(outputData.length + ckbfsCell.type.occupiedSize + ckbfsCell.lock.occupiedSize + 8) *
|
|
242
406
|
100000000n;
|
|
407
|
+
console.log(`Original capacity: ${ckbfsCell.capacity}, Calculated size: ${ckbfsCellSize}, Data size: ${outputData.length}`);
|
|
243
408
|
// Use the maximum value between calculated size and original capacity
|
|
244
409
|
const outputCapacity = ckbfsCellSize > ckbfsCell.capacity ? ckbfsCellSize : ckbfsCell.capacity;
|
|
245
|
-
// Create transaction
|
|
410
|
+
// Create initial transaction with the CKBFS cell input
|
|
246
411
|
const tx = core_1.Transaction.from({
|
|
247
412
|
inputs: [
|
|
248
413
|
{
|
|
@@ -257,10 +422,6 @@ async function createAppendV3Transaction(signer, options) {
|
|
|
257
422
|
capacity: outputCapacity,
|
|
258
423
|
},
|
|
259
424
|
],
|
|
260
|
-
witnesses: [
|
|
261
|
-
[], // Empty secp witness for signing
|
|
262
|
-
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
263
|
-
],
|
|
264
425
|
outputsData: [outputData],
|
|
265
426
|
});
|
|
266
427
|
// Add the CKBFS dep group cell dependency
|
|
@@ -271,10 +432,20 @@ async function createAppendV3Transaction(signer, options) {
|
|
|
271
432
|
},
|
|
272
433
|
depType: "depGroup",
|
|
273
434
|
});
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
//
|
|
277
|
-
|
|
435
|
+
const inputsBefore = tx.inputs.length;
|
|
436
|
+
const witnesses = [];
|
|
437
|
+
// add empty witness for signer if ckbfs's lock is the same as signer's lock
|
|
438
|
+
if (address.script.hash() === lock.hash()) {
|
|
439
|
+
witnesses.push("0x");
|
|
440
|
+
}
|
|
441
|
+
// add ckbfs witnesses
|
|
442
|
+
witnesses.push(...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`));
|
|
443
|
+
// Add empty witnesses for signer's input
|
|
444
|
+
// This is to ensure that the transaction is valid and can be signed
|
|
445
|
+
for (let i = inputsBefore; i < tx.inputs.length; i++) {
|
|
446
|
+
witnesses.push("0x");
|
|
447
|
+
}
|
|
448
|
+
tx.witnesses = witnesses;
|
|
278
449
|
return tx;
|
|
279
450
|
}
|
|
280
451
|
/**
|
package/examples/retrieve-v3.ts
CHANGED
|
@@ -2,6 +2,10 @@ import {
|
|
|
2
2
|
getFileContentFromChainByIdentifierV3,
|
|
3
3
|
saveFileFromChainByIdentifierV3,
|
|
4
4
|
ProtocolVersion,
|
|
5
|
+
CKBFSData,
|
|
6
|
+
getCKBFSScriptConfig,
|
|
7
|
+
NetworkType,
|
|
8
|
+
resolveCKBFSCell,
|
|
5
9
|
} from '../src/index';
|
|
6
10
|
import { ClientPublicTestnet } from '@ckb-ccc/core';
|
|
7
11
|
|
|
@@ -14,7 +18,7 @@ const client = new ClientPublicTestnet(); // Use testnet
|
|
|
14
18
|
async function retrieveFileByTypeIdV3Example() {
|
|
15
19
|
try {
|
|
16
20
|
// Example TypeID (replace with actual TypeID from a v3 published file)
|
|
17
|
-
const typeId = "
|
|
21
|
+
const typeId = "0x32d5f0b09f85a64143e91847b6167d8e952ea3026db92068cada45f4478dd814";
|
|
18
22
|
|
|
19
23
|
console.log(`Retrieving CKBFS v3 file by TypeID: ${typeId}`);
|
|
20
24
|
|
|
@@ -106,7 +110,7 @@ async function retrieveAndSaveFileV3Example() {
|
|
|
106
110
|
async function retrieveFileByURIV3Example() {
|
|
107
111
|
try {
|
|
108
112
|
// Example CKBFS URI (replace with actual URI)
|
|
109
|
-
const ckbfsUri = "ckbfs://
|
|
113
|
+
const ckbfsUri = "ckbfs://d52d8536d3498bd68fa1e235f7e090c2d047154e22fcea5cfa54ccb58c236eb3";
|
|
110
114
|
|
|
111
115
|
console.log(`Retrieving CKBFS v3 file by URI: ${ckbfsUri}`);
|
|
112
116
|
|
|
@@ -145,7 +149,7 @@ async function retrieveFileByURIV3Example() {
|
|
|
145
149
|
async function retrieveFileByOutPointV3Example() {
|
|
146
150
|
try {
|
|
147
151
|
// Example OutPoint URI (replace with actual transaction hash and index)
|
|
148
|
-
const outPointUri = "ckbfs://
|
|
152
|
+
const outPointUri = "ckbfs://1cc22be7489f64bda7cbc3a9227a94640394dc99f98181b1c3b1243d24897972i0";
|
|
149
153
|
|
|
150
154
|
console.log(`Retrieving CKBFS v3 file by OutPoint: ${outPointUri}`);
|
|
151
155
|
|
|
@@ -169,6 +173,7 @@ async function retrieveFileByOutPointV3Example() {
|
|
|
169
173
|
console.log(`Content type: ${fileData.contentType}`);
|
|
170
174
|
console.log(`Size: ${fileData.size} bytes`);
|
|
171
175
|
console.log(`Checksum: ${fileData.checksum}`);
|
|
176
|
+
console.log(`Content: ${fileData.content}`);
|
|
172
177
|
|
|
173
178
|
return fileData;
|
|
174
179
|
} catch (error) {
|
|
@@ -202,10 +207,10 @@ async function main() {
|
|
|
202
207
|
console.log('');
|
|
203
208
|
|
|
204
209
|
// Uncomment to test different retrieval methods:
|
|
205
|
-
await retrieveFileByTypeIdV3Example();
|
|
210
|
+
//await retrieveFileByTypeIdV3Example();
|
|
206
211
|
// await retrieveAndSaveFileV3Example();
|
|
207
212
|
await retrieveFileByURIV3Example();
|
|
208
|
-
//
|
|
213
|
+
//await retrieveFileByOutPointV3Example();
|
|
209
214
|
|
|
210
215
|
console.log('Retrieval example structure completed successfully!');
|
|
211
216
|
console.log('Update the identifiers and uncomment methods to test.');
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -34,7 +34,10 @@ import {
|
|
|
34
34
|
appendCKBFSV3,
|
|
35
35
|
transferCKBFSV3,
|
|
36
36
|
createPublishV3Transaction,
|
|
37
|
+
prepareAppendV3Transaction,
|
|
38
|
+
preparePublishV3Transaction,
|
|
37
39
|
createAppendV3Transaction,
|
|
40
|
+
createAppendV3TransactionDry,
|
|
38
41
|
createTransferV3Transaction,
|
|
39
42
|
} from "./utils/transaction";
|
|
40
43
|
import {
|
|
@@ -64,6 +67,7 @@ import {
|
|
|
64
67
|
getFileContentFromChainV3,
|
|
65
68
|
getFileContentFromChainByIdentifierV3,
|
|
66
69
|
saveFileFromChainByIdentifierV3,
|
|
70
|
+
resolveCKBFSCell
|
|
67
71
|
} from "./utils/file";
|
|
68
72
|
import {
|
|
69
73
|
createCKBFSWitness,
|
|
@@ -1046,6 +1050,7 @@ export {
|
|
|
1046
1050
|
createChunkedCKBFSV3Witnesses,
|
|
1047
1051
|
extractCKBFSV3WitnessContent,
|
|
1048
1052
|
isCKBFSV3Witness,
|
|
1053
|
+
resolveCKBFSCell,
|
|
1049
1054
|
CKBFSV3WitnessOptions,
|
|
1050
1055
|
|
|
1051
1056
|
// Molecule definitions
|
package/src/utils/file.ts
CHANGED
|
@@ -510,7 +510,7 @@ export function parseIdentifier(identifier: string): ParsedIdentifier {
|
|
|
510
510
|
* @param options Optional configuration for network, version, and useTypeID
|
|
511
511
|
* @returns Promise resolving to the found cell and transaction info, or null if not found
|
|
512
512
|
*/
|
|
513
|
-
async function resolveCKBFSCell(
|
|
513
|
+
export async function resolveCKBFSCell(
|
|
514
514
|
client: any,
|
|
515
515
|
identifier: string,
|
|
516
516
|
options: {
|
|
@@ -341,6 +341,150 @@ export async function createPublishV3Transaction(
|
|
|
341
341
|
return preTx;
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Prepares a transaction for appending content to a CKBFS v3 file without fee and change handling
|
|
346
|
+
* @param options Options for appending content
|
|
347
|
+
* @returns Promise resolving to the prepared transaction and the output index of CKBFS Cell
|
|
348
|
+
*/
|
|
349
|
+
export async function prepareAppendV3Transaction(
|
|
350
|
+
options: AppendV3Options,
|
|
351
|
+
): Promise<{tx: Transaction, outputIndex: number}> {
|
|
352
|
+
const {
|
|
353
|
+
from,
|
|
354
|
+
ckbfsCell,
|
|
355
|
+
contentChunks,
|
|
356
|
+
network = DEFAULT_NETWORK,
|
|
357
|
+
previousTxHash,
|
|
358
|
+
previousWitnessIndex,
|
|
359
|
+
previousChecksum,
|
|
360
|
+
} = options;
|
|
361
|
+
|
|
362
|
+
// Calculate new checksum by updating from previous checksum
|
|
363
|
+
const combinedContent = Buffer.concat(contentChunks);
|
|
364
|
+
const newChecksum = await updateChecksum(previousChecksum, combinedContent);
|
|
365
|
+
|
|
366
|
+
// Calculate the actual witness indices where our content is placed
|
|
367
|
+
const contentStartIndex = from?.witnesses.length || 1;
|
|
368
|
+
|
|
369
|
+
// Create CKBFS v3 witnesses with backlink info
|
|
370
|
+
const ckbfsWitnesses = createChunkedCKBFSV3Witnesses(contentChunks, {
|
|
371
|
+
previousTxHash,
|
|
372
|
+
previousWitnessIndex,
|
|
373
|
+
previousChecksum,
|
|
374
|
+
startIndex: contentStartIndex,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// Create updated CKBFS v3 cell output data
|
|
378
|
+
const outputData = CKBFSData.pack(
|
|
379
|
+
{
|
|
380
|
+
index: contentStartIndex,
|
|
381
|
+
checksum: newChecksum,
|
|
382
|
+
contentType: ckbfsCell.data.contentType,
|
|
383
|
+
filename: ckbfsCell.data.filename,
|
|
384
|
+
},
|
|
385
|
+
ProtocolVersion.V3,
|
|
386
|
+
);
|
|
387
|
+
|
|
388
|
+
// Get CKBFS script config
|
|
389
|
+
const config = getCKBFSScriptConfig(network, ProtocolVersion.V3, false);
|
|
390
|
+
|
|
391
|
+
// Calculate the required capacity for the output cell
|
|
392
|
+
const ckbfsCellSize =
|
|
393
|
+
BigInt(outputData.length + ckbfsCell.type.occupiedSize + ckbfsCell.lock.occupiedSize + 8) *
|
|
394
|
+
100000000n;
|
|
395
|
+
|
|
396
|
+
// Use the maximum value between calculated size and original capacity
|
|
397
|
+
const outputCapacity = ckbfsCellSize > ckbfsCell.capacity ? ckbfsCellSize : ckbfsCell.capacity;
|
|
398
|
+
|
|
399
|
+
// Create initial transaction with the CKBFS cell input
|
|
400
|
+
let preTx: Transaction;
|
|
401
|
+
if (from) {
|
|
402
|
+
// If from is not empty, inject/merge the fields
|
|
403
|
+
preTx = Transaction.from({
|
|
404
|
+
...from,
|
|
405
|
+
inputs: from.inputs.length === 0
|
|
406
|
+
? [
|
|
407
|
+
{
|
|
408
|
+
previousOutput: ckbfsCell.outPoint,
|
|
409
|
+
since: "0x0",
|
|
410
|
+
},
|
|
411
|
+
]
|
|
412
|
+
: [
|
|
413
|
+
...from.inputs,
|
|
414
|
+
{
|
|
415
|
+
previousOutput: ckbfsCell.outPoint,
|
|
416
|
+
since: "0x0",
|
|
417
|
+
},
|
|
418
|
+
],
|
|
419
|
+
outputs: from.outputs.length === 0
|
|
420
|
+
? [
|
|
421
|
+
{
|
|
422
|
+
lock: ckbfsCell.lock,
|
|
423
|
+
type: ckbfsCell.type,
|
|
424
|
+
capacity: outputCapacity,
|
|
425
|
+
},
|
|
426
|
+
]
|
|
427
|
+
: [
|
|
428
|
+
...from.outputs,
|
|
429
|
+
{
|
|
430
|
+
lock: ckbfsCell.lock,
|
|
431
|
+
type: ckbfsCell.type,
|
|
432
|
+
capacity: outputCapacity,
|
|
433
|
+
},
|
|
434
|
+
],
|
|
435
|
+
outputsData: from.outputsData.length === 0
|
|
436
|
+
? [outputData]
|
|
437
|
+
: [
|
|
438
|
+
...from.outputsData,
|
|
439
|
+
outputData,
|
|
440
|
+
],
|
|
441
|
+
witnesses: from.witnesses.length === 0
|
|
442
|
+
? [
|
|
443
|
+
[], // Empty secp witness for signing if not provided
|
|
444
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
445
|
+
]
|
|
446
|
+
: [
|
|
447
|
+
...from.witnesses,
|
|
448
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
449
|
+
],
|
|
450
|
+
});
|
|
451
|
+
} else {
|
|
452
|
+
preTx = Transaction.from({
|
|
453
|
+
inputs: [
|
|
454
|
+
{
|
|
455
|
+
previousOutput: ckbfsCell.outPoint,
|
|
456
|
+
since: "0x0",
|
|
457
|
+
},
|
|
458
|
+
],
|
|
459
|
+
outputs: [
|
|
460
|
+
{
|
|
461
|
+
lock: ckbfsCell.lock,
|
|
462
|
+
type: ckbfsCell.type,
|
|
463
|
+
capacity: outputCapacity,
|
|
464
|
+
},
|
|
465
|
+
],
|
|
466
|
+
witnesses: [
|
|
467
|
+
[], // Empty secp witness for signing
|
|
468
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
469
|
+
],
|
|
470
|
+
outputsData: [outputData],
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Add the CKBFS dep group cell dependency
|
|
475
|
+
preTx.addCellDeps({
|
|
476
|
+
outPoint: {
|
|
477
|
+
txHash: ensureHexPrefix(config.depTxHash),
|
|
478
|
+
index: config.depIndex || 0,
|
|
479
|
+
},
|
|
480
|
+
depType: "depGroup",
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
const outputIndex = from ? from.outputs.length : 0;
|
|
484
|
+
|
|
485
|
+
return {tx: preTx, outputIndex};
|
|
486
|
+
}
|
|
487
|
+
|
|
344
488
|
/**
|
|
345
489
|
* Creates a transaction for appending content to a CKBFS v3 file
|
|
346
490
|
* @param signer The signer to use for the transaction
|
|
@@ -353,27 +497,86 @@ export async function createAppendV3Transaction(
|
|
|
353
497
|
): Promise<Transaction> {
|
|
354
498
|
const {
|
|
355
499
|
ckbfsCell,
|
|
356
|
-
contentChunks,
|
|
357
500
|
feeRate,
|
|
501
|
+
} = options;
|
|
502
|
+
const { lock } = ckbfsCell;
|
|
503
|
+
|
|
504
|
+
// Use prepareAppendV3Transaction to create the base transaction
|
|
505
|
+
const { tx: preTx, outputIndex } = await prepareAppendV3Transaction(options);
|
|
506
|
+
|
|
507
|
+
// Get the recommended address to ensure lock script cell deps are included
|
|
508
|
+
const address = await signer.getRecommendedAddressObj();
|
|
509
|
+
|
|
510
|
+
const inputsBefore = preTx.inputs.length;
|
|
511
|
+
// If we need more capacity than the original cell had, add additional inputs
|
|
512
|
+
if (preTx.outputs[outputIndex].capacity > ckbfsCell.capacity) {
|
|
513
|
+
console.log(
|
|
514
|
+
`Need additional capacity: ${preTx.outputs[outputIndex].capacity - ckbfsCell.capacity} shannons`,
|
|
515
|
+
);
|
|
516
|
+
// Add more inputs to cover the increased capacity
|
|
517
|
+
await preTx.completeInputsByCapacity(signer);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
const witnesses: any = [];
|
|
521
|
+
// add empty witness for signer if ckbfs's lock is the same as signer's lock
|
|
522
|
+
if (address.script.hash() === lock.hash()) {
|
|
523
|
+
witnesses.push("0x");
|
|
524
|
+
}
|
|
525
|
+
// add ckbfs witnesses (skip the first witness which is for signing)
|
|
526
|
+
witnesses.push(...preTx.witnesses.slice(1));
|
|
527
|
+
|
|
528
|
+
// Add empty witnesses for additional signer inputs
|
|
529
|
+
// This is to ensure that the transaction is valid and can be signed
|
|
530
|
+
for (let i = inputsBefore; i < preTx.inputs.length; i++) {
|
|
531
|
+
witnesses.push("0x");
|
|
532
|
+
}
|
|
533
|
+
preTx.witnesses = witnesses;
|
|
534
|
+
|
|
535
|
+
// Complete fee
|
|
536
|
+
await preTx.completeFeeChangeToLock(signer, address.script, feeRate || 2000);
|
|
537
|
+
|
|
538
|
+
return preTx;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
/**
|
|
542
|
+
* Creates a transaction for appending content to a CKBFS v3 file (dry run without fee completion)
|
|
543
|
+
* @param signer The signer to use for the transaction
|
|
544
|
+
* @param options Options for appending content
|
|
545
|
+
* @returns Promise resolving to the created transaction
|
|
546
|
+
*/
|
|
547
|
+
export async function createAppendV3TransactionDry(
|
|
548
|
+
signer: Signer,
|
|
549
|
+
options: AppendV3Options,
|
|
550
|
+
): Promise<Transaction> {
|
|
551
|
+
const {
|
|
552
|
+
ckbfsCell,
|
|
553
|
+
contentChunks,
|
|
358
554
|
network = DEFAULT_NETWORK,
|
|
359
555
|
previousTxHash,
|
|
360
556
|
previousWitnessIndex,
|
|
361
557
|
previousChecksum,
|
|
362
558
|
} = options;
|
|
559
|
+
const { lock } = ckbfsCell;
|
|
363
560
|
|
|
364
561
|
// Calculate new checksum by updating from previous checksum
|
|
365
562
|
const combinedContent = Buffer.concat(contentChunks);
|
|
366
563
|
const newChecksum = await updateChecksum(previousChecksum, combinedContent);
|
|
367
564
|
|
|
565
|
+
// Get the recommended address to ensure lock script cell deps are included
|
|
566
|
+
const address = await signer.getRecommendedAddressObj();
|
|
567
|
+
|
|
568
|
+
// Calculate the actual witness indices where our content is placed
|
|
569
|
+
// CKBFS data starts at index 1 if signer's lock script is the same as ckbfs's lock script
|
|
570
|
+
// else CKBFS data starts at index 0
|
|
571
|
+
const contentStartIndex = address.script.hash() === lock.hash() ? 1 : 0;
|
|
572
|
+
|
|
368
573
|
// Create CKBFS v3 witnesses with backlink info
|
|
369
574
|
const ckbfsWitnesses = createChunkedCKBFSV3Witnesses(contentChunks, {
|
|
370
575
|
previousTxHash,
|
|
371
576
|
previousWitnessIndex,
|
|
372
577
|
previousChecksum,
|
|
578
|
+
startIndex: contentStartIndex,
|
|
373
579
|
});
|
|
374
|
-
|
|
375
|
-
// V3 format uses single index (first witness containing content)
|
|
376
|
-
const contentStartIndex = 1; // First witness is for signer, content starts at index 1
|
|
377
580
|
|
|
378
581
|
// Create updated CKBFS v3 cell output data
|
|
379
582
|
const outputData = CKBFSData.pack(
|
|
@@ -394,10 +597,14 @@ export async function createAppendV3Transaction(
|
|
|
394
597
|
BigInt(outputData.length + ckbfsCell.type.occupiedSize + ckbfsCell.lock.occupiedSize + 8) *
|
|
395
598
|
100000000n;
|
|
396
599
|
|
|
600
|
+
console.log(
|
|
601
|
+
`Original capacity: ${ckbfsCell.capacity}, Calculated size: ${ckbfsCellSize}, Data size: ${outputData.length}`,
|
|
602
|
+
);
|
|
603
|
+
|
|
397
604
|
// Use the maximum value between calculated size and original capacity
|
|
398
605
|
const outputCapacity = ckbfsCellSize > ckbfsCell.capacity ? ckbfsCellSize : ckbfsCell.capacity;
|
|
399
606
|
|
|
400
|
-
// Create transaction
|
|
607
|
+
// Create initial transaction with the CKBFS cell input
|
|
401
608
|
const tx = Transaction.from({
|
|
402
609
|
inputs: [
|
|
403
610
|
{
|
|
@@ -412,10 +619,6 @@ export async function createAppendV3Transaction(
|
|
|
412
619
|
capacity: outputCapacity,
|
|
413
620
|
},
|
|
414
621
|
],
|
|
415
|
-
witnesses: [
|
|
416
|
-
[], // Empty secp witness for signing
|
|
417
|
-
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
418
|
-
],
|
|
419
622
|
outputsData: [outputData],
|
|
420
623
|
});
|
|
421
624
|
|
|
@@ -428,11 +631,24 @@ export async function createAppendV3Transaction(
|
|
|
428
631
|
depType: "depGroup",
|
|
429
632
|
});
|
|
430
633
|
|
|
431
|
-
|
|
432
|
-
await tx.completeInputsByCapacity(signer);
|
|
634
|
+
const inputsBefore = tx.inputs.length;
|
|
433
635
|
|
|
434
|
-
|
|
435
|
-
|
|
636
|
+
const witnesses: any = [];
|
|
637
|
+
// add empty witness for signer if ckbfs's lock is the same as signer's lock
|
|
638
|
+
if (address.script.hash() === lock.hash()) {
|
|
639
|
+
witnesses.push("0x");
|
|
640
|
+
}
|
|
641
|
+
// add ckbfs witnesses
|
|
642
|
+
witnesses.push(
|
|
643
|
+
...ckbfsWitnesses.map((w) => `0x${Buffer.from(w).toString("hex")}`),
|
|
644
|
+
);
|
|
645
|
+
|
|
646
|
+
// Add empty witnesses for signer's input
|
|
647
|
+
// This is to ensure that the transaction is valid and can be signed
|
|
648
|
+
for (let i = inputsBefore; i < tx.inputs.length; i++) {
|
|
649
|
+
witnesses.push("0x");
|
|
650
|
+
}
|
|
651
|
+
tx.witnesses = witnesses;
|
|
436
652
|
|
|
437
653
|
return tx;
|
|
438
654
|
}
|