@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
@@ -8,7 +8,7 @@ var OutPoint_1, CellOutput_1, Since_1, CellInput_1, CellDep_1, WitnessArgs_1, Tr
8
8
  import { bytesFrom } from "../bytes/index.js";
9
9
  import { ClientBlockHeader, } from "../client/index.js";
10
10
  import { KnownScript } from "../client/knownScript.js";
11
- import { Zero, fixedPointFrom, fixedPointToString, } from "../fixedPoint/index.js";
11
+ import { Zero, fixedPointFrom } from "../fixedPoint/index.js";
12
12
  import { HasherCkb, hashCkb } from "../hasher/index.js";
13
13
  import { hexFrom } from "../hex/index.js";
14
14
  import { mol } from "../molecule/index.js";
@@ -16,6 +16,7 @@ import { numFrom, numFromBytes, numToBytes, numToHex, } from "../num/index.js";
16
16
  import { apply, reduceAsync } from "../utils/index.js";
17
17
  import { Script, ScriptOpt } from "./script.js";
18
18
  import { DEP_TYPE_TO_NUM, NUM_TO_DEP_TYPE } from "./transaction.advanced.js";
19
+ import { ErrorTransactionInsufficientCapacity, ErrorTransactionInsufficientCoin, } from "./transactionErrors.js";
19
20
  export const DepTypeCodec = mol.Codec.from({
20
21
  byteLength: 1,
21
22
  encode: depTypeToBytes,
@@ -115,6 +116,34 @@ let OutPoint = OutPoint_1 = class OutPoint extends mol.Entity.Base() {
115
116
  }
116
117
  return new OutPoint_1(hexFrom(outPoint.txHash), numFrom(outPoint.index));
117
118
  }
119
+ /**
120
+ * Clone a OutPoint.
121
+ *
122
+ * @returns A cloned OutPoint instance.
123
+ *
124
+ * @example
125
+ * ```typescript
126
+ * const outPoint1 = outPoint0.clone();
127
+ * ```
128
+ */
129
+ clone() {
130
+ return new OutPoint_1(this.txHash, this.index);
131
+ }
132
+ /**
133
+ * Check if the OutPoint is equal to another OutPoint.
134
+ * @public
135
+ * @param other - The other OutPoint to compare with
136
+ * @returns True if the OutPoints are equal, false otherwise
137
+ *
138
+ * @example
139
+ * ```typescript
140
+ * const isEqual = outPoint0.eq(outPoint1);
141
+ * ```
142
+ */
143
+ eq(other) {
144
+ other = OutPoint_1.from(other);
145
+ return this.txHash === other.txHash && this.index === other.index;
146
+ }
118
147
  };
