@ckb-ccc/core 1.9.1 → 1.10.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 (82) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/ckb/index.d.ts +1 -0
  3. package/dist/ckb/index.d.ts.map +1 -1
  4. package/dist/ckb/index.js +1 -0
  5. package/dist/ckb/script.d.ts +12 -0
  6. package/dist/ckb/script.d.ts.map +1 -1
  7. package/dist/ckb/script.js +17 -0
  8. package/dist/ckb/transaction.d.ts +147 -14
  9. package/dist/ckb/transaction.d.ts.map +1 -1
  10. package/dist/ckb/transaction.js +165 -28
  11. package/dist/ckb/transactionErrors.d.ts +15 -0
  12. package/dist/ckb/transactionErrors.d.ts.map +1 -0
  13. package/dist/ckb/transactionErrors.js +21 -0
  14. package/dist/client/cache/cache.d.ts +7 -0
  15. package/dist/client/cache/cache.d.ts.map +1 -1
  16. package/dist/client/cache/cache.js +11 -0
  17. package/dist/client/cache/memory.advanced.d.ts +36 -3
  18. package/dist/client/cache/memory.advanced.d.ts.map +1 -1
  19. package/dist/client/cache/memory.advanced.js +53 -25
  20. package/dist/client/cache/memory.d.ts +10 -1
  21. package/dist/client/cache/memory.d.ts.map +1 -1
  22. package/dist/client/cache/memory.js +13 -2
  23. package/dist/client/client.d.ts.map +1 -1
  24. package/dist/client/client.js +6 -9
  25. package/dist/client/clientPublicMainnet.advanced.d.ts.map +1 -1
  26. package/dist/client/clientPublicMainnet.advanced.js +60 -0
  27. package/dist/client/clientPublicTestnet.advanced.d.ts +23 -0
  28. package/dist/client/clientPublicTestnet.advanced.d.ts.map +1 -1
  29. package/dist/client/clientPublicTestnet.advanced.js +144 -0
  30. package/dist/client/clientTypes.advanced.d.ts +0 -1
  31. package/dist/client/clientTypes.advanced.d.ts.map +1 -1
  32. package/dist/client/clientTypes.advanced.js +0 -1
  33. package/dist/client/knownScript.d.ts +3 -1
  34. package/dist/client/knownScript.d.ts.map +1 -1
  35. package/dist/client/knownScript.js +4 -0
  36. package/dist.commonjs/ckb/index.d.ts +1 -0
  37. package/dist.commonjs/ckb/index.d.ts.map +1 -1
  38. package/dist.commonjs/ckb/index.js +1 -0
  39. package/dist.commonjs/ckb/script.d.ts +12 -0
  40. package/dist.commonjs/ckb/script.d.ts.map +1 -1
  41. package/dist.commonjs/ckb/script.js +17 -0
  42. package/dist.commonjs/ckb/transaction.d.ts +147 -14
  43. package/dist.commonjs/ckb/transaction.d.ts.map +1 -1
  44. package/dist.commonjs/ckb/transaction.js +164 -27
  45. package/dist.commonjs/ckb/transactionErrors.d.ts +15 -0
  46. package/dist.commonjs/ckb/transactionErrors.d.ts.map +1 -0
  47. package/dist.commonjs/ckb/transactionErrors.js +26 -0
  48. package/dist.commonjs/client/cache/cache.d.ts +7 -0
  49. package/dist.commonjs/client/cache/cache.d.ts.map +1 -1
  50. package/dist.commonjs/client/cache/cache.js +11 -0
  51. package/dist.commonjs/client/cache/memory.advanced.d.ts +36 -3
  52. package/dist.commonjs/client/cache/memory.advanced.d.ts.map +1 -1
  53. package/dist.commonjs/client/cache/memory.advanced.js +54 -26
  54. package/dist.commonjs/client/cache/memory.d.ts +10 -1
  55. package/dist.commonjs/client/cache/memory.d.ts.map +1 -1
  56. package/dist.commonjs/client/cache/memory.js +12 -1
  57. package/dist.commonjs/client/client.d.ts.map +1 -1
  58. package/dist.commonjs/client/client.js +5 -8
  59. package/dist.commonjs/client/clientPublicMainnet.advanced.d.ts.map +1 -1
  60. package/dist.commonjs/client/clientPublicMainnet.advanced.js +60 -0
  61. package/dist.commonjs/client/clientPublicTestnet.advanced.d.ts +23 -0
  62. package/dist.commonjs/client/clientPublicTestnet.advanced.d.ts.map +1 -1
  63. package/dist.commonjs/client/clientPublicTestnet.advanced.js +145 -1
  64. package/dist.commonjs/client/clientTypes.advanced.d.ts +0 -1
  65. package/dist.commonjs/client/clientTypes.advanced.d.ts.map +1 -1
  66. package/dist.commonjs/client/clientTypes.advanced.js +1 -2
  67. package/dist.commonjs/client/knownScript.d.ts +3 -1
  68. package/dist.commonjs/client/knownScript.d.ts.map +1 -1
  69. package/dist.commonjs/client/knownScript.js +4 -0
  70. package/package.json +1 -1
  71. package/src/ckb/index.ts +1 -0
  72. package/src/ckb/script.ts +20 -0
  73. package/src/ckb/transaction.ts +210 -33
  74. package/src/ckb/transactionErrors.ts +36 -0
  75. package/src/client/cache/cache.ts +14 -1
  76. package/src/client/cache/memory.advanced.ts +57 -30
  77. package/src/client/cache/memory.ts +18 -1
  78. package/src/client/client.ts +5 -10
  79. package/src/client/clientPublicMainnet.advanced.ts +68 -0
  80. package/src/client/clientPublicTestnet.advanced.ts +163 -0
  81. package/src/client/clientTypes.advanced.ts +0 -1
  82. package/src/client/knownScript.ts +5 -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
  /**
@@ -229,6 +259,20 @@ export class CellOutput extends mol.Entity.Base<CellOutputLike, CellOutput>() {
229
259
  apply(Script.from, cellOutput.type),
230
260
  );
231
261
  }
262
+
263
+ /**
264
+ * Clone a CellOutput.
265
+ *
266
+ * @returns A cloned CellOutput instance.
267
+ *
268
+ * @example
269
+ * ```typescript
270
+ * const cellOutput1 = cellOutput0.clone();
271
+ * ```
272
+ */
273
+ clone(): CellOutput {
274
+ return new CellOutput(this.capacity, this.lock.clone(), this.type?.clone());
275
+ }
232
276
  }
