@ckbfs/api 2.0.18 → 2.0.19

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.
@@ -222,7 +222,7 @@ async function prepareAppendV3Transaction(options) {
222
222
  const combinedContent = Buffer.concat(contentChunks);
223
223
  const newChecksum = await (0, checksum_1.updateChecksum)(previousChecksum, combinedContent);
224
224
  // Calculate the actual witness indices where our content is placed
225
- const contentStartIndex = witnessStartIndex || from?.witnesses.length || 1;
225
+ const contentStartIndex = witnessStartIndex || from?.witnesses.length || 0;
226
226
  // Create CKBFS v3 witnesses with backlink info
227
227
  const ckbfsWitnesses = (0, witness_1.createChunkedCKBFSV3Witnesses)(contentChunks, {
228
228
  previousTxHash,
@@ -0,0 +1,503 @@
1
+ import fs from "fs"
2
+ import path, { parse } from 'path';
3
+ import {
4
+ CKBFS,
5
+ NetworkType,
6
+ CKBFSDataType,
7
+ ProtocolVersion,
8
+ CKBFSData,
9
+ getFileContentFromChain,
10
+ getCKBFSScriptConfig,
11
+ extractCKBFSV3WitnessContent,
12
+ isCKBFSV3Witness,
13
+ resolveCKBFSCell,
14
+ createAppendV3Transaction
15
+ } from '../src';
16
+ import {
17
+ Script,
18
+ Address,
19
+ ClientPublicTestnet,
20
+ ClientPublicMainnet,
21
+ Transaction,
22
+ ccc
23
+ } from "@ckb-ccc/core";
24
+ import {
25
+ transferSpore
26
+ } from "@ckb-ccc/spore";
27
+ //import { isMainnet, network, CKB_PRIVATE_KEY } from "../config"
28
+
29
+ const isMainnet = false
30
+ const CKB_PRIVATE_KEY = "0x1733e6f7825f99eb41ca26f8569fa90c2cb8c5ae199bbd38a97562291f784983"
31
+
32
+ const network = isMainnet ? NetworkType.Mainnet : NetworkType.Testnet
33
+ const client = isMainnet ? new ClientPublicMainnet() : new ClientPublicTestnet()
34
+
35
+ export const TestnetTraceLockDep = {
36
+ outPoint: {
37
+ // txHash: "0xb60c07715089877b238a60559ff4c15b46991ddcf1cfd2f9a40c32d6fff668ee",
38
+ txHash: "0x7a3e492188438ca4b260fed0f7a00d18ec9e4671a8c3b54fc2b3b49447ab2e2f",
39
+ index: 0x0,
40
+ },
41
+ depType: "code",
42
+ }
43
+
44
+ export const MainnetTraceLockDep = {
45
+ outPoint: {
46
+ txHash: "0xd00a0222a12ae202d68fa4e90650ba560b325a3707c901395966a191372c47f7",
47
+ index: 0x0,
48
+ },
49
+ depType: "code",
50
+ }
51
+
52
+ export const TestnetShadowDep = {
53
+ outPoint: {
54
+ txHash: "0x2d8a311a55e42d7d6810610149f6e125f0d292112f8ed4a21177aec05da2b905",
55
+ index: 0x0,
56
+ },
57
+ depType: "code",
58
+ }
59
+
60
+ export const MainnetShadowDep = {
61
+ outPoint: {
62
+ txHash: "0x5b20ec7d7d5c6410ac4924c57d1eac1dd50129b6263be3dc93055397b5724dc2",
63
+ index: 0x0,
64
+ },
65
+ depType: "code",
66
+ }
67
+
68
+
69
+ const TraceLockDep = isMainnet ? MainnetTraceLockDep : TestnetTraceLockDep
70
+ const ShadowDep = isMainnet ? MainnetShadowDep : TestnetShadowDep
71
+
72
+ const TraceLogCodeHash = "0xbd7e56daaa4cc9ecf488a50d2f4db2a55a7c86eb2df2157d24a62be9be5b34ab"
73
+ const ShadowSporeCodeHash = "0x6361d4b20d845953d9c9431bbba08905573005a71e2a2432e7e0e7c685666f24"
74
+
75
+ const ckbfs = new CKBFS(
76
+ CKB_PRIVATE_KEY,
77
+ isMainnet ? NetworkType.Mainnet : NetworkType.Testnet,
78
+ {
79
+ version: ProtocolVersion.V3,
80
+ chunkSize: 30 * 1024, // 30KB chunks
81
+ useTypeID: false // Use code hash instead of type ID
82
+ }
83
+ );
84
+
85
+ async function getAddressScriptHash(addr: string | Address) {
86
+ if (typeof addr === 'string') {
87
+ addr = await Address.fromString(addr, client)
88
+ console.log("addr=", addr)
89
+ }
90
+ return `0x03${addr.script.hash().slice(2)}`
91
+ }
92
+
93
+ export async function mint(physicalArtifactNo: string) {
94
+ // Get address info
95
+ const address = await ckbfs.getAddress();
96
+ const ownerHash = await getAddressScriptHash(address)
97
+
98
+ const content = `MINT,TO:${ownerHash}\n`
99
+
100
+ // You can provide additional options
101
+ const options = {
102
+ contentType: 'text/plain',
103
+ filename: `${physicalArtifactNo}.LOGS`,
104
+ };
105
+ const tx = await ckbfs.createPublishContentTransaction(content, options);
106
+ const signer = ckbfs['signer']
107
+ const txHash = await signer.sendTransaction(tx)
108
+ const ckbfsId = tx.outputs[0].type?.args.toString()
109
+ return { txHash, log: content, ckbfsId };
110
+ }
111
+
112
+ export async function claim(to: string, sporeId: string, ckbfsId: string) {
113
+ const from = await ckbfs.getAddress();
114
+ const ckbfsCell = await getCkbfsCellInfo(ckbfsId);
115
+ console.log("ckbfsCell=", ckbfsCell)
116
+ const fromHash = await getAddressScriptHash(from)
117
+ const toHash = await getAddressScriptHash(to)
118
+
119
+ const content = `TRANSFER,FROM:${fromHash},TO:${toHash}\n`
120
+
121
+ const tx = await ckbfs.createAppendContentTransaction(content, ckbfsCell);
122
+
123
+ tx.addCellDeps(TraceLockDep)
124
+
125
+ const sizeBefore = tx.outputs[0].lock.occupiedSize
126
+ const traceLogLock = Script.from({
127
+ codeHash: TraceLogCodeHash,
128
+ hashType: "type",
129
+ args: toHash
130
+ })
131
+ tx.outputs[0].lock = traceLogLock
132
+ const sizeAfter = traceLogLock.occupiedSize
133
+
134
+ const diffSize = BigInt(sizeAfter - sizeBefore) * BigInt(100000000)
135
+ tx.outputs[0].capacity = tx.outputs[0].capacity + diffSize
136
+
137
+ const sporeLockArgs = `0x01${ tx.outputs[0].type?.hash().slice(2) }`
138
+ const newSporeLock = Script.from({
139
+ codeHash: ShadowSporeCodeHash,
140
+ args: sporeLockArgs,
141
+ hashType: "data1",
142
+ })
143
+
144
+ tx.outputs = [
145
+ tx.outputs[0]
146
+ ]
147
+
148
+ const signer = ckbfs['signer']
149
+ const sporeTx = await transferSpore({
150
+ signer: signer as any,
151
+ id: sporeId,
152
+ to: newSporeLock,
153
+ tx: tx,
154
+ })
155
+
156
+ tx.addCellDeps(ShadowDep)
157
+
158
+ const finalTx = sporeTx.tx
159
+ await finalTx.completeFeeBy(signer as any)
160
+ console.log("finalTx=", finalTx)
161
+ const txHash = await signer.sendTransaction(finalTx)
162
+ console.log(`File appended successfully! Transaction Hash: ${txHash}`);
163
+ return { txHash, log: content }
164
+ }
165
+
166
+ export async function giveName(name: string, ckbfsId: string) {
167
+ const ckbfsCell = await getCkbfsCellInfo(ckbfsId)
168
+ const content = `GIVE_NAME,NEW_NAME:${name}\n`
169
+
170
+ console.log("ckbfsCell=", ckbfsCell)
171
+
172
+ const signer = ckbfs['signer']
173
+ let tx = await ckbfs.createAppendContentTransaction(content, ckbfsCell);
174
+ tx.addCellDeps(TraceLockDep)
175
+
176
+
177
+ parseCkbfsData(tx.outputsData[0])
178
+
179
+
180
+ //tx.witnesses = tx.witnesses.reverse()
181
+ // tx.witnesses[0] = '0x'
182
+ // tx.witnesses[0] = tx.witnesses[0]
183
+
184
+ console.log("tx=", tx)
185
+
186
+ tx = await signer.signTransaction(tx)
187
+
188
+ console.log("signed tx=", tx)
189
+ // await signer.sendTransaction(tx)
190
+ const txHash = await signer.sendTransaction(tx)
191
+ console.log(`File appended successfully! Transaction Hash: ${txHash}`);
192
+ return { txHash, log: content }
193
+ }
194
+
195
+ export async function transfer(to: string, ckbfsId: string) {
196
+ const ckbfsCell = await getCkbfsCellInfo(ckbfsId)
197
+ const from = await ckbfs.getAddress();
198
+ const fromHash = await getAddressScriptHash(from)
199
+ const toHash = await getAddressScriptHash(to)
200
+ const content = `TRANSFER,FROM:${fromHash},TO:${toHash}\n`
201
+
202
+ const signer = ckbfs['signer']
203
+ let tx = await ckbfs.createAppendContentTransaction(content, ckbfsCell);
204
+ tx.outputs[0].lock.args = toHash as `0x{string}`;
205
+ // tx.addCellDeps(TestnetTraceLockDep)
206
+ const txHash = await signer.sendTransaction(tx)
207
+ console.log(`File appended successfully! Transaction Hash: ${txHash}`);
208
+
209
+ return { txHash, log: content };
210
+ }
211
+
212
+ // export async function release(from: string, ckbfsId: string) {
213
+ // const ckbfsCell = await await getCkbfsCellInfo(ckbfsId)
214
+ // const fromHash = await getAddressScriptHash(from)
215
+ // const content = `RELEASE,FROM:${fromHash}\n`
216
+ // const signer = ckbfs['signer']
217
+ // let tx = await ckbfs.createAppendContentTransaction(content, ckbfsCell);
218
+
219
+ // tx.addCellDeps(TestnetTraceLockDep)
220
+ // const txHash = await signer.sendTransaction(tx)
221
+ // console.log(`File appended successfully! Transaction Hash: ${txHash}`);
222
+ // return { txHash, log: content };
223
+ // }
224
+
225
+ // export async function retrieveLogsFromChain(ckbfsId: string) {
226
+ // const { outPoint, data } = await getCkbfsCellInfo(ckbfsId)
227
+ // const content = await getFileContentFromChain(client, outPoint, data)
228
+ // const logs = content.toString().trim().split("\n")
229
+ // return logs
230
+ // }
231
+
232
+ export async function getCkbfsCellInfo(ckbfsId: string) {
233
+ const { codeHash, hashType } = getCKBFSScriptConfig(isMainnet ? NetworkType.Mainnet : NetworkType.Testnet, ProtocolVersion.V3)
234
+ console.log("codeHash, hashType=", codeHash, hashType, ckbfsId)
235
+ const cell = await client.findSingletonCellByType({
236
+ codeHash,
237
+ hashType,
238
+ args: ckbfsId
239
+ }, true)
240
+
241
+ if (!cell || !cell.cellOutput.type) {
242
+ throw new Error('No CKBFS cell found');
243
+ }
244
+
245
+ const rawData = cell.outputData.startsWith('0x')
246
+ ? Buffer.from(cell.outputData.slice(2), 'hex')
247
+ : Buffer.from(cell.outputData, 'hex');
248
+
249
+ const ckbfsData = CKBFSData.unpack(rawData, ProtocolVersion.V3)
250
+
251
+ return {
252
+ outPoint: { txHash: cell.outPoint.txHash as string, index: Number(cell.outPoint.index) },
253
+ type: cell.cellOutput.type,
254
+ lock: cell.cellOutput.lock,
255
+ capacity: cell.cellOutput.capacity,
256
+ data: ckbfsData,
257
+ };
258
+ }
259
+
260
+
261
+ export function getCkbfsList() {
262
+ const file = path.join(__dirname, `./data/${isMainnet ? 'mainnet' : 'testnet'}/ckbfs-list-x3.json`)
263
+ return JSON.parse(fs.readFileSync(file).toString())
264
+ }
265
+
266
+ export async function mintAll() {
267
+ const ckbfsList = getCkbfsList()
268
+ for(const no of ckbfsList) {
269
+ const { txHash, ckbfsId } = await mint(no)
270
+ console.log(`minted ckbfs no=${no}, ckbfsId=${ckbfsId}, txHash=${txHash}`);
271
+
272
+ const interval = setInterval(async () => {
273
+ try {
274
+ const tx = await client.getTransaction(txHash)
275
+ if (tx && tx.status === 'committed') {
276
+ clearInterval(interval)
277
+ console.info(`tx confirmed, https://explorer.nervos.org/transaction/${txHash}`);
278
+ }
279
+ } catch (error) {
280
+ console.error(error)
281
+ }
282
+ }, 5 * 1000)
283
+ }
284
+ }
285
+
286
+ async function getCellInfoFromV3Transaction(txHash: string): Promise<{
287
+ outPoint: { txHash: string; index: number };
288
+ type: Script;
289
+ data: CKBFSDataType;
290
+ lock: Script;
291
+ capacity: bigint;
292
+ previousTxHash: string;
293
+ previousWitnessIndex: number;
294
+ previousChecksum: number;
295
+ }> {
296
+ console.log(`Retrieving v3 transaction data for: ${txHash}`);
297
+
298
+ try {
299
+ // Get transaction from RPC
300
+ const txWithStatus = await client.getTransaction(txHash);
301
+ if (!txWithStatus || !txWithStatus.transaction) {
302
+ throw new Error(`Transaction ${txHash} not found`);
303
+ }
304
+
305
+ const tx = Transaction.from(txWithStatus.transaction);
306
+ console.log(`Transaction found with ${tx.outputs.length} outputs`);
307
+
308
+ // Find the CKBFS cell output (first output with type script)
309
+ let ckbfsCellIndex = 0;
310
+ const output = tx.outputs[ckbfsCellIndex];
311
+ if (!output || !output.type) {
312
+ throw new Error('No CKBFS cell found in the transaction');
313
+ }
314
+
315
+ console.log(`Found CKBFS v3 cell at index ${ckbfsCellIndex}`);
316
+ console.log(`Cell type script hash: ${output.type.hash()}`);
317
+
318
+ // Get output data
319
+ const outputData = tx.outputsData[ckbfsCellIndex];
320
+ if (!outputData) {
321
+ throw new Error('Output data not found');
322
+ }
323
+
324
+ // Parse the output data as CKBFS v3 data
325
+ const rawData = outputData.startsWith('0x')
326
+ ? ccc.bytesFrom(outputData.slice(2), 'hex')
327
+ : Buffer.from(outputData, 'hex');
328
+
329
+ // Unpack the raw data using v3 format
330
+ const version = ProtocolVersion.V3;
331
+ console.log(`Using protocol version ${version} for unpacking v3 cell data`);
332
+
333
+ let ckbfsData: CKBFSDataType;
334
+ try {
335
+ ckbfsData = CKBFSData.unpack(rawData, version);
336
+
337
+ console.log('Successfully unpacked CKBFS v3 cell data:');
338
+ console.log(`- Checksum: ${ckbfsData.checksum}`);
339
+ console.log(`- File: ${ckbfsData.filename}`);
340
+ console.log(`- Content Type: ${ckbfsData.contentType}`);
341
+ console.log(`- Index: ${ckbfsData.index}`);
342
+ } catch (error) {
343
+ console.error('Error unpacking CKBFS v3 data:', error);
344
+ throw new Error(`Failed to unpack CKBFS v3 data: ${error}`);
345
+ }
346
+
347
+ // Extract backlink information from v3 witness
348
+ let previousTxHash = '0x' + '00'.repeat(32);
349
+ let previousWitnessIndex = 0;
350
+ let previousChecksum = 0;
351
+
352
+ if (ckbfsData.index !== undefined && ckbfsData.index < tx.witnesses.length) {
353
+ const witnessHex = tx.witnesses[ckbfsData.index];
354
+ const witness = Buffer.from(witnessHex.slice(2), 'hex');
355
+
356
+ if (isCKBFSV3Witness(witness)) {
357
+ try {
358
+ const witnessData = extractCKBFSV3WitnessContent(witness, true);
359
+ previousTxHash = witnessData.previousTxHash || previousTxHash;
360
+ previousWitnessIndex = witnessData.previousWitnessIndex || 0;
361
+ previousChecksum = witnessData.previousChecksum || 0;
362
+
363
+ console.log('Extracted v3 backlink information:');
364
+ console.log(`- Previous TX Hash: ${previousTxHash}`);
365
+ console.log(`- Previous Witness Index: ${previousWitnessIndex}`);
366
+ console.log(`- Previous Checksum: ${previousChecksum}`);
367
+ } catch (error) {
368
+ console.warn('Could not extract backlink info from witness:', error);
369
+ }
370
+ }
371
+ }
372
+
373
+ return {
374
+ outPoint: {
375
+ txHash,
376
+ index: ckbfsCellIndex
377
+ },
378
+ type: output.type,
379
+ lock: output.lock,
380
+ capacity: output.capacity,
381
+ data: ckbfsData,
382
+ previousTxHash,
383
+ previousWitnessIndex,
384
+ previousChecksum
385
+ };
386
+ } catch (error) {
387
+ console.error('Error retrieving v3 transaction data:', error);
388
+ throw new Error(`Failed to retrieve or parse v3 cell data: ${error}`);
389
+ }
390
+ }
391
+
392
+ export async function getCkbfsTxContent(txHash: string, index = 0) {
393
+ const tx = await client.getTransaction(txHash);
394
+ const {
395
+ transaction,
396
+ status: txStatus,
397
+ blockNumber,
398
+ } = tx!;
399
+
400
+ let timestamp: number = 0;
401
+ if (txStatus === 'committed') {
402
+ const header = await client.getHeaderByNumber(blockNumber!);
403
+ timestamp = Number(header?.timestamp);
404
+ }
405
+ const toAddress = Address.fromScript(
406
+ transaction.outputs[0].lock,
407
+ client,
408
+ ).toString();
409
+ const outputData = transaction.outputsData[index];
410
+
411
+ const rawData = outputData.startsWith('0x')
412
+ ? Buffer.from(outputData.slice(2), 'hex')
413
+ : Buffer.from(outputData, 'hex');
414
+ const ckbfsData = CKBFSData.unpack(rawData as any, ProtocolVersion.V3);
415
+ const contentChunks = [];
416
+
417
+ // const indexes =
418
+ // ckbfsData.indexes ||
419
+ // (ckbfsData.index !== undefined ? [ckbfsData.index] : []);
420
+ // if (indexes.length > 0) {
421
+ // // Get content from each witness index
422
+ // for (const idx of indexes) {
423
+ // if (idx >= transaction.witnesses.length) {
424
+ // console.warn(`Witness index ${idx} out of range`);
425
+ // continue;
426
+ // }
427
+ // const witnessHex = transaction.witnesses[idx];
428
+ // const witness = Buffer.from(witnessHex.slice(2), 'hex'); // Remove 0x prefix
429
+ // // Extract content (skip CKBFS header + version byte)
430
+ // if (witness.length >= 6 && witness.slice(0, 5).toString() === 'CKBFS') {
431
+ // const content = witness.slice(6);
432
+ // contentChunks.push(content); // Add to beginning of array (we're going backwards)
433
+ // } else {
434
+ // console.warn(`Witness at index ${idx} is not a valid CKBFS witness`);
435
+ // }
436
+ // }
437
+ // }
438
+ const witnessHex = transaction.witnesses[ckbfsData.index!];
439
+ const witness = Buffer.from(witnessHex.slice(2), 'hex');
440
+ const witnessData = extractCKBFSV3WitnessContent(new Uint8Array(witness), true);
441
+
442
+ // const witnessData = extractCKBFSV3WitnessContent(witness as any, true);
443
+
444
+ console.log("witnessData=", witnessData)
445
+ console.log("content=", Buffer.from(witnessData.content).toString())
446
+
447
+ const ckbfsId = transaction.outputs[0].type?.args.toString();
448
+
449
+ // const content = Buffer.concat(contentChunks).toString().trim();
450
+ return {
451
+ data: ckbfsData,
452
+ toAddress,
453
+ content: "",
454
+ ckbfsId,
455
+ txStatus,
456
+ committedAt: timestamp,
457
+ };
458
+ }
459
+
460
+ function parseCkbfsData(outputData: string){
461
+ // const outputData = "0x4500000014000000180000001c0000002a000000010000002614cb020a000000746578742f706c61696e170000003358303132354e4d4e5641503030303130312e4c4f4753"
462
+ // const outputData = "0x4500000014000000180000001c0000002a000000010000009443df5b0a000000746578742f706c61696e170000003358303132354e4d4e5641503030303130322e4c4f4753"
463
+ const rawData = outputData.startsWith('0x')
464
+ ? Buffer.from(outputData.slice(2), 'hex')
465
+ : Buffer.from(outputData, 'hex');
466
+ const ckbfsData = CKBFSData.unpack(rawData as any, ProtocolVersion.V3);
467
+ console.log(ckbfsData)
468
+ }
469
+
470
+
471
+ // mintAll()
472
+
473
+ // mint("3X0125NMNVAP000102").then(console.log)
474
+
475
+ // claim("ckt1qyqtaymen8quq9r3sf7x20uuq4pl5t8r4x8qqunj4q",
476
+ // "0x2b3cbd4c86a42e3ea1d092daab2f213e1600203906f33645f825159e1071d1e4", // sporeId
477
+ // "0x33fd0f19f411eec7cc84d32577a015612afc00cd008225a2ed34a8fda475e498" // ckbfsId
478
+ // );
479
+
480
+ // getCellInfoFromV3Transaction("0x54c86dbdc6bdafb4616221d54cfc0ff186a4a772eba509838684f8e1ea271b40").then(console.log)
481
+
482
+ // parseCkbfsData()
483
+
484
+ giveName(
485
+ "aaa",
486
+ "0x33fd0f19f411eec7cc84d32577a015612afc00cd008225a2ed34a8fda475e498" // ckbfsId
487
+ ).then(console.log)
488
+
489
+ // transfer(
490
+ // "ckt1qyqtaymen8quq9r3sf7x20uuq4pl5t8r4x8qqunj4q",
491
+ // "0xe58baa82c4844add60911bb890487fea3c45436b9d7b203cc1065301d0a9c503"
492
+ // ).then(console.log)
493
+
494
+ // getCkbfsCellInfo("0x52315569e5ff0d6d6392f047fcbea97bae1b32b688c29aca5251a157157278d3").then((data) => {
495
+ // console.log("ckbfs data=", data)
496
+ // })
497
+
498
+ // getCkbfsTxContent("0xecc76092e301d15a808ee91570bc8c71ea448d17e9dc186c699db716878f116a").then(console.log)
499
+ // getCellInfoFromV3Transaction("0x8dc79a7630145e2a220e2363beca2781c6a4f0f79ef7a01ab73f17dc90571fb7").then(console.log)
500
+
501
+ // getCkbfsCellInfo("0x0084106b9bea08f512e35b725c4a710ebf09cb65c05601ebde8d911bc6c662bf").then(console.log)
502
+
503
+ // testReresolve("0x0084106b9bea08f512e35b725c4a710ebf09cb65c05601ebde8d911bc6c662bf").then(console.log)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ckbfs/api",
3
- "version": "2.0.18",
3
+ "version": "2.0.19",
4
4
  "description": "SDK for CKBFS protocol on CKB",
5
5
  "license": "MIT",
6
6
  "author": "Code Monad<code@lab-11.org>",
@@ -29,7 +29,7 @@
29
29
  "sdk"
30
30
  ],
31
31
  "devDependencies": {
32
- "@ckb-ccc/spore": "^1.5.5",
32
+ "@ckb-ccc/spore": "1.5.5",
33
33
  "@types/jest": "^29.5.12",
34
34
  "@types/node": "^22.7.9",
35
35
  "jest": "^29.7.0",
package/src/index.ts CHANGED
@@ -712,6 +712,8 @@ export class CKBFS {
712
712
  ckbfsCell.data.index !== undefined &&
713
713
  ckbfsCell.data.checksum !== undefined) {
714
714
 
715
+
716
+
715
717
  return createAppendV3Transaction(this.signer, {
716
718
  ckbfsCell,
717
719
  contentChunks,
@@ -366,7 +366,7 @@ export async function prepareAppendV3Transaction(
366
366
  const newChecksum = await updateChecksum(previousChecksum, combinedContent);
367
367
 
368
368
  // Calculate the actual witness indices where our content is placed
369
- const contentStartIndex = witnessStartIndex || from?.witnesses.length || 1;
369
+ const contentStartIndex = witnessStartIndex || from?.witnesses.length || 0;
370
370
 
371
371
  // Create CKBFS v3 witnesses with backlink info
372
372
  const ckbfsWitnesses = createChunkedCKBFSV3Witnesses(contentChunks, {