@ckb-ccc/core 1.9.1 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/ckb/hash.d.ts +18 -0
  3. package/dist/ckb/hash.d.ts.map +1 -0
  4. package/dist/ckb/hash.js +19 -0
  5. package/dist/ckb/index.d.ts +2 -0
  6. package/dist/ckb/index.d.ts.map +1 -1
  7. package/dist/ckb/index.js +2 -0
  8. package/dist/ckb/script.d.ts +12 -0
  9. package/dist/ckb/script.d.ts.map +1 -1
  10. package/dist/ckb/script.js +17 -0
  11. package/dist/ckb/transaction.d.ts +326 -25
  12. package/dist/ckb/transaction.d.ts.map +1 -1
  13. package/dist/ckb/transaction.js +354 -79
  14. package/dist/ckb/transactionErrors.d.ts +15 -0
  15. package/dist/ckb/transactionErrors.d.ts.map +1 -0
  16. package/dist/ckb/transactionErrors.js +21 -0
  17. package/dist/client/cache/cache.d.ts +7 -0
  18. package/dist/client/cache/cache.d.ts.map +1 -1
  19. package/dist/client/cache/cache.js +11 -0
  20. package/dist/client/cache/memory.advanced.d.ts +36 -3
  21. package/dist/client/cache/memory.advanced.d.ts.map +1 -1
  22. package/dist/client/cache/memory.advanced.js +53 -25
  23. package/dist/client/cache/memory.d.ts +10 -1
  24. package/dist/client/cache/memory.d.ts.map +1 -1
  25. package/dist/client/cache/memory.js +13 -2
  26. package/dist/client/client.d.ts.map +1 -1
  27. package/dist/client/client.js +6 -9
  28. package/dist/client/clientTypes.advanced.d.ts +0 -1
  29. package/dist/client/clientTypes.advanced.d.ts.map +1 -1
  30. package/dist/client/clientTypes.advanced.js +0 -1
  31. package/dist/hasher/hasherCkb.d.ts +0 -16
  32. package/dist/hasher/hasherCkb.d.ts.map +1 -1
  33. package/dist/hasher/hasherCkb.js +0 -18
  34. package/dist/molecule/codec.d.ts +30 -10
  35. package/dist/molecule/codec.d.ts.map +1 -1
  36. package/dist/molecule/codec.js +46 -13
  37. package/dist/signer/ckb/signerCkbScriptReadonly.d.ts +18 -6
  38. package/dist/signer/ckb/signerCkbScriptReadonly.d.ts.map +1 -1
  39. package/dist/signer/ckb/signerCkbScriptReadonly.js +18 -6
  40. package/dist/signer/signer/index.d.ts +11 -1
  41. package/dist/signer/signer/index.d.ts.map +1 -1
  42. package/dist/signer/signer/index.js +23 -0
  43. package/dist.commonjs/ckb/hash.d.ts +18 -0
  44. package/dist.commonjs/ckb/hash.d.ts.map +1 -0
  45. package/dist.commonjs/ckb/hash.js +22 -0
  46. package/dist.commonjs/ckb/index.d.ts +2 -0
  47. package/dist.commonjs/ckb/index.d.ts.map +1 -1
  48. package/dist.commonjs/ckb/index.js +2 -0
  49. package/dist.commonjs/ckb/script.d.ts +12 -0
  50. package/dist.commonjs/ckb/script.d.ts.map +1 -1
  51. package/dist.commonjs/ckb/script.js +17 -0
  52. package/dist.commonjs/ckb/transaction.d.ts +326 -25
  53. package/dist.commonjs/ckb/transaction.d.ts.map +1 -1
  54. package/dist.commonjs/ckb/transaction.js +353 -78
  55. package/dist.commonjs/ckb/transactionErrors.d.ts +15 -0
  56. package/dist.commonjs/ckb/transactionErrors.d.ts.map +1 -0
  57. package/dist.commonjs/ckb/transactionErrors.js +26 -0
  58. package/dist.commonjs/client/cache/cache.d.ts +7 -0
  59. package/dist.commonjs/client/cache/cache.d.ts.map +1 -1
  60. package/dist.commonjs/client/cache/cache.js +11 -0
  61. package/dist.commonjs/client/cache/memory.advanced.d.ts +36 -3
  62. package/dist.commonjs/client/cache/memory.advanced.d.ts.map +1 -1
  63. package/dist.commonjs/client/cache/memory.advanced.js +54 -26
  64. package/dist.commonjs/client/cache/memory.d.ts +10 -1
  65. package/dist.commonjs/client/cache/memory.d.ts.map +1 -1
  66. package/dist.commonjs/client/cache/memory.js +12 -1
  67. package/dist.commonjs/client/client.d.ts.map +1 -1
  68. package/dist.commonjs/client/client.js +5 -8
  69. package/dist.commonjs/client/clientTypes.advanced.d.ts +0 -1
  70. package/dist.commonjs/client/clientTypes.advanced.d.ts.map +1 -1
  71. package/dist.commonjs/client/clientTypes.advanced.js +1 -2
  72. package/dist.commonjs/hasher/hasherCkb.d.ts +0 -16
  73. package/dist.commonjs/hasher/hasherCkb.d.ts.map +1 -1
  74. package/dist.commonjs/hasher/hasherCkb.js +2 -21
  75. package/dist.commonjs/molecule/codec.d.ts +30 -10
  76. package/dist.commonjs/molecule/codec.d.ts.map +1 -1
  77. package/dist.commonjs/molecule/codec.js +46 -13
  78. package/dist.commonjs/signer/ckb/signerCkbScriptReadonly.d.ts +18 -6
  79. package/dist.commonjs/signer/ckb/signerCkbScriptReadonly.d.ts.map +1 -1
  80. package/dist.commonjs/signer/ckb/signerCkbScriptReadonly.js +18 -6
  81. package/dist.commonjs/signer/signer/index.d.ts +11 -1
  82. package/dist.commonjs/signer/signer/index.d.ts.map +1 -1
  83. package/dist.commonjs/signer/signer/index.js +23 -0
  84. package/package.json +1 -1
  85. package/prettier.config.mjs +1 -1
  86. package/src/ckb/hash.ts +28 -0
  87. package/src/ckb/index.ts +2 -0
  88. package/src/ckb/script.ts +20 -0
  89. package/src/ckb/transaction.ts +436 -80
  90. package/src/ckb/transactionErrors.ts +36 -0
  91. package/src/client/cache/cache.ts +14 -1
  92. package/src/client/cache/memory.advanced.ts +57 -30
  93. package/src/client/cache/memory.ts +18 -1
  94. package/src/client/client.ts +5 -10
  95. package/src/client/clientTypes.advanced.ts +0 -1
  96. package/src/hasher/hasherCkb.ts +0 -26
  97. package/src/molecule/codec.ts +50 -13
  98. package/src/signer/ckb/signerCkbScriptReadonly.ts +24 -8
  99. package/src/signer/signer/index.ts +34 -0