233
277
  export const CellOutputVec = mol.vector(CellOutput);
234
278
 
@@ -700,12 +744,23 @@ export class CellInput extends mol.Entity.Base<CellInputLike, CellInput>() {
700
744
  return (await this.getCell(client)).getDaoProfit(client);
701
745
  }
702
746
 
747
+ /**
748
+ * Clone a CellInput.
749
+ *
750
+ * @returns A cloned CellInput instance.
751
+ *
752
+ * @example
753
+ * ```typescript
754
+ * const cellInput1 = cellInput0.clone();
755
+ * ```
756
+ */
703
757
  clone(): CellInput {
704
- const cloned = super.clone();
705
- cloned.cellOutput = this.cellOutput;
706
- cloned.outputData = this.outputData;
707
-
708
- return cloned;
758
+ return new CellInput(
759
+ this.previousOutput.clone(),
760
+ this.since,
761
+ this.cellOutput?.clone(),
762
+ this.outputData,
763
+ );
709
764
  }
710
765
  }
711
766
  export const CellInputVec = mol.vector(CellInput);
@@ -741,21 +796,6 @@ export class CellDep extends mol.Entity.Base<CellDepLike, CellDep>() {
741
796
  super();
742
797
  }
743
798
 
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
799
  /**
760
800
  * Creates a CellDep instance from a CellDepLike object.
761
801
  *
@@ -781,6 +821,20 @@ export class CellDep extends mol.Entity.Base<CellDepLike, CellDep>() {
781
821
  depTypeFrom(cellDep.depType),
782
822
  );
783
823
  }
824
+
825
+ /**
826
+ * Clone a CellDep.
827
+ *
828
+ * @returns A cloned CellDep instance.
829
+ *
830
+ * @example
831
+ * ```typescript
832
+ * const cellDep1 = cellDep0.clone();
833
+ * ```
834
+ */
835
+ clone(): CellDep {
836
+ return new CellDep(this.outPoint.clone(), this.depType);
837
+ }
784
838
  }