119
148
  OutPoint = OutPoint_1 = __decorate([
120
149
  mol.codec(mol.struct({
@@ -143,26 +172,28 @@ let CellOutput = CellOutput_1 = class CellOutput extends mol.Entity.Base() {
143
172
  get occupiedSize() {
144
173
  return 8 + this.lock.occupiedSize + (this.type?.occupiedSize ?? 0);
145
174
  }
175
+ static from(cellOutput, outputData) {
176
+ if (cellOutput instanceof CellOutput_1) {
177
+ return cellOutput;
178
+ }
179
+ const output = new CellOutput_1(numFrom(cellOutput.capacity ?? 0), Script.from(cellOutput.lock), apply(Script.from, cellOutput.type));
180
+ if (output.capacity === Zero) {
181
+ output.capacity = fixedPointFrom(output.occupiedSize + bytesFrom(outputData ?? "0x").length);
182
+ }
183
+ return output;
184
+ }
146
185
  /**
147
- * Creates a CellOutput instance from a CellOutputLike object.
186
+ * Clone a CellOutput.
148
187
  *
149
- * @param cellOutput - A CellOutputLike object or an instance of CellOutput.
150
- * @returns A CellOutput instance.
188
+ * @returns A cloned CellOutput instance.
151
189
  *
152
190
  * @example
153
191
  * ```typescript
154
- * const cellOutput = CellOutput.from({
155
- * capacity: 1000n,
156
- * lock: { codeHash: "0x...", hashType: "type", args: "0x..." },
157
- * type: { codeHash: "0x...", hashType: "type", args: "0x..." }
158
- * });
192
+ * const cellOutput1 = cellOutput0.clone();
159
193
  * ```
160
194
  */
161
- static from(cellOutput) {
162
- if (cellOutput instanceof CellOutput_1) {
163
- return cellOutput;
164
- }
165
- return new CellOutput_1(numFrom(cellOutput.capacity), Script.from(cellOutput.lock), apply(Script.from, cellOutput.type));
195
+ clone() {
196
+ return new CellOutput_1(this.capacity, this.lock.clone(), this.type?.clone());
166
197
  }
167
198
  };
168
199
  CellOutput = CellOutput_1 = __decorate([
@@ -192,15 +223,43 @@ export class Cell {
192
223
  }
193
224
  /**
194
225
  * Creates a Cell instance from a CellLike object.
226
+ * This method accepts either `outPoint` or `previousOutput` to specify the cell's location,
227
+ * and supports automatic capacity calculation for the cell output.
195
228
  *
196
- * @param cell - A CellLike object or an instance of Cell.
229
+ * @param cell - A CellLike object or an instance of Cell. The object can use either:
230
+ * - `outPoint`: For referencing a cell output
231
+ * - `previousOutput`: For referencing a cell input (alternative name for outPoint)
232
+ * The cellOutput can omit capacity for automatic calculation.
197
233
  * @returns A Cell instance.
234
+ *
235
+ * @example
236
+ * ```typescript
237
+ * // Using outPoint with explicit capacity
238
+ * const cell1 = Cell.from({
239
+ * outPoint: { txHash: "0x...", index: 0 },
240
+ * cellOutput: {
241
+ * capacity: 1000n,
242
+ * lock: { codeHash: "0x...", hashType: "type", args: "0x..." }
243
+ * },
244
+ * outputData: "0x"
245
+ * });
246
+ *
247
+ * // Using previousOutput with automatic capacity calculation
248
+ * const cell2 = Cell.from({
249
+ * previousOutput: { txHash: "0x...", index: 0 },
250
+ * cellOutput: {
251
+ * lock: { codeHash: "0x...", hashType: "type", args: "0x..." }
252
+ * // capacity will be calculated automatically
253
+ * },
254
+ * outputData: "0x1234"
255
+ * });
256
+ * ```
198
257
  */
199
258
  static from(cell) {
200
259
  if (cell instanceof Cell) {
201
260
  return cell;
202
261
  }
203
- return new Cell(OutPoint.from("outPoint" in cell ? cell.outPoint : cell.previousOutput), CellOutput.from(cell.cellOutput), hexFrom(cell.outputData));
262
+ return new Cell(OutPoint.from("outPoint" in cell ? cell.outPoint : cell.previousOutput), CellOutput.from(cell.cellOutput, cell.outputData), hexFrom(cell.outputData));
204
263
  }
205
264
  get capacityFree() {
206
265
  const occupiedSize = fixedPointFrom(this.cellOutput.occupiedSize + bytesFrom(this.outputData).length);
@@ -493,11 +552,18 @@ let CellInput = CellInput_1 = class CellInput extends mol.Entity.Base() {
493
552
  async getExtraCapacity(client) {
494
553
  return (await this.getCell(client)).getDaoProfit(client);
495
554
  }
555
+ /**
556
+ * Clone a CellInput.
557
+ *
558
+ * @returns A cloned CellInput instance.
559
+ *
560
+ * @example
561
+ * ```typescript
562
+ * const cellInput1 = cellInput0.clone();
563
+ * ```
564
+ */
496
565
  clone() {
497
- const cloned = super.clone();
498
- cloned.cellOutput = this.cellOutput;
499
- cloned.outputData = this.outputData;
500
- return cloned;
566
+ return new CellInput_1(this.previousOutput.clone(), this.since, this.cellOutput?.clone(), this.outputData);
501
567
  }
502
568
  };
503
569
  CellInput = CellInput_1 = __decorate([
@@ -525,19 +591,6 @@ let CellDep = CellDep_1 = class CellDep extends mol.Entity.Base() {
525
591
  this.outPoint = outPoint;
526
592
  this.depType = depType;
527
593
  }
528
- /**
529
- * Clone a CellDep.
530
- *
531
- * @returns A cloned CellDep instance.
532
- *
533
- * @example
534
- * ```typescript
535
- * const cellDep1 = cellDep0.clone();
536
- * ```
537
- */
538
- clone() {
539
- return new CellDep_1(this.outPoint.clone(), this.depType);
540
- }
541
594
  /**
542
595
  * Creates a CellDep instance from a CellDepLike object.
543
596
  *
@@ -558,6 +611,19 @@ let CellDep = CellDep_1 = class CellDep extends mol.Entity.Base() {
558
611
  }
559
612
  return new CellDep_1(OutPoint.from(cellDep.outPoint), depTypeFrom(cellDep.depType));
560
613
  }
614
+ /**
615
+ * Clone a CellDep.
616
+ *
617
+ * @returns A cloned CellDep instance.
618
+ *
619
+ * @example
620
+ * ```typescript
621
+ * const cellDep1 = cellDep0.clone();
622
+ * ```
623
+ */
624
+ clone() {
625
+ return new CellDep_1(this.outPoint.clone(), this.depType);
626
+ }
561
627
  };
562
628
  CellDep = CellDep_1 = __decorate([
563
629
  mol.codec(mol.struct({
@@ -671,6 +737,9 @@ let Transaction = Transaction_1 = class Transaction extends mol.Entity.Base() {
671
737
  }
672
738
  /**
673
739
  * Copy every properties from another transaction.
740
+ * This method replaces all properties of the current transaction with those from the provided transaction.
741
+ *
742
+ * @param txLike - The transaction-like object to copy properties from.
674
743
  *
675
744
  * @example
676
745
  * ```typescript
@@ -687,6 +756,46 @@ let Transaction = Transaction_1 = class Transaction extends mol.Entity.Base() {
687
756
  this.outputsData = tx.outputsData;
688
757
  this.witnesses = tx.witnesses;
689
758
  }
759
+ /**
760
+ * Creates a deep copy of the transaction.
761
+ * This method creates a new Transaction instance with all nested objects cloned,
762
+ * ensuring that modifications to the cloned transaction do not affect the original.
763
+ *
764
+ * @returns A new Transaction instance that is a deep copy of the current transaction.
765
+ *
766
+ * @example
767
+ * ```typescript
768
+ * const originalTx = Transaction.from({
769
+ * version: 0,
770
+ * inputs: [{ previousOutput: { txHash: "0x...", index: 0 } }],
771
+ * outputs: [{ capacity: 1000n, lock: lockScript }],
772
+ * outputsData: ["0x"],
773
+ * witnesses: ["0x"]
774
+ * });
775
+ *
776
+ * const clonedTx = originalTx.clone();
777
+ *
778
+ * // Modifications to clonedTx won't affect originalTx
779
+ * clonedTx.addOutput({ capacity: 2000n, lock: anotherLockScript });
780
+ * console.log(originalTx.outputs.length); // Still 1
781
+ * console.log(clonedTx.outputs.length); // Now 2
782
+ * ```
783
+ *
784
+ * @remarks
785
+ * The clone operation performs deep copying for:
786
+ * - Cell dependencies (cellDeps) - each CellDep is cloned
787
+ * - Inputs - each CellInput is cloned
788
+ * - Outputs - each CellOutput is cloned
789
+ *
790
+ * The following are shallow copied (references to immutable data):
791
+ * - Header dependencies (headerDeps) - Hex strings are immutable
792
+ * - Output data (outputsData) - Hex strings are immutable
793
+ * - Witnesses - Hex strings are immutable
794
+ * - Version - bigint is immutable
795
+ */
796
+ clone() {
797
+ return new Transaction_1(this.version, this.cellDeps.map((c) => c.clone()), this.headerDeps.map((h) => h), this.inputs.map((i) => i.clone()), this.outputs.map((o) => o.clone()), this.outputsData.map((o) => o), this.witnesses.map((w) => w));
798
+ }
690
799
  /**
691
800
  * Creates a Transaction instance from a TransactionLike object.
692
801
  *
@@ -710,17 +819,7 @@ let Transaction = Transaction_1 = class Transaction extends mol.Entity.Base() {
710
819
  if (tx instanceof Transaction_1) {
711
820
  return tx;
712
821
  }
713
- const outputs = tx.outputs?.map((output, i) => {
714
- const o = CellOutput.from({
715
- ...output,
716
- capacity: output.capacity ?? 0,
717
- });
718
- if (o.capacity === Zero) {
719
- o.capacity = fixedPointFrom(o.occupiedSize +
720
- (apply(bytesFrom, tx.outputsData?.[i])?.length ?? 0));
721
- }
722
- return o;
723
- }) ?? [];
822
+ const outputs = tx.outputs?.map((output, i) => CellOutput.from(output, tx.outputsData?.[i] ?? [])) ?? [];
724
823
  const outputsData = outputs.map((_, i) => hexFrom(tx.outputsData?.[i] ?? "0x"));
725
824
  if (tx.outputsData != null && outputsData.length < tx.outputsData.length) {
726
825
  outputsData.push(...tx.outputsData.slice(outputsData.length).map((d) => hexFrom(d)));
@@ -903,7 +1002,7 @@ let Transaction = Transaction_1 = class Transaction extends mol.Entity.Base() {
903
1002
  *
904
1003
  * @param scriptLike - The script associated with the transaction, represented as a ScriptLike object.
905
1004
  * @param client - The client for complete extra infos in the transaction.
906
- * @returns A promise that resolves to the prepared transaction
1005
+ * @returns A promise that resolves to the found index, or undefined if no matching input is found.
907
1006
  *
908
1007
  * @example
909
1008
  * ```typescript
@@ -924,7 +1023,7 @@ let Transaction = Transaction_1 = class Transaction extends mol.Entity.Base() {
924
1023
  *
925
1024
  * @param scriptLike - The script associated with the transaction, represented as a ScriptLike object.
926
1025
  * @param client - The client for complete extra infos in the transaction.
927
- * @returns A promise that resolves to the prepared transaction
1026
+ * @returns A promise that resolves to the found index, or undefined if no matching input is found.
928
1027
  *
929
1028
  * @example
930
1029
  * ```typescript
@@ -1012,18 +1111,18 @@ let Transaction = Transaction_1 = class Transaction extends mol.Entity.Base() {
1012
1111
  * Set output data at index.
1013
1112
  *
1014
1113
  * @param index - The index of the output data.
1015
- * @param witness - The data to set.
1114
+ * @param data - The data to set.
1016
1115
  *
1017
1116
  * @example
1018
1117
  * ```typescript
1019
- * await tx.setOutputDataAt(0, "0x00");
1118
+ * tx.setOutputDataAt(0, "0x00");
1020
1119
  * ```
1021
1120
  */
1022
- setOutputDataAt(index, witness) {
1121
+ setOutputDataAt(index, data) {
1023
1122
  if (this.outputsData.length < index) {
1024
1123
  this.outputsData.push(...Array.from(new Array(index - this.outputsData.length), () => "0x"));
1025
1124
  }
1026
- this.outputsData[index] = hexFrom(witness);
1125
+ this.outputsData[index] = hexFrom(data);
1027
1126
  }
1028
1127
  /**
1029
1128
  * get input
@@ -1086,14 +1185,7 @@ let Transaction = Transaction_1 = class Transaction extends mol.Entity.Base() {
1086
1185
  * ```
1087
1186
  */
1088
1187
  addOutput(outputLike, outputData = "0x") {
1089
- const output = CellOutput.from({
1090
- ...outputLike,
1091
- capacity: outputLike.capacity ?? 0,
1092
- });
1093
- if (output.capacity === Zero) {
1094
- output.capacity = fixedPointFrom(output.occupiedSize + bytesFrom(outputData).length);
1095
- }
1096
- const len = this.outputs.push(output);
1188
+ const len = this.outputs.push(CellOutput.from(outputLike, outputData));
1097
1189
  this.setOutputDataAt(len - 1, outputData);
1098
1190
  return len;
1099
1191
  }
@@ -1240,7 +1332,7 @@ let Transaction = Transaction_1 = class Transaction extends mol.Entity.Base() {
1240
1332
  if (accumulated === undefined) {
1241
1333
  return addedCount;
1242
1334
  }
1243
- throw new Error(`Insufficient CKB, need ${fixedPointToString(expectedCapacity - accumulated)} extra CKB`);
1335
+ throw new ErrorTransactionInsufficientCapacity(expectedCapacity - accumulated);
1244
1336
  }
1245
1337
  async completeInputsAll(from, filter) {
1246
1338
  const { addedCount } = await this.completeInputs(from, filter ?? {
@@ -1290,7 +1382,7 @@ let Transaction = Transaction_1 = class Transaction extends mol.Entity.Base() {
1290
1382
  if (accumulated === undefined || accumulated >= expectedBalance) {
1291
1383
  return addedCount;
1292
1384
  }
1293
- throw new Error(`Insufficient coin, need ${expectedBalance - accumulated} extra coin`);
1385
+ throw new ErrorTransactionInsufficientCoin(expectedBalance - accumulated, type);
1294
1386
  }
1295
1387
  async completeInputsAddOne(from, filter) {
1296
1388
  const { addedCount, accumulated } = await this.completeInputs(from, filter ?? {
@@ -1320,6 +1412,44 @@ let Transaction = Transaction_1 = class Transaction extends mol.Entity.Base() {
1320
1412
  // + 999 then / 1000 to ceil the calculated fee
1321
1413
  return (numFrom(txSize) * numFrom(feeRate) + numFrom(999)) / numFrom(1000);
1322
1414
  }
1415
+ /**
1416
+ * Completes the transaction fee by adding inputs and handling change outputs.
1417
+ * This method automatically calculates the required fee based on the transaction size and fee rate,
1418
+ * adds necessary inputs to cover the fee, and handles change outputs through the provided change function.
1419
+ *
1420
+ * @param from - The signer to complete inputs from and prepare the transaction.
1421
+ * @param change - A function that handles change capacity. It receives the transaction and excess capacity,
1422
+ * and should return the additional capacity needed (0 if change is handled successfully,
1423
+ * positive number if more capacity is needed for change cell creation).
1424
+ * @param expectedFeeRate - The expected fee rate in shannons per 1000 bytes. If not provided,
1425
+ * it will be fetched from the client.
1426
+ * @param filter - Optional filter for selecting cells when adding inputs.
1427
+ * @param options - Optional configuration object.
1428
+ * @param options.feeRateBlockRange - Block range for fee rate calculation when expectedFeeRate is not provided.
1429
+ * @param options.maxFeeRate - Maximum allowed fee rate.
1430
+ * @param options.shouldAddInputs - Whether to add inputs automatically. Defaults to true.
1431
+ * @returns A promise that resolves to a tuple containing:
1432
+ * - The number of inputs added during the process
1433
+ * - A boolean indicating whether change outputs were created (true) or fee was paid without change (false)
1434
+ *
1435
+ * @throws {ErrorTransactionInsufficientCapacity} When there's not enough capacity to cover the fee.
1436
+ * @throws {Error} When the change function doesn't properly handle the available capacity.
1437
+ *
1438
+ * @example
1439
+ * ```typescript
1440
+ * const [addedInputs, hasChange] = await tx.completeFee(
1441
+ * signer,
1442
+ * (tx, capacity) => {
1443
+ * if (capacity >= 61_00000000n) { // Minimum for a change cell
1444
+ * tx.addOutput({ capacity, lock: changeScript });
1445
+ * return 0;
1446
+ * }
1447
+ * return 61_00000000n; // Need more capacity for change cell
1448
+ * },
1449
+ * 1000n // 1000 shannons per 1000 bytes
1450
+ * );
1451
+ * ```
1452
+ */
1323
1453
  async completeFee(from, change, expectedFeeRate, filter, options) {
1324
1454
  const feeRate = expectedFeeRate ??
1325
1455
  (await from.client.getFeeRate(options?.feeRateBlockRange, options));
@@ -1327,33 +1457,53 @@ let Transaction = Transaction_1 = class Transaction extends mol.Entity.Base() {
1327
1457
  await this.getInputsCapacity(from.client);
1328
1458
  let leastFee = Zero;
1329
1459
  let leastExtraCapacity = Zero;
1460
+ let collected = 0;
1461
+ // ===
1462
+ // Usually, for the worst situation, three iterations are needed
1463
+ // 1. First attempt to complete the transaction.
1464
+ // 2. Not enough capacity for the change cell.
1465
+ // 3. Fee increased by the change cell.
1466
+ // ===
1330
1467
  while (true) {
1331
- const tx = this.clone();
1332
- const collected = await (async () => {
1468
+ collected += await (async () => {
1469
+ if (!(options?.shouldAddInputs ?? true)) {
1470
+ return 0;
1471
+ }
1333
1472
  try {
1334
- return await tx.completeInputsByCapacity(from, leastFee + leastExtraCapacity, filter);
1473
+ return await this.completeInputsByCapacity(from, leastFee + leastExtraCapacity, filter);
1335
1474
  }
1336
1475
  catch (err) {
1337
- if (leastExtraCapacity !== Zero) {
1338
- throw new Error("Not enough capacity for the change cell");
1476
+ if (err instanceof ErrorTransactionInsufficientCapacity &&
1477
+ leastExtraCapacity !== Zero) {
1478
+ throw new ErrorTransactionInsufficientCapacity(err.amount, {
1479
+ isForChange: true,
1480
+ });
1339
1481
  }
1340
1482
  throw err;
1341
1483
  }
1342
1484
  })();
1343
- await from.prepareTransaction(tx);
1485
+ const fee = await this.getFee(from.client);
1486
+ if (fee < leastFee + leastExtraCapacity) {
1487
+ // Not enough capacity are collected, it should only happens when shouldAddInputs is false
1488
+ throw new ErrorTransactionInsufficientCapacity(leastFee + leastExtraCapacity - fee, { isForChange: leastExtraCapacity !== Zero });
1489
+ }
1490
+ await from.prepareTransaction(this);
1344
1491
  if (leastFee === Zero) {
1345
1492
  // The initial fee is calculated based on prepared transaction
1346
- leastFee = tx.estimateFee(feeRate);
1493
+ // This should only happens during the first iteration
1494
+ leastFee = this.estimateFee(feeRate);
1347
1495
  }
1348
- const fee = await tx.getFee(from.client);
1349
1496
  // The extra capacity paid the fee without a change
1497
+ // leastExtraCapacity should be 0 here, otherwise we should failed in the previous check
1498
+ // So this only happens in the first iteration
1350
1499
  if (fee === leastFee) {
1351
- this.copy(tx);
1352
1500
  return [collected, false];
1353
1501
  }
1502
+ // Invoke the change function on a transaction multiple times may cause problems, so we clone it
1503
+ const tx = this.clone();
1354
1504
  const needed = numFrom(await Promise.resolve(change(tx, fee - leastFee)));
1355
- // No enough extra capacity to create new cells for change
1356
1505
  if (needed > Zero) {
1506
+ // No enough extra capacity to create new cells for change, collect inputs again
1357
1507
  leastExtraCapacity = needed;
1358
1508
  continue;
1359
1509
  }
@@ -1375,7 +1525,39 @@ let Transaction = Transaction_1 = class Transaction extends mol.Entity.Base() {
1375
1525
  leastFee = changedFee;
1376
1526
  }
1377
1527
  }
1378
- completeFeeChangeToLock(from, change, feeRate, filter) {
1528
+ /**
1529
+ * Completes the transaction fee by adding inputs and creating a change output with the specified lock script.
1530
+ * This is a convenience method that automatically creates a change cell with the provided lock script
1531
+ * when there's excess capacity after paying the transaction fee.
1532
+ *
1533
+ * @param from - The signer to complete inputs from and prepare the transaction.
1534
+ * @param change - The lock script for the change output cell.
1535
+ * @param feeRate - Optional fee rate in shannons per 1000 bytes. If not provided, it will be fetched from the client.
1536
+ * @param filter - Optional filter for selecting cells when adding inputs.
1537
+ * @param options - Optional configuration object.
1538
+ * @param options.feeRateBlockRange - Block range for fee rate calculation when feeRate is not provided.
1539
+ * @param options.maxFeeRate - Maximum allowed fee rate.
1540
+ * @param options.shouldAddInputs - Whether to add inputs automatically. Defaults to true.
1541
+ * @returns A promise that resolves to a tuple containing:
1542
+ * - The number of inputs added during the process
1543
+ * - A boolean indicating whether change outputs were created (true) or fee was paid without change (false)
1544
+ *
1545
+ * @example
1546
+ * ```typescript
1547
+ * const changeScript = Script.from({
1548
+ * codeHash: "0x...",
1549
+ * hashType: "type",
1550
+ * args: "0x..."
1551
+ * });
1552
+ *
1553
+ * const [addedInputs, hasChange] = await tx.completeFeeChangeToLock(
1554
+ * signer,
1555
+ * changeScript,
1556
+ * 1000n // 1000 shannons per 1000 bytes
1557
+ * );
1558
+ * ```
1559
+ */
1560
+ completeFeeChangeToLock(from, change, feeRate, filter, options) {
1379
1561
  const script = Script.from(change);
1380
1562
  return this.completeFee(from, (tx, capacity) => {
1381
1563
  const changeCell = CellOutput.from({ capacity: 0, lock: script });
@@ -1386,13 +1568,69 @@ let Transaction = Transaction_1 = class Transaction extends mol.Entity.Base() {
1386
1568
  changeCell.capacity = capacity;
1387
1569
  tx.addOutput(changeCell);
1388
1570
  return 0;
1389
- }, feeRate, filter);
1571
+ }, feeRate, filter, options);
1390
1572
  }
1391
- async completeFeeBy(from, feeRate, filter) {
1573
+ /**
1574
+ * Completes the transaction fee using the signer's recommended address for change.
1575
+ * This is a convenience method that automatically uses the signer's recommended
1576
+ * address as the change destination, making it easier to complete transactions
1577
+ * without manually specifying a change address.
1578
+ *
1579
+ * @param from - The signer to complete inputs from and prepare the transaction.
1580
+ * @param feeRate - Optional fee rate in shannons per 1000 bytes. If not provided, it will be fetched from the client.
1581
+ * @param filter - Optional filter for selecting cells when adding inputs.
1582
+ * @param options - Optional configuration object.
1583
+ * @param options.feeRateBlockRange - Block range for fee rate calculation when feeRate is not provided.
1584
+ * @param options.maxFeeRate - Maximum allowed fee rate.
1585
+ * @param options.shouldAddInputs - Whether to add inputs automatically. Defaults to true.
1586
+ * @returns A promise that resolves to a tuple containing:
1587
+ * - The number of inputs added during the process
1588
+ * - A boolean indicating whether change outputs were created (true) or fee was paid without change (false)
1589
+ *
1590
+ * @example
1591
+ * ```typescript
1592
+ * const [addedInputs, hasChange] = await tx.completeFeeBy(
1593
+ * signer,
1594
+ * 1000n // 1000 shannons per 1000 bytes
1595
+ * );
1596
+ *
1597
+ * // Change will automatically go to signer's recommended address
1598
+ * ```
1599
+ */
1600
+ async completeFeeBy(from, feeRate, filter, options) {
1392
1601
  const { script } = await from.getRecommendedAddressObj();
1393
- return this.completeFeeChangeToLock(from, script, feeRate, filter);
1602
+ return this.completeFeeChangeToLock(from, script, feeRate, filter, options);
1394
1603
  }
1395
- completeFeeChangeToOutput(from, index, feeRate, filter) {
1604
+ /**
1605
+ * Completes the transaction fee by adding excess capacity to an existing output.
1606
+ * Instead of creating a new change output, this method adds any excess capacity
1607
+ * to the specified existing output in the transaction.
1608
+ *
1609
+ * @param from - The signer to complete inputs from and prepare the transaction.
1610
+ * @param index - The index of the existing output to add excess capacity to.
1611
+ * @param feeRate - Optional fee rate in shannons per 1000 bytes. If not provided, it will be fetched from the client.
1612
+ * @param filter - Optional filter for selecting cells when adding inputs.
1613
+ * @param options - Optional configuration object.
1614
+ * @param options.feeRateBlockRange - Block range for fee rate calculation when feeRate is not provided.
1615
+ * @param options.maxFeeRate - Maximum allowed fee rate.
1616
+ * @param options.shouldAddInputs - Whether to add inputs automatically. Defaults to true.
1617
+ * @returns A promise that resolves to a tuple containing:
1618
+ * - The number of inputs added during the process
1619
+ * - A boolean indicating whether change was applied (true) or fee was paid without change (false)
1620
+ *
1621
+ * @throws {Error} When the specified output index doesn't exist.
1622
+ *
1623
+ * @example
1624
+ * ```typescript
1625
+ * // Add excess capacity to the first output (index 0)
1626
+ * const [addedInputs, hasChange] = await tx.completeFeeChangeToOutput(
1627
+ * signer,
1628
+ * 0, // Output index
1629
+ * 1000n // 1000 shannons per 1000 bytes
1630
+ * );
1631
+ * ```
1632
+ */
1633
+ completeFeeChangeToOutput(from, index, feeRate, filter, options) {
1396
1634
  const change = Number(numFrom(index));
1397
1635
  if (!this.outputs[change]) {
1398
1636
  throw new Error("Non-existed output to change");
@@ -1400,7 +1638,7 @@ let Transaction = Transaction_1 = class Transaction extends mol.Entity.Base() {
1400
1638
  return this.completeFee(from, (tx, capacity) => {
1401
1639
  tx.outputs[change].capacity += capacity;
1402
1640
  return 0;
1403
- }, feeRate, filter);
1641
+ }, feeRate, filter, options);
1404
1642
  }
1405
1643
  };
1406
1644
  Transaction = Transaction_1 = __decorate([
@@ -1420,7 +1658,26 @@ Transaction = Transaction_1 = __decorate([
1420
1658
  ], Transaction);
1421
1659
  export { Transaction };
1422
1660
  /**
1423
- * Calculate Nervos DAO profit between two blocks
1661
+ * Calculate Nervos DAO profit between two blocks.
1662
+ * This function computes the profit earned from a Nervos DAO deposit
1663
+ * based on the capacity and the time period between deposit and withdrawal.
1664
+ *
1665
+ * @param profitableCapacity - The capacity that earns profit (total capacity minus occupied capacity).
1666
+ * @param depositHeaderLike - The block header when the DAO deposit was made.
1667
+ * @param withdrawHeaderLike - The block header when the DAO withdrawal is made.
1668
+ * @returns The profit amount in CKB (capacity units).
1669
+ *
1670
+ * @example
1671
+ * ```typescript
1672
+ * const profit = calcDaoProfit(
1673
+ * ccc.fixedPointFrom(100), // 100 CKB profitable capacity
1674
+ * depositHeader,
1675
+ * withdrawHeader
1676
+ * );
1677
+ * console.log(`Profit: ${profit} shannons`);
1678
+ * ```
1679
+ *
1680
+ * @see {@link https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md | Nervos DAO RFC}
1424
1681
  */
1425
1682
  export function calcDaoProfit(profitableCapacity, depositHeaderLike, withdrawHeaderLike) {
1426
1683
  const depositHeader = ClientBlockHeader.from(depositHeaderLike);
@@ -1430,8 +1687,26 @@ export function calcDaoProfit(profitableCapacity, depositHeaderLike, withdrawHea
1430
1687
  profitableSize);
1431
1688
  }
1432
1689
  /**
1433
- * Calculate claimable epoch for Nervos DAO withdrawal
1434
- * See https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md
1690
+ * Calculate claimable epoch for Nervos DAO withdrawal.
1691
+ * This function determines the earliest epoch when a Nervos DAO withdrawal
1692
+ * can be claimed based on the deposit and withdrawal epochs.
1693
+ *
1694
+ * @param depositHeader - The block header when the DAO deposit was made.
1695
+ * @param withdrawHeader - The block header when the DAO withdrawal was initiated.
1696
+ * @returns The epoch when the withdrawal can be claimed, represented as [number, index, length].
1697
+ *
1698
+ * @example
1699
+ * ```typescript
1700
+ * const claimEpoch = calcDaoClaimEpoch(depositHeader, withdrawHeader);
1701
+ * console.log(`Can claim at epoch: ${claimEpoch[0]}, index: ${claimEpoch[1]}, length: ${claimEpoch[2]}`);
1702
+ * ```
1703
+ *
1704
+ * @remarks
1705
+ * The Nervos DAO has a minimum lock period of 180 epochs (~30 days).
1706
+ * This function calculates the exact epoch when the withdrawal becomes claimable
1707
+ * based on the deposit epoch and withdrawal epoch timing.
1708
+ *
1709
+ * @see {@link https://github.com/nervosnetwork/rfcs/blob/master/rfcs/0023-dao-deposit-withdraw/0023-dao-deposit-withdraw.md | Nervos DAO RFC}
1435
1710
  */
1436
1711
  export function calcDaoClaimEpoch(depositHeader, withdrawHeader) {
1437
1712
  const depositEpoch = ClientBlockHeader.from(depositHeader).epoch;
@@ -0,0 +1,15 @@
1
+ import { Num, NumLike } from "../num/index.js";
2
+ import { Script, ScriptLike } from "./script.js";
3
+ export declare class ErrorTransactionInsufficientCapacity extends Error {
4
+ readonly amount: Num;
5
+ readonly isForChange: boolean;
6
+ constructor(amountLike: NumLike, reason?: {
7
+ isForChange?: boolean;
8
+ });
9
+ }
10
+ export declare class ErrorTransactionInsufficientCoin extends Error {
11
+ readonly amount: Num;
12
+ readonly type: Script;
13
+ constructor(amountLike: NumLike, typeLike: ScriptLike);
14
+ }
15
+ //# sourceMappingURL=transactionErrors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transactionErrors.d.ts","sourceRoot":"","sources":["../../src/ckb/transactionErrors.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,GAAG,EAAW,OAAO,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEjD,qBAAa,oCAAqC,SAAQ,KAAK;IAC7D,SAAgB,MAAM,EAAE,GAAG,CAAC;IAC5B,SAAgB,WAAW,EAAE,OAAO,CAAC;gBAGnC,UAAU,EAAE,OAAO,EACnB,MAAM,CAAC,EAAE;QACP,WAAW,CAAC,EAAE,OAAO,CAAC;KACvB;CAUJ;AAED,qBAAa,gCAAiC,SAAQ,KAAK;IACzD,SAAgB,MAAM,EAAE,GAAG,CAAC;IAC5B,SAAgB,IAAI,EAAE,MAAM,CAAC;gBAEjB,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,UAAU;CAOtD"}
@@ -0,0 +1,21 @@
1
+ import { fixedPointToString } from "../fixedPoint/index.js";
2
+ import { numFrom } from "../num/index.js";
3
+ import { Script } from "./script.js";
4
+ export class ErrorTransactionInsufficientCapacity extends Error {
5
+ constructor(amountLike, reason) {
6
+ const amount = numFrom(amountLike);
7
+ const isForChange = reason?.isForChange ?? false;
8
+ super(`Insufficient CKB, need ${fixedPointToString(amount)} extra CKB${isForChange ? " for the change cell" : ""}`);
9
+ this.amount = amount;
10
+ this.isForChange = isForChange;
11
+ }
12
+ }
13
+ export class ErrorTransactionInsufficientCoin extends Error {
14
+ constructor(amountLike, typeLike) {
15
+ const amount = numFrom(amountLike);
16
+ const type = Script.from(typeLike);
17
+ super(`Insufficient coin, need ${amount} extra coin`);
18
+ this.amount = amount;
19
+ this.type = type;
20
+ }
21
+ }