@@ -7,11 +7,7 @@ import {
7
7
  type ClientBlockHeaderLike,
8
8
  } from "../client/index.js";
9
9
  import { KnownScript } from "../client/knownScript.js";
10
- import {
11
- Zero,
12
- fixedPointFrom,
13
- fixedPointToString,
14
- } from "../fixedPoint/index.js";
10
+ import { Zero, fixedPointFrom } from "../fixedPoint/index.js";
15
11
  import { Hasher, HasherCkb, hashCkb } from "../hasher/index.js";
16
12
  import { Hex, HexLike, hexFrom } from "../hex/index.js";
17
13
  import { mol } from "../molecule/index.js";
@@ -27,6 +23,10 @@ import type { Signer } from "../signer/index.js";
27
23
  import { apply, reduceAsync } from "../utils/index.js";
28
24
  import { Script, ScriptLike, ScriptOpt } from "./script.js";
29
25
  import { DEP_TYPE_TO_NUM, NUM_TO_DEP_TYPE } from "./transaction.advanced.js";
26
+ import {
27
+ ErrorTransactionInsufficientCapacity,
28
+ ErrorTransactionInsufficientCoin,
29
+ } from "./transactionErrors.js";
30
30
  import type { LumosTransactionSkeletonType } from "./transactionLumos.js";
31
31
 
32
32
  export const DepTypeCodec: mol.Codec<DepTypeLike, DepType> = mol.Codec.from({
@@ -162,6 +162,36 @@ export class OutPoint extends mol.Entity.Base<OutPointLike, OutPoint>() {
162
162
  }
163
163
  return new OutPoint(hexFrom(outPoint.txHash), numFrom(outPoint.index));
164
164
  }
165
+
166
+ /**
167
+ * Clone a OutPoint.
168
+ *
169
+ * @returns A cloned OutPoint instance.
170
+ *
171
+ * @example
172
+ * ```typescript
173
+ * const outPoint1 = outPoint0.clone();
174
+ * ```
175
+ */
176
+ clone(): OutPoint {
177
+ return new OutPoint(this.txHash, this.index);
178
+ }
179
+
180
+ /**
181
+ * Check if the OutPoint is equal to another OutPoint.
182
+ * @public
183
+ * @param other - The other OutPoint to compare with
184
+ * @returns True if the OutPoints are equal, false otherwise
185
+ *
186
+ * @example
187
+ * ```typescript
188
+ * const isEqual = outPoint0.eq(outPoint1);
189
+ * ```
190
+ */
191
+ eq(other: OutPointLike): boolean {
192
+ other = OutPoint.from(other);
193
+ return this.txHash === other.txHash && this.index === other.index;
194
+ }
165
195
  }
166
196
 
167
197
  /**
@@ -205,29 +235,72 @@ export class CellOutput extends mol.Entity.Base<CellOutputLike, CellOutput>() {
205
235
 
206
236
  /**
207
237
  * Creates a CellOutput instance from a CellOutputLike object.
238
+ * This method supports automatic capacity calculation when capacity is 0 or omitted.
208
239
  *
209
240
  * @param cellOutput - A CellOutputLike object or an instance of CellOutput.
241
+ * Can also be an object without capacity when outputData is provided.
242
+ * @param outputData - Optional output data used for automatic capacity calculation.
243
+ * When provided and capacity is 0, the capacity will be calculated
244
+ * as occupiedSize + outputData.length.
210
245
  * @returns A CellOutput instance.
211
246
  *
212
247
  * @example
213
248
  * ```typescript
214
- * const cellOutput = CellOutput.from({
249
+ * // Basic usage with explicit capacity
250
+ * const cellOutput1 = CellOutput.from({
215
251
  * capacity: 1000n,
216
252
  * lock: { codeHash: "0x...", hashType: "type", args: "0x..." },
217
253
  * type: { codeHash: "0x...", hashType: "type", args: "0x..." }
218
254
  * });
255
+ *
256
+ * // Automatic capacity calculation
257
+ * const cellOutput2 = CellOutput.from({
258
+ * lock: { codeHash: "0x...", hashType: "type", args: "0x..." }
259
+ * }, "0x1234"); // Capacity will be calculated automatically
219
260
  * ```
220
261
  */