785
839
  export const CellDepVec = mol.vector(CellDep);
786
840
 
@@ -964,6 +1018,55 @@ export class Transaction extends mol.Entity.Base<
964
1018
  this.witnesses = tx.witnesses;
965
1019
  }
966
1020
 
1021
+ /**
1022
+ * Creates a deep copy of the transaction.
1023
+ * This method creates a new Transaction instance with all nested objects cloned,
1024
+ * ensuring that modifications to the cloned transaction do not affect the original.
1025
+ *
1026
+ * @returns A new Transaction instance that is a deep copy of the current transaction.
1027
+ *
1028
+ * @example
1029
+ * ```typescript
1030
+ * const originalTx = Transaction.from({
1031
+ * version: 0,
1032
+ * inputs: [{ previousOutput: { txHash: "0x...", index: 0 } }],
1033
+ * outputs: [{ capacity: 1000n, lock: lockScript }],
1034
+ * outputsData: ["0x"],
1035
+ * witnesses: ["0x"]
1036
+ * });
1037
+ *
1038
+ * const clonedTx = originalTx.clone();
1039
+ *
1040
+ * // Modifications to clonedTx won't affect originalTx
1041
+ * clonedTx.addOutput({ capacity: 2000n, lock: anotherLockScript });
1042
+ * console.log(originalTx.outputs.length); // Still 1
1043
+ * console.log(clonedTx.outputs.length); // Now 2
1044
+ * ```
1045
+ *
1046
+ * @remarks
1047
+ * The clone operation performs deep copying for:
1048
+ * - Cell dependencies (cellDeps) - each CellDep is cloned
1049
+ * - Inputs - each CellInput is cloned
1050
+ * - Outputs - each CellOutput is cloned
1051
+ *
1052
+ * The following are shallow copied (references to immutable data):
1053
+ * - Header dependencies (headerDeps) - Hex strings are immutable
1054
+ * - Output data (outputsData) - Hex strings are immutable
1055
+ * - Witnesses - Hex strings are immutable
1056
+ * - Version - bigint is immutable
1057
+ */
1058
+ clone(): Transaction {
1059
+ return new Transaction(
1060
+ this.version,
1061
+ this.cellDeps.map((c) => c.clone()),
1062
+ this.headerDeps.map((h) => h),
1063
+ this.inputs.map((i) => i.clone()),
1064
+ this.outputs.map((o) => o.clone()),
1065
+ this.outputsData.map((o) => o),
1066
+ this.witnesses.map((w) => w),
1067
+ );
1068
+ }
1069
+
967
1070
  /**
968
1071
  * Creates a Transaction instance from a TransactionLike object.
969
1072
  *
@@ -1697,8 +1800,8 @@ export class Transaction extends mol.Entity.Base<
1697
1800
  return addedCount;
1698
1801
  }
1699
1802
 
1700
- throw new Error(
1701
- `Insufficient CKB, need ${fixedPointToString(expectedCapacity - accumulated)} extra CKB`,
1803
+ throw new ErrorTransactionInsufficientCapacity(
1804
+ expectedCapacity - accumulated,
1702
1805
  );
1703
1806
  }
1704
1807
 
@@ -1782,8 +1885,9 @@ export class Transaction extends mol.Entity.Base<
1782
1885
  return addedCount;
1783
1886
  }
1784
1887
 
1785
- throw new Error(
1786
- `Insufficient coin, need ${expectedBalance - accumulated} extra coin`,
1888
+ throw new ErrorTransactionInsufficientCoin(
1889
+ expectedBalance - accumulated,
1890
+ type,
1787
1891
  );
1788
1892
  }
1789
1893
 
@@ -1836,12 +1940,54 @@ export class Transaction extends mol.Entity.Base<
1836
1940
  return (numFrom(txSize) * numFrom(feeRate) + numFrom(999)) / numFrom(1000);
1837
1941
  }
1838
1942
 
1943
+ /**
1944
+ * Completes the transaction fee by adding inputs and handling change outputs.
1945
+ * This method automatically calculates the required fee based on the transaction size and fee rate,
1946
+ * adds necessary inputs to cover the fee, and handles change outputs through the provided change function.
1947
+ *
1948
+ * @param from - The signer to complete inputs from and prepare the transaction.
1949
+ * @param change - A function that handles change capacity. It receives the transaction and excess capacity,
1950
+ * and should return the additional capacity needed (0 if change is handled successfully,
1951
+ * positive number if more capacity is needed for change cell creation).
1952
+ * @param expectedFeeRate - The expected fee rate in shannons per 1000 bytes. If not provided,
1953
+ * it will be fetched from the client.
1954
+ * @param filter - Optional filter for selecting cells when adding inputs.
1955
+ * @param options - Optional configuration object.
1956
+ * @param options.feeRateBlockRange - Block range for fee rate calculation when expectedFeeRate is not provided.
1957
+ * @param options.maxFeeRate - Maximum allowed fee rate.
1958
+ * @param options.shouldAddInputs - Whether to add inputs automatically. Defaults to true.
1959
+ * @returns A promise that resolves to a tuple containing:
1960
+ * - The number of inputs added during the process
1961
+ * - A boolean indicating whether change outputs were created (true) or fee was paid without change (false)
1962
+ *
1963
+ * @throws {ErrorTransactionInsufficientCapacity} When there's not enough capacity to cover the fee.
1964
+ * @throws {Error} When the change function doesn't properly handle the available capacity.
1965
+ *
1966
+ * @example
1967
+ * ```typescript
1968
+ * const [addedInputs, hasChange] = await tx.completeFee(
1969
+ * signer,
1970
+ * (tx, capacity) => {
1971
+ * if (capacity >= 61_00000000n) { // Minimum for a change cell
1972
+ * tx.addOutput({ capacity, lock: changeScript });
1973
+ * return 0;
1974
+ * }
1975
+ * return 61_00000000n; // Need more capacity for change cell
1976
+ * },
1977
+ * 1000n // 1000 shannons per 1000 bytes
1978
+ * );
1979
+ * ```
1980
+ */
1839
1981
  async completeFee(
1840
1982
  from: Signer,
1841
1983
  change: (tx: Transaction, capacity: Num) => Promise<NumLike> | NumLike,
1842
1984
  expectedFeeRate?: NumLike,
1843
1985
  filter?: ClientCollectableSearchKeyFilterLike,
1844
- options?: { feeRateBlockRange?: NumLike; maxFeeRate?: NumLike },
1986
+ options?: {
1987
+ feeRateBlockRange?: NumLike;
1988
+ maxFeeRate?: NumLike;
1989
+ shouldAddInputs?: boolean;
1990
+ },
1845
1991
  ): Promise<[number, boolean]> {
1846
1992
  const feeRate =
1847
1993
  expectedFeeRate ??
@@ -1856,6 +2002,15 @@ export class Transaction extends mol.Entity.Base<
1856
2002
  while (true) {
1857
2003
  const tx = this.clone();
1858
2004
  const collected = await (async () => {
2005
+ if (!(options?.shouldAddInputs ?? true)) {
2006
+ const fee =
2007
+ (await tx.getFee(from.client)) - leastFee - leastExtraCapacity;
2008
+ if (fee < Zero) {
2009
+ throw new ErrorTransactionInsufficientCapacity(-fee);
2010
+ }
2011
+ return 0;
2012
+ }
2013
+
1859
2014
  try {
1860
2015
  return await tx.completeInputsByCapacity(
1861
2016
  from,
@@ -1863,8 +2018,13 @@ export class Transaction extends mol.Entity.Base<
1863
2018
  filter,
1864
2019
  );
1865
2020
  } catch (err) {
1866
- if (leastExtraCapacity !== Zero) {
1867
- throw new Error("Not enough capacity for the change cell");
2021
+ if (
2022
+ err instanceof ErrorTransactionInsufficientCapacity &&
2023
+ leastExtraCapacity !== Zero
2024
+ ) {
2025
+ throw new ErrorTransactionInsufficientCapacity(err.amount, {
2026
+ isForChange: true,
2027
+ });
1868
2028
  }
1869
2029
 
1870
2030
  throw err;
@@ -1918,6 +2078,11 @@ export class Transaction extends mol.Entity.Base<
1918
2078
  change: ScriptLike,
1919
2079
  feeRate?: NumLike,
1920
2080
  filter?: ClientCollectableSearchKeyFilterLike,
2081
+ options?: {
2082
+ feeRateBlockRange?: NumLike;
2083
+ maxFeeRate?: NumLike;
2084
+ shouldAddInputs?: boolean;
2085
+ },
1921
2086
  ): Promise<[number, boolean]> {
1922
2087
  const script = Script.from(change);
1923
2088
 
@@ -1935,6 +2100,7 @@ export class Transaction extends mol.Entity.Base<
1935
2100
  },
1936
2101
  feeRate,
1937
2102
  filter,
2103
+ options,
1938
2104
  );
1939
2105
  }
1940
2106
 
@@ -1942,10 +2108,15 @@ export class Transaction extends mol.Entity.Base<
1942
2108
  from: Signer,
1943
2109
  feeRate?: NumLike,
1944
2110
  filter?: ClientCollectableSearchKeyFilterLike,
2111
+ options?: {
2112
+ feeRateBlockRange?: NumLike;
2113
+ maxFeeRate?: NumLike;
2114
+ shouldAddInputs?: boolean;
2115
+ },
1945
2116
  ): Promise<[number, boolean]> {
1946
2117
  const { script } = await from.getRecommendedAddressObj();
1947
2118
 
1948
- return this.completeFeeChangeToLock(from, script, feeRate, filter);
2119
+ return this.completeFeeChangeToLock(from, script, feeRate, filter, options);
1949
2120
  }
1950
2121
 
1951
2122
  completeFeeChangeToOutput(
@@ -1953,6 +2124,11 @@ export class Transaction extends mol.Entity.Base<
1953
2124
  index: NumLike,
1954
2125
  feeRate?: NumLike,
1955
2126
  filter?: ClientCollectableSearchKeyFilterLike,
2127
+ options?: {
2128
+ feeRateBlockRange?: NumLike;
2129
+ maxFeeRate?: NumLike;
2130
+ shouldAddInputs?: boolean;
2131
+ },
1956
2132
  ): Promise<[number, boolean]> {
1957
2133
  const change = Number(numFrom(index));
1958
2134
  if (!this.outputs[change]) {
@@ -1966,6 +2142,7 @@ export class Transaction extends mol.Entity.Base<
1966
2142
  },
1967
2143
  feeRate,
1968
2144
  filter,
2145
+ options,
1969
2146
  );
1970
2147
  }
1971
2148
  }
@@ -0,0 +1,36 @@
1
+ import { fixedPointToString } from "../fixedPoint/index.js";
2
+ import { Num, numFrom, NumLike } from "../num/index.js";
3
+ import { Script, ScriptLike } from "./script.js";
4
+
5
+ export class ErrorTransactionInsufficientCapacity extends Error {
6
+ public readonly amount: Num;
7
+ public readonly isForChange: boolean;
8
+
9
+ constructor(
10
+ amountLike: NumLike,
11
+ reason?: {
12
+ isForChange?: boolean;
13
+ },
14
+ ) {
15
+ const amount = numFrom(amountLike);
16
+ const isForChange = reason?.isForChange ?? false;
17
+ super(
18
+ `Insufficient CKB, need ${fixedPointToString(amount)} extra CKB${isForChange ? " for the change cell" : ""}`,
19
+ );
20
+ this.amount = amount;
21
+ this.isForChange = isForChange;
22
+ }
23
+ }
24
+
25
+ export class ErrorTransactionInsufficientCoin extends Error {
26
+ public readonly amount: Num;
27
+ public readonly type: Script;
28
+
29
+ constructor(amountLike: NumLike, typeLike: ScriptLike) {
30
+ const amount = numFrom(amountLike);
31
+ const type = Script.from(typeLike);
32
+ super(`Insufficient coin, need ${amount} extra coin`);
33
+ this.amount = amount;
34
+ this.type = type;
35
+ }
36
+ }
@@ -6,7 +6,7 @@ import {
6
6
  TransactionLike,
7
7
  } from "../../ckb/index.js";