221
- static from(cellOutput: CellOutputLike): CellOutput {
262
+ static from(cellOutput: CellOutputLike, outputData?: HexLike): CellOutput;
263
+ static from(
264
+ cellOutput: Omit<CellOutputLike, "capacity"> &
265
+ Partial<Pick<CellOutputLike, "capacity">>,
266
+ outputData: HexLike,
267
+ ): CellOutput;
268
+ static from(
269
+ cellOutput: Omit<CellOutputLike, "capacity"> &
270
+ Partial<Pick<CellOutputLike, "capacity">>,
271
+ outputData?: HexLike,
272
+ ): CellOutput {
222
273
  if (cellOutput instanceof CellOutput) {
223
274
  return cellOutput;
224
275
  }
225
276
 
226
- return new CellOutput(
227
- numFrom(cellOutput.capacity),
277
+ const output = new CellOutput(
278
+ numFrom(cellOutput.capacity ?? 0),
228
279
  Script.from(cellOutput.lock),
229
280
  apply(Script.from, cellOutput.type),
230
281
  );
282
+
283
+ if (output.capacity === Zero) {
284
+ output.capacity = fixedPointFrom(
285
+ output.occupiedSize + bytesFrom(outputData ?? "0x").length,
286
+ );
287
+ }
288
+
289
+ return output;
290
+ }
291
+
292
+ /**
293
+ * Clone a CellOutput.
294
+ *
295
+ * @returns A cloned CellOutput instance.
296
+ *
297
+ * @example
298
+ * ```typescript
299
+ * const cellOutput1 = cellOutput0.clone();
300
+ * ```
301
+ */
302
+ clone(): CellOutput {
303
+ return new CellOutput(this.capacity, this.lock.clone(), this.type?.clone());
231
304
  }
232
305
  }
233
306
  export const CellOutputVec = mol.vector(CellOutput);
@@ -264,9 +337,37 @@ export class Cell {
264
337
 
265
338
  /**
266
339
  * Creates a Cell instance from a CellLike object.
340
+ * This method accepts either `outPoint` or `previousOutput` to specify the cell's location,
341
+ * and supports automatic capacity calculation for the cell output.
267
342
  *
268
- * @param cell - A CellLike object or an instance of Cell.
343
+ * @param cell - A CellLike object or an instance of Cell. The object can use either:
344
+ * - `outPoint`: For referencing a cell output
345
+ * - `previousOutput`: For referencing a cell input (alternative name for outPoint)
346
+ * The cellOutput can omit capacity for automatic calculation.
269
347
  * @returns A Cell instance.
348
+ *
349
+ * @example
350
+ * ```typescript
351
+ * // Using outPoint with explicit capacity
352
+ * const cell1 = Cell.from({
353
+ * outPoint: { txHash: "0x...", index: 0 },
354
+ * cellOutput: {
355
+ * capacity: 1000n,
356
+ * lock: { codeHash: "0x...", hashType: "type", args: "0x..." }
357
+ * },
358
+ * outputData: "0x"
359
+ * });
360
+ *
361
+ * // Using previousOutput with automatic capacity calculation
362
+ * const cell2 = Cell.from({
363
+ * previousOutput: { txHash: "0x...", index: 0 },
364
+ * cellOutput: {
365
+ * lock: { codeHash: "0x...", hashType: "type", args: "0x..." }
366
+ * // capacity will be calculated automatically
367
+ * },
368
+ * outputData: "0x1234"
369
+ * });
370
+ * ```
270
371
  */
271
372
 
272
373
  static from(cell: CellLike): Cell {
@@ -276,7 +377,7 @@ export class Cell {
276
377
 
277
378
  return new Cell(
278
379
  OutPoint.from("outPoint" in cell ? cell.outPoint : cell.previousOutput),
279
- CellOutput.from(cell.cellOutput),
380
+ CellOutput.from(cell.cellOutput, cell.outputData),
280
381
  hexFrom(cell.outputData),
281
382
  );
282
383
  }
@@ -700,12 +801,23 @@ export class CellInput extends mol.Entity.Base<CellInputLike, CellInput>() {
700
801
  return (await this.getCell(client)).getDaoProfit(client);
701
802
  }
702
803
 
804
+ /**
805
+ * Clone a CellInput.
806
+ *
807
+ * @returns A cloned CellInput instance.
808
+ *
809
+ * @example
810
+ * ```typescript
811
+ * const cellInput1 = cellInput0.clone();
812
+ * ```
813
+ */
703
814
  clone(): CellInput {
704
- const cloned = super.clone();
705
- cloned.cellOutput = this.cellOutput;
706
- cloned.outputData = this.outputData;
707
-
708
- return cloned;
815
+ return new CellInput(
816
+ this.previousOutput.clone(),
817
+ this.since,
818
+ this.cellOutput?.clone(),
819
+ this.outputData,
820
+ );
709
821
  }
710
822
  }
711
823
  export const CellInputVec = mol.vector(CellInput);
@@ -741,21 +853,6 @@ export class CellDep extends mol.Entity.Base<CellDepLike, CellDep>() {
741
853
  super();
742
854
  }
743
855
 
744
- /**
745
- * Clone a CellDep.
746
- *
747
- * @returns A cloned CellDep instance.
748
- *
749
- * @example
750
- * ```typescript
751
- * const cellDep1 = cellDep0.clone();
752
- * ```
753
- */
754
-
755
- clone(): CellDep {
756
- return new CellDep(this.outPoint.clone(), this.depType);
757
- }
758
-
759
856
  /**
760
857
  * Creates a CellDep instance from a CellDepLike object.
761
858
  *
@@ -781,6 +878,20 @@ export class CellDep extends mol.Entity.Base<CellDepLike, CellDep>() {
781
878
  depTypeFrom(cellDep.depType),
782
879
  );
783
880
  }
881
+
882
+ /**
883
+ * Clone a CellDep.
884
+ *
885
+ * @returns A cloned CellDep instance.
886
+ *
887
+ * @example
888
+ * ```typescript
889
+ * const cellDep1 = cellDep0.clone();
890
+ * ```
891
+ */
892
+ clone(): CellDep {
893
+ return new CellDep(this.outPoint.clone(), this.depType);
894
+ }
784
895
  }
785
896
  export const CellDepVec = mol.vector(CellDep);
786
897
 
@@ -947,6 +1058,9 @@ export class Transaction extends mol.Entity.Base<
947
1058
 
948
1059
  /**
949
1060
  * Copy every properties from another transaction.
1061
+ * This method replaces all properties of the current transaction with those from the provided transaction.
1062
+ *
1063
+ * @param txLike - The transaction-like object to copy properties from.
950
1064
  *
951
1065
  * @example
952
1066
  * ```typescript
@@ -964,6 +1078,55 @@ export class Transaction extends mol.Entity.Base<
964
1078
  this.witnesses = tx.witnesses;
965
1079
  }
966
1080
 
1081
+ /**
1082
+ * Creates a deep copy of the transaction.
1083
+ * This method creates a new Transaction instance with all nested objects cloned,
1084
+ * ensuring that modifications to the cloned transaction do not affect the original.
1085
+ *
1086
+ * @returns A new Transaction instance that is a deep copy of the current transaction.
1087
+ *
1088
+ * @example
1089
+ * ```typescript
1090
+ * const originalTx = Transaction.from({
1091
+ * version: 0,
1092
+ * inputs: [{ previousOutput: { txHash: "0x...", index: 0 } }],
1093
+ * outputs: [{ capacity: 1000n, lock: lockScript }],
1094
+ * outputsData: ["0x"],
1095
+ * witnesses: ["0x"]
1096
+ * });
1097
+ *
1098
+ * const clonedTx = originalTx.clone();
1099
+ *
1100
+ * // Modifications to clonedTx won't affect originalTx
1101
+ * clonedTx.addOutput({ capacity: 2000n, lock: anotherLockScript });
1102
+ * console.log(originalTx.outputs.length); // Still 1
1103
+ * console.log(clonedTx.outputs.length); // Now 2
1104
+ * ```
1105
+ *
1106
+ * @remarks
1107
+ * The clone operation performs deep copying for:
1108
+ * - Cell dependencies (cellDeps) - each CellDep is cloned
1109
+ * - Inputs - each CellInput is cloned
1110
+ * - Outputs - each CellOutput is cloned
1111
+ *
1112
+ * The following are shallow copied (references to immutable data):
1113
+ * - Header dependencies (headerDeps) - Hex strings are immutable
1114
+ * - Output data (outputsData) - Hex strings are immutable
1115
+ * - Witnesses - Hex strings are immutable
1116
+ * - Version - bigint is immutable
1117
+ */
1118
+ clone(): Transaction {
1119
+ return new Transaction(
1120
+ this.version,
1121
+ this.cellDeps.map((c) => c.clone()),
1122
+ this.headerDeps.map((h) => h),
1123
+ this.inputs.map((i) => i.clone()),
1124
+ this.outputs.map((o) => o.clone()),
1125
+ this.outputsData.map((o) => o),
1126
+ this.witnesses.map((w) => w),
1127
+ );
1128
+ }
1129
+
967
1130
  /**
968
1131
  * Creates a Transaction instance from a TransactionLike object.
969
1132
  *
@@ -989,19 +1152,9 @@ export class Transaction extends mol.Entity.Base<
989
1152
  return tx;
990
1153
  }
991
1154
  const outputs =
992
- tx.outputs?.map((output, i) => {
993
- const o = CellOutput.from({
994
- ...output,
995
- capacity: output.capacity ?? 0,
996
- });
997
- if (o.capacity === Zero) {
998
- o.capacity = fixedPointFrom(
999
- o.occupiedSize +
1000
- (apply(bytesFrom, tx.outputsData?.[i])?.length ?? 0),
1001
- );
1002
- }
1003
- return o;
1004
- }) ?? [];
1155
+ tx.outputs?.map((output, i) =>
1156
+ CellOutput.from(output, tx.outputsData?.[i] ?? []),
1157
+ ) ?? [];
1005
1158
  const outputsData = outputs.map((_, i) =>
1006
1159
  hexFrom(tx.outputsData?.[i] ?? "0x"),
1007
1160
  );
@@ -1229,7 +1382,7 @@ export class Transaction extends mol.Entity.Base<
1229
1382
  *
1230
1383
  * @param scriptLike - The script associated with the transaction, represented as a ScriptLike object.
1231
1384
  * @param client - The client for complete extra infos in the transaction.
1232
- * @returns A promise that resolves to the prepared transaction
1385
+ * @returns A promise that resolves to the found index, or undefined if no matching input is found.
1233
1386
  *
1234
1387
  * @example
1235
1388
  * ```typescript
@@ -1256,7 +1409,7 @@ export class Transaction extends mol.Entity.Base<
1256
1409
  *
1257
1410
  * @param scriptLike - The script associated with the transaction, represented as a ScriptLike object.
1258
1411
  * @param client - The client for complete extra infos in the transaction.
1259
- * @returns A promise that resolves to the prepared transaction
1412
+ * @returns A promise that resolves to the found index, or undefined if no matching input is found.
1260
1413
  *
1261
1414
  * @example
1262
1415
  * ```typescript
@@ -1369,14 +1522,14 @@ export class Transaction extends mol.Entity.Base<
1369
1522
  * Set output data at index.
1370
1523
  *
1371
1524
  * @param index - The index of the output data.
1372
- * @param witness - The data to set.
1525
+ * @param data - The data to set.
1373
1526
  *
1374
1527
  * @example
1375
1528
  * ```typescript
1376
- * await tx.setOutputDataAt(0, "0x00");
1529
+ * tx.setOutputDataAt(0, "0x00");
1377
1530
  * ```
1378
1531
  */
1379
- setOutputDataAt(index: number, witness: HexLike): void {
1532
+ setOutputDataAt(index: number, data: HexLike): void {
1380
1533
  if (this.outputsData.length < index) {
1381
1534
  this.outputsData.push(
1382
1535
  ...Array.from(
@@ -1386,7 +1539,7 @@ export class Transaction extends mol.Entity.Base<
1386
1539
  );
1387
1540
  }
1388
1541
 
1389
- this.outputsData[index] = hexFrom(witness);
1542
+ this.outputsData[index] = hexFrom(data);
1390
1543
  }
1391
1544
 
1392
1545
  /**
@@ -1461,16 +1614,7 @@ export class Transaction extends mol.Entity.Base<
1461
1614
  Partial<Pick<CellOutputLike, "capacity">>,
1462
1615
  outputData: HexLike = "0x",
1463
1616
  ): number {
1464
- const output = CellOutput.from({
1465
- ...outputLike,
1466
- capacity: outputLike.capacity ?? 0,
1467
- });
1468
- if (output.capacity === Zero) {
1469
- output.capacity = fixedPointFrom(
1470
- output.occupiedSize + bytesFrom(outputData).length,
1471
- );
1472
- }
1473
- const len = this.outputs.push(output);
1617
+ const len = this.outputs.push(CellOutput.from(outputLike, outputData));
1474
1618
  this.setOutputDataAt(len - 1, outputData);
1475
1619
 
1476
1620
  return len;
@@ -1697,8 +1841,8 @@ export class Transaction extends mol.Entity.Base<
1697
1841
  return addedCount;
1698
1842
  }
1699
1843
 
1700
- throw new Error(
1701
- `Insufficient CKB, need ${fixedPointToString(expectedCapacity - accumulated)} extra CKB`,
1844
+ throw new ErrorTransactionInsufficientCapacity(
1845
+ expectedCapacity - accumulated,
1702
1846
  );
1703
1847
  }
1704
1848
 
@@ -1782,8 +1926,9 @@ export class Transaction extends mol.Entity.Base<
1782
1926
  return addedCount;
1783
1927
  }
1784
1928
 
1785
- throw new Error(
1786
- `Insufficient coin, need ${expectedBalance - accumulated} extra coin`,
1929
+ throw new ErrorTransactionInsufficientCoin(
1930
+ expectedBalance - accumulated,
1931
+ type,
1787
1932
  );
1788
1933
  }
1789
1934
 
@@ -1836,12 +1981,54 @@ export class Transaction extends mol.Entity.Base<
1836
1981
  return (numFrom(txSize) * numFrom(feeRate) + numFrom(999)) / numFrom(1000);
1837
1982
  }
1838
1983
 
1984
+ /**
1985
+ * Completes the transaction fee by adding inputs and handling change outputs.
1986
+ * This method automatically calculates the required fee based on the transaction size and fee rate,
1987
+ * adds necessary inputs to cover the fee, and handles change outputs through the provided change function.
1988
+ *
1989
+ * @param from - The signer to complete inputs from and prepare the transaction.
1990
+ * @param change - A function that handles change capacity. It receives the transaction and excess capacity,
1991
+ * and should return the additional capacity needed (0 if change is handled successfully,
1992
+ * positive number if more capacity is needed for change cell creation).
1993
+ * @param expectedFeeRate - The expected fee rate in shannons per 1000 bytes. If not provided,
1994
+ * it will be fetched from the client.
1995
+ * @param filter - Optional filter for selecting cells when adding inputs.
1996
+ * @param options - Optional configuration object.
1997
+ * @param options.feeRateBlockRange - Block range for fee rate calculation when expectedFeeRate is not provided.
1998
+ * @param options.maxFeeRate - Maximum allowed fee rate.
1999
+ * @param options.shouldAddInputs - Whether to add inputs automatically. Defaults to true.
2000
+ * @returns A promise that resolves to a tuple containing:
2001
+ * - The number of inputs added during the process
2002
+ * - A boolean indicating whether change outputs were created (true) or fee was paid without change (false)
2003
+ *
2004
+ * @throws {ErrorTransactionInsufficientCapacity} When there's not enough capacity to cover the fee.
2005
+ * @throws {Error} When the change function doesn't properly handle the available capacity.
2006
+ *
2007
+ * @example
2008
+ * ```typescript
2009
+ * const [addedInputs, hasChange] = await tx.completeFee(
2010
+ * signer,
2011
+ * (tx, capacity) => {
2012
+ * if (capacity >= 61_00000000n) { // Minimum for a change cell
2013
+ * tx.addOutput({ capacity, lock: changeScript });
2014
+ * return 0;
2015
+ * }
2016
+ * return 61_00000000n; // Need more capacity for change cell
2017
+ * },
2018
+ * 1000n // 1000 shannons per 1000 bytes
2019
+ * );
2020
+ * ```
2021
+ */
1839
2022
  async completeFee(
1840
2023
  from: Signer,
1841
2024
  change: (tx: Transaction, capacity: Num) => Promise<NumLike> | NumLike,
1842
2025
  expectedFeeRate?: NumLike,
1843
2026
  filter?: ClientCollectableSearchKeyFilterLike,
1844
- options?: { feeRateBlockRange?: NumLike; maxFeeRate?: NumLike },
2027
+ options?: {
2028
+ feeRateBlockRange?: NumLike;
2029
+ maxFeeRate?: NumLike;
2030
+ shouldAddInputs?: boolean;
2031
+ },
1845
2032
  ): Promise<[number, boolean]> {
1846
2033
  const feeRate =
1847
2034
  expectedFeeRate ??
@@ -1852,40 +2039,67 @@ export class Transaction extends mol.Entity.Base<
1852
2039
 
1853
2040
  let leastFee = Zero;
1854
2041
  let leastExtraCapacity = Zero;
1855
-
2042
+ let collected = 0;
2043
+
2044
+ // ===
2045
+ // Usually, for the worst situation, three iterations are needed
2046
+ // 1. First attempt to complete the transaction.
2047
+ // 2. Not enough capacity for the change cell.
2048
+ // 3. Fee increased by the change cell.
2049
+ // ===
1856
2050
  while (true) {
1857
- const tx = this.clone();
1858
- const collected = await (async () => {
2051
+ collected += await (async () => {
2052
+ if (!(options?.shouldAddInputs ?? true)) {
2053
+ return 0;
2054
+ }
2055
+
1859
2056
  try {
1860
- return await tx.completeInputsByCapacity(
2057
+ return await this.completeInputsByCapacity(
1861
2058
  from,
1862
2059
  leastFee + leastExtraCapacity,
1863
2060
  filter,
1864
2061
  );
1865
2062
  } catch (err) {
1866
- if (leastExtraCapacity !== Zero) {
1867
- throw new Error("Not enough capacity for the change cell");
2063
+ if (
2064
+ err instanceof ErrorTransactionInsufficientCapacity &&
2065
+ leastExtraCapacity !== Zero
2066
+ ) {
2067
+ throw new ErrorTransactionInsufficientCapacity(err.amount, {
2068
+ isForChange: true,
2069
+ });
1868
2070
  }
1869
2071
 
1870
2072
  throw err;
1871
2073
  }
1872
2074
  })();
1873
2075
 
1874
- await from.prepareTransaction(tx);
2076
+ const fee = await this.getFee(from.client);
2077
+ if (fee < leastFee + leastExtraCapacity) {
2078
+ // Not enough capacity are collected, it should only happens when shouldAddInputs is false
2079
+ throw new ErrorTransactionInsufficientCapacity(
2080
+ leastFee + leastExtraCapacity - fee,
2081
+ { isForChange: leastExtraCapacity !== Zero },
2082
+ );
2083
+ }
2084
+
2085
+ await from.prepareTransaction(this);
1875
2086
  if (leastFee === Zero) {
1876
2087
  // The initial fee is calculated based on prepared transaction
1877
- leastFee = tx.estimateFee(feeRate);
2088
+ // This should only happens during the first iteration
2089
+ leastFee = this.estimateFee(feeRate);
1878
2090
  }
1879
- const fee = await tx.getFee(from.client);
1880
2091
  // The extra capacity paid the fee without a change
2092
+ // leastExtraCapacity should be 0 here, otherwise we should failed in the previous check
2093
+ // So this only happens in the first iteration
1881
2094
  if (fee === leastFee) {
1882
- this.copy(tx);
1883
2095
  return [collected, false];
1884
2096
  }
1885
2097
 
2098
+ // Invoke the change function on a transaction multiple times may cause problems, so we clone it
2099
+ const tx = this.clone();
1886
2100
  const needed = numFrom(await Promise.resolve(change(tx, fee - leastFee)));
1887
- // No enough extra capacity to create new cells for change
1888
2101
  if (needed > Zero) {
2102
+ // No enough extra capacity to create new cells for change, collect inputs again
1889
2103
  leastExtraCapacity = needed;
1890
2104
  continue;
1891
2105
  }
@@ -1913,11 +2127,48 @@ export class Transaction extends mol.Entity.Base<
1913
2127
  }
1914
2128
  }
1915
2129
 
2130
+ /**
2131
+ * Completes the transaction fee by adding inputs and creating a change output with the specified lock script.
2132
+ * This is a convenience method that automatically creates a change cell with the provided lock script
2133
+ * when there's excess capacity after paying the transaction fee.
2134
+ *
2135
+ * @param from - The signer to complete inputs from and prepare the transaction.
2136
+ * @param change - The lock script for the change output cell.
2137
+ * @param feeRate - Optional fee rate in shannons per 1000 bytes. If not provided, it will be fetched from the client.
2138
+ * @param filter - Optional filter for selecting cells when adding inputs.
2139
+ * @param options - Optional configuration object.
2140
+ * @param options.feeRateBlockRange - Block range for fee rate calculation when feeRate is not provided.
2141
+ * @param options.maxFeeRate - Maximum allowed fee rate.
2142
+ * @param options.shouldAddInputs - Whether to add inputs automatically. Defaults to true.
2143
+ * @returns A promise that resolves to a tuple containing:
2144
+ * - The number of inputs added during the process
2145
+ * - A boolean indicating whether change outputs were created (true) or fee was paid without change (false)
2146
+ *
2147
+ * @example
2148
+ * ```typescript
2149
+ * const changeScript = Script.from({
2150
+ * codeHash: "0x...",
2151
+ * hashType: "type",
2152
+ * args: "0x..."
2153
+ * });
2154
+ *
2155
+ * const [addedInputs, hasChange] = await tx.completeFeeChangeToLock(
2156
+ * signer,
2157
+ * changeScript,
2158
+ * 1000n // 1000 shannons per 1000 bytes
2159
+ * );
2160
+ * ```
2161
+ */
1916
2162
  completeFeeChangeToLock(
1917
2163
  from: Signer,
1918
2164
  change: ScriptLike,
1919
2165
  feeRate?: NumLike,
1920
2166
  filter?: ClientCollectableSearchKeyFilterLike,
2167
+ options?: {
2168
+ feeRateBlockRange?: NumLike;
2169
+ maxFeeRate?: NumLike;
2170
+ shouldAddInputs?: boolean;
2171
+ },
1921
2172
  ): Promise<[number, boolean]> {
1922
2173
  const script = Script.from(change);
1923
2174
 
@@ -1935,24 +2186,91 @@ export class Transaction extends mol.Entity.Base<
1935
2186
  },
1936
2187
  feeRate,
1937
2188
  filter,
2189
+ options,
1938
2190
  );
1939
2191
  }
1940
2192
 
2193
+ /**
2194
+ * Completes the transaction fee using the signer's recommended address for change.
2195
+ * This is a convenience method that automatically uses the signer's recommended
2196
+ * address as the change destination, making it easier to complete transactions
2197
+ * without manually specifying a change address.
2198
+ *
2199
+ * @param from - The signer to complete inputs from and prepare the transaction.
2200
+ * @param feeRate - Optional fee rate in shannons per 1000 bytes. If not provided, it will be fetched from the client.
2201
+ * @param filter - Optional filter for selecting cells when adding inputs.
2202
+ * @param options - Optional configuration object.
2203
+ * @param options.feeRateBlockRange - Block range for fee rate calculation when feeRate is not provided.
2204
+ * @param options.maxFeeRate - Maximum allowed fee rate.
2205
+ * @param options.shouldAddInputs - Whether to add inputs automatically. Defaults to true.
2206
+ * @returns A promise that resolves to a tuple containing:
2207
+ * - The number of inputs added during the process
2208
+ * - A boolean indicating whether change outputs were created (true) or fee was paid without change (false)
2209
+ *
2210
+ * @example
2211
+ * ```typescript
2212
+ * const [addedInputs, hasChange] = await tx.completeFeeBy(
2213
+ * signer,
2214
+ * 1000n // 1000 shannons per 1000 bytes
2215
+ * );
2216
+ *
2217
+ * // Change will automatically go to signer's recommended address
2218
+ * ```
2219
+ */
1941
2220
  async completeFeeBy(
1942
2221
  from: Signer,
1943
2222
  feeRate?: NumLike,
1944
2223
  filter?: ClientCollectableSearchKeyFilterLike,
2224
+ options?: {
2225
+ feeRateBlockRange?: NumLike;
2226
+ maxFeeRate?: NumLike;
2227
+ shouldAddInputs?: boolean;
2228
+ },
1945
2229
  ): Promise<[number, boolean]> {
1946
2230
  const { script } = await from.getRecommendedAddressObj();
1947
2231
 
1948
- return this.completeFeeChangeToLock(from, script, feeRate, filter);
2232
+ return this.completeFeeChangeToLock(from, script, feeRate, filter, options);
1949
2233
  }
1950
2234
 
2235
+ /**
2236
+ * Completes the transaction fee by adding excess capacity to an existing output.
2237
+ * Instead of creating a new change output, this method adds any excess capacity
2238
+ * to the specified existing output in the transaction.
2239
+ *
2240
+ * @param from - The signer to complete inputs from and prepare the transaction.
2241
+ * @param index - The index of the existing output to add excess capacity to.
2242
+ * @param feeRate - Optional fee rate in shannons per 1000 bytes. If not provided, it will be fetched from the client.
2243
+ * @param filter - Optional filter for selecting cells when adding inputs.
2244
+ * @param options - Optional configuration object.
2245
+ * @param options.feeRateBlockRange - Block range for fee rate calculation when feeRate is not provided.
2246
+ * @param options.maxFeeRate - Maximum allowed fee rate.
2247
+ * @param options.shouldAddInputs - Whether to add inputs automatically. Defaults to true.
2248
+ * @returns A promise that resolves to a tuple containing:
2249
+ * - The number of inputs added during the process
2250
+ * - A boolean indicating whether change was applied (true) or fee was paid without change (false)
2251
+ *
2252
+ * @throws {Error} When the specified output index doesn't exist.
2253
+ *
2254
+ * @example
2255
+ * ```typescript
2256
+ * // Add excess capacity to the first output (index 0)
2257
+ * const [addedInputs, hasChange] = await tx.completeFeeChangeToOutput(
2258
+ * signer,
2259
+ * 0, // Output index
2260
+ * 1000n // 1000 shannons per 1000 bytes
2261
+ * );
2262
+ * ```
2263
+ */
1951
2264
  completeFeeChangeToOutput(
1952
2265
  from: Signer,
1953
2266
  index: NumLike,
1954
2267
  feeRate?: NumLike,
1955
2268
  filter?: ClientCollectableSearchKeyFilterLike,
2269
+ options?: {
2270
+ feeRateBlockRange?: NumLike;
2271
+ maxFeeRate?: NumLike;
2272
+ shouldAddInputs?: boolean;
2273
+ },
1956
2274
  ): Promise<[number, boolean]> {
1957
2275
  const change = Number(numFrom(index));
1958
2276
  if (!this.outputs[change]) {
@@ -1966,12 +2284,32 @@ export class Transaction extends mol.Entity.Base<
1966
2284
  },
1967
2285
  feeRate,
1968
2286
  filter,
2287
+ options,
1969
2288
  );
1970
2289
  }
1971
2290
  }
1972
2291
 
1973
2292
  /**
1974
- * Calculate Nervos DAO profit between two blocks
2293
+ * Calculate Nervos DAO profit between two blocks.
2294
+ * This function computes the profit earned from a Nervos DAO deposit
2295
+ * based on the capacity and the time period between deposit and withdrawal.
2296
+ *
2297
+ * @param profitableCapacity - The capacity that earns profit (total capacity minus occupied capacity).
2298
+ * @param depositHeaderLike - The block header when the DAO deposit was made.
2299
+ * @param withdrawHeaderLike - The block header when the DAO withdrawal is made.
2300
+ * @returns The profit amount in CKB (capacity units).
2301
+ *
2302
+ * @example
2303
+ * ```typescript
2304
+ * const profit = calcDaoProfit(
2305
+ * ccc.fixedPointFrom(100), // 100 CKB profitable capacity
2306
+ * depositHeader,
2307
+ * withdrawHeader
2308
+ * );
2309
+ * console.log(`Profit: ${profit} shannons`);
2310
+ * ```
2311
+ *
2312
+ * @see {@link https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md | Nervos DAO RFC}
1975
2313
  */
1976
2314
  export function calcDaoProfit(
1977
2315
  profitableCapacity: NumLike,
@@ -1990,8 +2328,26 @@ export function calcDaoProfit(
1990
2328
  }
1991
2329
 
1992
2330
  /**
1993
- * Calculate claimable epoch for Nervos DAO withdrawal
1994
- * See https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md
2331
+ * Calculate claimable epoch for Nervos DAO withdrawal.
2332
+ * This function determines the earliest epoch when a Nervos DAO withdrawal
2333
+ * can be claimed based on the deposit and withdrawal epochs.
2334
+ *
2335
+ * @param depositHeader - The block header when the DAO deposit was made.
2336
+ * @param withdrawHeader - The block header when the DAO withdrawal was initiated.
2337
+ * @returns The epoch when the withdrawal can be claimed, represented as [number, index, length].
2338
+ *
2339
+ * @example
2340
+ * ```typescript
2341
+ * const claimEpoch = calcDaoClaimEpoch(depositHeader, withdrawHeader);
2342
+ * console.log(`Can claim at epoch: ${claimEpoch[0]}, index: ${claimEpoch[1]}, length: ${claimEpoch[2]}`);
2343
+ * ```
2344
+ *
2345
+ * @remarks
2346
+ * The Nervos DAO has a minimum lock period of 180 epochs (~30 days).
2347
+ * This function calculates the exact epoch when the withdrawal becomes claimable
2348
+ * based on the deposit epoch and withdrawal epoch timing.
2349
+ *
2350
+ * @see {@link https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md | Nervos DAO RFC}
1995
2351
  */
1996
2352
  export function calcDaoClaimEpoch(
1997
2353
  depositHeader: ClientBlockHeaderLike,