8
8
  import { HexLike } from "../../hex/index.js";
9
- import { NumLike } from "../../num/index.js";
9
+ import { numFrom, NumLike } from "../../num/index.js";
10
10
  import { ClientCollectableSearchKeyLike } from "../clientTypes.advanced.js";
11
11
  import {
12
12
  ClientBlock,
@@ -16,6 +16,7 @@ import {
16
16
  ClientTransactionResponse,
17
17
  ClientTransactionResponseLike,
18
18
  } from "../clientTypes.js";
19
+ import { DEFAULT_CONFIRMED_BLOCK_TIME } from "./memory.advanced.js";
19
20
 
20
21
  /**
21
22
  * @public
@@ -185,4 +186,16 @@ export abstract class ClientCache {
185
186
  async getBlockByNumber(_number: NumLike): Promise<ClientBlock | undefined> {
186
187
  return;
187
188
  }
189
+
190
+ /**
191
+ * Checks if a block header is considered confirmed.
192
+ * The default implementation compares the header's timestamp against the current time
193
+ * and a configured confirmation time. Override this method for custom confirmation logic.
194
+ * @param header
195
+ */
196
+ hasHeaderConfirmed(header: ClientBlockHeader): boolean {
197
+ return (
198
+ numFrom(Date.now()) - header.timestamp >= DEFAULT_CONFIRMED_BLOCK_TIME
199
+ );
200
+ }
188
201
  }
@@ -8,6 +8,8 @@ import {
8
8
  } from "../clientTypes.advanced.js";
9
9
  import { ClientIndexerSearchKey } from "../clientTypes.js";
10
10
 
11
+ export const DEFAULT_CONFIRMED_BLOCK_TIME = numFrom(1000 * 10 * 50); // 50 blocks * 10s
12
+
11
13
  // [isLive, Cell | OutPoint]
12
14
  export type CellRecord =
13
15
  | [
@@ -142,49 +144,74 @@ export function filterCell(
142
144
  return true;
143
145
  }
144
146
 
147
+ /**
148
+ * A Least Recently Used (LRU) cache implemented using a Map.
149
+ *
150
+ * This class extends the built-in Map to provide an LRU cache with a fixed capacity.
151
+ * When the cache is full, the least recently used entry is automatically evicted.
152
+ *
153
+ * @template K The type of the keys in the cache.
154
+ * @template V The type of the values in the cache.
155
+ */
145
156
  export class MapLru<K, V> extends Map<K, V> {
146
- private readonly lru: K[] = [];
147
-
157
+ /**
158
+ * Constructs a new MapLru instance.
159
+ *
160
+ * @param capacity The maximum number of entries the cache can hold. Must be a positive integer.
161
+ * @throws {Error} If the capacity is not a positive integer.
162
+ */
148
163
  constructor(private readonly capacity: number) {
149
164
  super();
150
- }
151
-
152
- get(key: K) {
153
- const val = super.get(key);
154
- if (val === undefined) {
155
- return;
165
+ if (!Number.isInteger(capacity) || capacity < 1) {
166
+ throw new Error("Capacity must be a positive integer");
156
167
  }
168
+ }
157
169
 
158
- const index = this.lru.indexOf(key);
159
- if (index !== -1) {
160
- this.lru.splice(index, 1);
161
- }
162
- this.lru.push(key);
163
- if (this.lru.length > this.capacity) {
164
- this.delete(this.lru[0]);
165
- this.lru.shift();
170
+ /**
171
+ * Retrieves a value from the cache.
172
+ *
173
+ * If the key is present in the cache, the value is moved to the most-recently-used position.
174
+ *
175
+ * @param key The key of the value to retrieve.
176
+ * @returns The value associated with the key, or undefined if the key is not present.
177
+ */
178
+ override get(key: K): V | undefined {
179
+ // Check if the key exists. If not, return undefined.
180
+ if (!super.has(key)) {
181
+ return undefined;
166
182
  }
167
183
 
168
- return val;
169
- }
170
-
171
- set(key: K, value: V) {
172
- this.get(key);
184
+ const value = super.get(key) as V;
173
185
 
186
+ // Move to most-recently-used position
187
+ super.delete(key);
174
188
  super.set(key, value);
175
- return this;
189
+
190
+ return value;
176
191
  }
177
192
 
178
- delete(key: K): boolean {
179
- if (!super.delete(key)) {
180
- return false;
181
- }
193
+ /**
194
+ * Inserts a new value into the cache, or updates an existing value.
195
+ *
196
+ * If the key is already present in the cache, it is first deleted so that the re-insertion
197
+ * moves it to the most-recently-used position.
198
+ * If the cache is over capacity after the insertion, the least recently used entry is evicted.
199
+ *
200
+ * @param key The key of the value to insert or update.
201
+ * @param value The value to associate with the key.
202
+ * @returns This MapLru instance.
203
+ */
204
+ override set(key: K, value: V): this {
205
+ // Delete and re-insert to move key to the end (most-recently-used)
206
+ super.delete(key);
207
+ super.set(key, value);
182
208
 
183
- const index = this.lru.indexOf(key);
184
- if (index !== -1) {
185
- this.lru.splice(index, 1);
209
+ // Evict oldest if over capacity
210
+ if (super.size > this.capacity) {
211
+ const oldestKey = super.keys().next().value!;
212
+ super.delete(oldestKey);
186
213
  }
187
214
 
188
- return true;
215
+ return this;
189
216
  }
190
217
  }
@@ -11,7 +11,12 @@ import {
11
11
  ClientTransactionResponseLike,
12
12
  } from "../clientTypes.js";
13
13
  import { ClientCache } from "./cache.js";
14
- import { CellRecord, filterCell, MapLru } from "./memory.advanced.js";
14
+ import {
15
+ CellRecord,
16
+ DEFAULT_CONFIRMED_BLOCK_TIME,
17
+ filterCell,
18
+ MapLru,
19
+ } from "./memory.advanced.js";
15
20
 
16
21
  export class ClientCacheMemory extends ClientCache {
17
22
  /**
@@ -37,10 +42,18 @@ export class ClientCacheMemory extends ClientCache {
37
42
  Pick<ClientBlock, "header"> | ClientBlock
38
43
  >;
39
44
 
45
+ /**
46
+ * @param maxCells - Maximum number of cells to store in the cache. Defaults to 512.
47
+ * @param maxTxs - Maximum number of transactions to store in the cache. Defaults to 256.
48
+ * @param maxBlocks - Maximum number of blocks to store in the cache. Defaults to 128.
49
+ * @param confirmedBlockTime - Time in milliseconds after which a block is considered confirmed.
50
+ * Defaults to DEFAULT_CONFIRMED_BLOCK_TIME (50 blocks * 10s).
51
+ */
40
52
  constructor(
41
53
  private readonly maxCells = 512,
42
54
  private readonly maxTxs = 256,
43
55
  private readonly maxBlocks = 128,
56
+ private readonly confirmedBlockTime = DEFAULT_CONFIRMED_BLOCK_TIME,
44
57
  ) {
45
58
  super();
46
59
 
@@ -216,4 +229,8 @@ export class ClientCacheMemory extends ClientCache {
216
229
  }
217
230
  return this.getBlockByHash(hash);
218
231
  }
232
+
233
+ hasHeaderConfirmed(header: ClientBlockHeader): boolean {
234
+ return numFrom(Date.now()) - header.timestamp >= this.confirmedBlockTime;
235
+ }
219
236
  }
@@ -14,7 +14,6 @@ import { reduceAsync, sleep } from "../utils/index.js";
14
14
  import { ClientCache } from "./cache/index.js";
15
15
  import { ClientCacheMemory } from "./cache/memory.js";
16
16
  import {
17
- CONFIRMED_BLOCK_TIME,
18
17
  ClientCollectableSearchKeyLike,
19
18
  DEFAULT_MAX_FEE_RATE,
20
19
  DEFAULT_MIN_FEE_RATE,
@@ -38,10 +37,6 @@ import {
38
37
  } from "./clientTypes.js";
39
38
  import { KnownScript } from "./knownScript.js";
40
39
 
41
- function hasHeaderConfirmed(header: ClientBlockHeader): boolean {
42
- return numFrom(Date.now()) - header.timestamp >= CONFIRMED_BLOCK_TIME;
43
- }
44
-
45
40
  /**
46
41
  * @public
47
42
  */
@@ -112,7 +107,7 @@ export abstract class Client {
112
107
  verbosity,
113
108
  withCycles,
114
109
  );
115
- if (res && hasHeaderConfirmed(res.header)) {
110
+ if (res && this.cache.hasHeaderConfirmed(res.header)) {
116
111
  await this.cache.recordBlocks(res);
117
112
  }
118
113
  return res;
@@ -132,7 +127,7 @@ export abstract class Client {
132
127
  verbosity,
133
128
  withCycles,
134
129
  );
135
- if (res && hasHeaderConfirmed(res.header)) {
130
+ if (res && this.cache.hasHeaderConfirmed(res.header)) {
136
131
  await this.cache.recordBlocks(res);
137
132
  }
138
133
  return res;
@@ -147,7 +142,7 @@ export abstract class Client {
147
142
  }
148
143
 
149
144
  const res = await this.getHeaderByNumberNoCache(blockNumber, verbosity);
150
- if (res && hasHeaderConfirmed(res)) {
145
+ if (res && this.cache.hasHeaderConfirmed(res)) {
151
146
  await this.cache.recordHeaders(res);
152
147
  }
153
148
  return res;
@@ -162,7 +157,7 @@ export abstract class Client {
162
157
  }
163
158
 
164
159
  const res = await this.getHeaderByHashNoCache(blockHash, verbosity);
165
- if (res && hasHeaderConfirmed(res)) {
160
+ if (res && this.cache.hasHeaderConfirmed(res)) {
166
161
  await this.cache.recordHeaders(res);
167
162
  }
168
163
  return res;
@@ -636,7 +631,7 @@ export abstract class Client {
636
631
  const tx = await this.cache.getTransactionResponse(txHash);
637
632
  if (tx?.blockHash) {
638
633
  const header = await this.getHeaderByHash(tx.blockHash);
639
- if (header && hasHeaderConfirmed(header)) {
634
+ if (header && this.cache.hasHeaderConfirmed(header)) {
640
635
  return {
641
636
  transaction: tx,
642
637
  header,