@ickb/sdk 9000.0.1 → 9001.0.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 (138) hide show
  1. package/README.md +14 -11
  2. package/dist/constants.d.ts +0 -4
  3. package/dist/constants.d.ts.map +1 -1
  4. package/dist/constants.js +6 -24
  5. package/dist/constants.js.map +1 -1
  6. package/dist/conversion/estimate.d.ts +0 -6
  7. package/dist/conversion/estimate.d.ts.map +1 -1
  8. package/dist/conversion/estimate.js +1 -1
  9. package/dist/conversion/estimate.js.map +1 -1
  10. package/dist/conversion/maturity.d.ts +1 -1
  11. package/dist/conversion/maturity.js +13 -12
  12. package/dist/conversion/maturity.js.map +1 -1
  13. package/dist/conversion/plans.d.ts.map +1 -1
  14. package/dist/conversion/plans.js +6 -6
  15. package/dist/conversion/plans.js.map +1 -1
  16. package/dist/conversion/projection.d.ts +5 -6
  17. package/dist/conversion/projection.d.ts.map +1 -1
  18. package/dist/conversion/projection.js +11 -8
  19. package/dist/conversion/projection.js.map +1 -1
  20. package/dist/conversion/types.d.ts +29 -17
  21. package/dist/conversion/types.d.ts.map +1 -1
  22. package/dist/conversion/withdrawal_ring.d.ts +1 -3
  23. package/dist/conversion/withdrawal_ring.d.ts.map +1 -1
  24. package/dist/conversion/withdrawal_ring.js +4 -4
  25. package/dist/conversion/withdrawal_ring.js.map +1 -1
  26. package/dist/dao.d.ts +19 -5
  27. package/dist/dao.d.ts.map +1 -1
  28. package/dist/dao.js +24 -12
  29. package/dist/dao.js.map +1 -1
  30. package/dist/index.d.ts +9 -11
  31. package/dist/index.d.ts.map +1 -1
  32. package/dist/index.js +2 -2
  33. package/dist/index.js.map +1 -1
  34. package/dist/logic.d.ts +10 -14
  35. package/dist/logic.d.ts.map +1 -1
  36. package/dist/logic.js +28 -39
  37. package/dist/logic.js.map +1 -1
  38. package/dist/order/cells.d.ts +10 -49
  39. package/dist/order/cells.d.ts.map +1 -1
  40. package/dist/order/cells.js +28 -146
  41. package/dist/order/cells.js.map +1 -1
  42. package/dist/order/conversion.d.ts +3 -9
  43. package/dist/order/conversion.d.ts.map +1 -1
  44. package/dist/order/conversion.js +8 -11
  45. package/dist/order/conversion.js.map +1 -1
  46. package/dist/order/fill.d.ts +2 -2
  47. package/dist/order/fill.js +3 -3
  48. package/dist/order/fill.js.map +1 -1
  49. package/dist/order/info.d.ts.map +1 -1
  50. package/dist/order/info.js +3 -0
  51. package/dist/order/info.js.map +1 -1
  52. package/dist/order/matcher.d.ts +1 -1
  53. package/dist/order/matcher.d.ts.map +1 -1
  54. package/dist/order/matcher.js +3 -2
  55. package/dist/order/matcher.js.map +1 -1
  56. package/dist/order/order.d.ts +6 -2
  57. package/dist/order/order.d.ts.map +1 -1
  58. package/dist/order/order.js +32 -37
  59. package/dist/order/order.js.map +1 -1
  60. package/dist/order/order_data.d.ts.map +1 -1
  61. package/dist/order/order_data.js +3 -0
  62. package/dist/order/order_data.js.map +1 -1
  63. package/dist/order/ratio.d.ts +0 -2
  64. package/dist/order/ratio.d.ts.map +1 -1
  65. package/dist/order/ratio.js +3 -11
  66. package/dist/order/ratio.js.map +1 -1
  67. package/dist/order/relative.d.ts.map +1 -1
  68. package/dist/order/relative.js +3 -0
  69. package/dist/order/relative.js.map +1 -1
  70. package/dist/owned_owner.d.ts +10 -15
  71. package/dist/owned_owner.d.ts.map +1 -1
  72. package/dist/owned_owner.js +27 -47
  73. package/dist/owned_owner.js.map +1 -1
  74. package/dist/sdk.d.ts +74 -31
  75. package/dist/sdk.d.ts.map +1 -1
  76. package/dist/sdk.js +81 -59
  77. package/dist/sdk.js.map +1 -1
  78. package/dist/send/sign_and_send_transaction.d.ts +32 -17
  79. package/dist/send/sign_and_send_transaction.d.ts.map +1 -1
  80. package/dist/send/sign_and_send_transaction.js +128 -28
  81. package/dist/send/sign_and_send_transaction.js.map +1 -1
  82. package/dist/send/wait_transaction.d.ts +7 -6
  83. package/dist/send/wait_transaction.d.ts.map +1 -1
  84. package/dist/send/wait_transaction.js +29 -61
  85. package/dist/send/wait_transaction.js.map +1 -1
  86. package/dist/udt.d.ts +20 -35
  87. package/dist/udt.d.ts.map +1 -1
  88. package/dist/udt.js +39 -103
  89. package/dist/udt.js.map +1 -1
  90. package/dist/utils/chain.d.ts +0 -35
  91. package/dist/utils/chain.d.ts.map +1 -1
  92. package/dist/utils/chain.js +1 -19
  93. package/dist/utils/chain.js.map +1 -1
  94. package/dist/utils/codec.d.ts.map +1 -1
  95. package/dist/utils/codec.js +3 -0
  96. package/dist/utils/codec.js.map +1 -1
  97. package/dist/utils/utils.d.ts +19 -46
  98. package/dist/utils/utils.d.ts.map +1 -1
  99. package/dist/utils/utils.js +53 -74
  100. package/dist/utils/utils.js.map +1 -1
  101. package/dist/utils/verified_client.d.ts +19 -0
  102. package/dist/utils/verified_client.d.ts.map +1 -0
  103. package/dist/utils/verified_client.js +92 -0
  104. package/dist/utils/verified_client.js.map +1 -0
  105. package/package.json +2 -2
  106. package/src/constants.ts +6 -25
  107. package/src/conversion/estimate.ts +1 -3
  108. package/src/conversion/maturity.ts +13 -14
  109. package/src/conversion/plans.ts +6 -6
  110. package/src/conversion/projection.ts +15 -7
  111. package/src/conversion/types.ts +32 -17
  112. package/src/conversion/withdrawal_ring.ts +4 -8
  113. package/src/dao.ts +38 -14
  114. package/src/index.ts +8 -8
  115. package/src/logic.ts +40 -52
  116. package/src/order/cells.ts +37 -174
  117. package/src/order/conversion.ts +8 -19
  118. package/src/order/fill.ts +3 -3
  119. package/src/order/info.ts +3 -0
  120. package/src/order/matcher.ts +3 -3
  121. package/src/order/order.ts +38 -52
  122. package/src/order/order_data.ts +3 -0
  123. package/src/order/ratio.ts +3 -20
  124. package/src/order/relative.ts +3 -0
  125. package/src/owned_owner.ts +47 -77
  126. package/src/sdk.ts +95 -108
  127. package/src/send/sign_and_send_transaction.ts +164 -28
  128. package/src/send/wait_transaction.ts +44 -88
  129. package/src/udt.ts +56 -124
  130. package/src/utils/chain.ts +0 -41
  131. package/src/utils/codec.ts +4 -0
  132. package/src/utils/utils.ts +76 -105
  133. package/src/utils/verified_client.ts +136 -0
  134. package/dist/utils/transaction_header.d.ts +0 -17
  135. package/dist/utils/transaction_header.d.ts.map +0 -1
  136. package/dist/utils/transaction_header.js +0 -44
  137. package/dist/utils/transaction_header.js.map +0 -1
  138. package/src/utils/transaction_header.ts +0 -66
@@ -1,7 +1,7 @@
1
+ import { ccc } from "@ckb-ccc/core";
1
2
  import { DAO_OUTPUT_LIMIT } from "../dao.ts";
2
3
  import { readyDeposits, type IckbDepositCell } from "../logic.ts";
3
- import { ICKB_DEPOSIT_CAP, convert } from "../udt.ts";
4
- import { maxBigInt } from "../utils/utils.ts";
4
+ import { ICKB_DEPOSIT_CAP, ckbFromUdt } from "../udt.ts";
5
5
  import {
6
6
  DEFAULT_ORDER_FEE,
7
7
  DEFAULT_ORDER_FEE_BASE,
@@ -37,7 +37,7 @@ export function ckbToIckbConversionPlans(
37
37
  options: ConversionTransactionOptions,
38
38
  ): CkbToIckbConversionPlan[] {
39
39
  const { amount, context } = options;
40
- const depositCapacity = convert(false, ICKB_DEPOSIT_CAP, context.system.exchangeRatio);
40
+ const depositCapacity = ckbFromUdt(ICKB_DEPOSIT_CAP, context.system.exchangeRatio);
41
41
  const depositQuotient = depositCapacity === 0n ? 0n : amount / depositCapacity;
42
42
  const maxDeposits =
43
43
  depositQuotient > BigInt(MAX_PLANNED_DEPOSITS)
@@ -98,7 +98,7 @@ function ckbToIckbConversionPlan(
98
98
  if (estimate?.maturity === undefined) {
99
99
  return undefined;
100
100
  }
101
- estimatedMaturity = maxBigInt(estimatedMaturity, estimate.maturity);
101
+ estimatedMaturity = ccc.numMax(estimatedMaturity, estimate.maturity);
102
102
  order = { amounts, estimate };
103
103
  }
104
104
 
@@ -118,7 +118,7 @@ function ickbToCkbConversionPlan(
118
118
  let estimatedMaturity = context.estimatedMaturity;
119
119
  // Selected deposits are ready, so their sampled claim is their date.
120
120
  for (const deposit of selectedDeposits) {
121
- estimatedMaturity = maxBigInt(
121
+ estimatedMaturity = ccc.numMax(
122
122
  estimatedMaturity,
123
123
  deposit.claimEpoch.toUnix(context.system.tip),
124
124
  );
@@ -132,7 +132,7 @@ function ickbToCkbConversionPlan(
132
132
  if (estimate === undefined) {
133
133
  return undefined;
134
134
  }
135
- estimatedMaturity = maxBigInt(estimatedMaturity, estimate.maturity);
135
+ estimatedMaturity = ccc.numMax(estimatedMaturity, estimate.maturity);
136
136
  order = { amounts, estimate };
137
137
  }
138
138
 
@@ -1,8 +1,8 @@
1
1
  import { ccc } from "@ckb-ccc/core";
2
2
  import { DAO_HEADER_INDEX_LIMIT } from "../dao.ts";
3
3
  import type { OrderGroup } from "../order/cells.ts";
4
+ import { isRefused, isStale } from "../order/fill.ts";
4
5
  import type { WithdrawalGroup } from "../owned_owner.ts";
5
- import { maxBigInt } from "../utils/utils.ts";
6
6
  import { maturity } from "./maturity.ts";
7
7
  import type {
8
8
  AccountAvailabilityProjection,
@@ -13,21 +13,29 @@ import type {
13
13
 
14
14
  /**
15
15
  * Builds the conversion planner context from account state and the caller's own orders,
16
- * split by what it will melt: `available` orders are collected and budgeted as account
17
- * value, `pending` ones stay on the book.
16
+ * split by what it will melt: fulfilled orders, buys the bot's whole-fill rule refuses,
17
+ * and orders past the age cutoff are collected and budgeted as account value; the rest
18
+ * stay on the book. DAO growth can make a waiting sell profitable later, so a sell waits
19
+ * for the age cutoff even when the bot would not fill it today.
18
20
  */
19
21
  export function projectConversionTransactionContext(
20
22
  system: SystemState,
21
23
  account: AccountState,
22
- orders: { available: OrderGroup[]; pending: OrderGroup[] },
24
+ orders: readonly OrderGroup[],
23
25
  ): ConversionTransactionContextProjection {
24
- const projection = projectAccountAvailability(account, orders);
25
- const estimatedMaturity = [
26
+ const collectable = (group: OrderGroup): boolean =>
27
+ group.order.isFulfilled() || isRefused(group, system) || isStale(group, system.tip);
28
+ const projection = projectAccountAvailability(account, {
29
+ available: orders.filter(collectable),
30
+ pending: orders.filter((group) => !collectable(group)),
31
+ });
32
+ const estimatedMaturity = ccc.numMax(
33
+ system.tip.timestamp,
26
34
  ...projection.pendingWithdrawals.map((group) =>
27
35
  group.owned.maturity.toUnix(system.tip),
28
36
  ),
29
37
  ...projection.pendingOrders.map((group) => maturity(group.order, system)),
30
- ].reduce(maxBigInt, system.tip.timestamp);
38
+ );
31
39
 
32
40
  return {
33
41
  projection,
@@ -1,12 +1,12 @@
1
1
  import type { ccc } from "@ckb-ccc/core";
2
- import type { LockUpPolicy } from "../dao.ts";
2
+ import type { BroadcastDeadline, LockUpPolicy } from "../dao.ts";
3
3
  import type { IckbDepositCell, LogicManager, ReceiptCell } from "../logic.ts";
4
4
  import type { OrderCell, OrderGroup } from "../order/cells.ts";
5
5
  import type { Info } from "../order/info.ts";
6
6
  import type { OrderManager } from "../order/order.ts";
7
7
  import type { Ratio } from "../order/ratio.ts";
8
8
  import type { OwnedOwnerManager, WithdrawalGroup } from "../owned_owner.ts";
9
- import type { IckbUdt } from "../udt.ts";
9
+ import type { IckbUdt, ProtocolCell } from "../udt.ts";
10
10
  import type { ValueComponents } from "../utils/utils.ts";
11
11
 
12
12
  /**
@@ -88,13 +88,8 @@ export interface ConversionNotice {
88
88
  incentiveCkb: bigint;
89
89
  }
90
90
 
91
- /**
92
- * High-level conversion composition used by a built transaction.
93
- */
94
- export interface ConversionMetadata {
95
- /** Composition category selected for the built conversion. */
96
- kind: "direct" | "order" | "direct-plus-order" | "collect-only";
97
- }
91
+ /** Composition category selected for a built conversion. */
92
+ export type ConversionKind = "direct" | "order" | "direct-plus-order" | "collect-only";
98
93
 
99
94
  /**
100
95
  * Result of attempting to build a conversion transaction.
@@ -107,7 +102,7 @@ export type ConversionTransactionResult =
107
102
  /** Estimated maturity timestamp for the conversion result. */
108
103
  estimatedMaturity: bigint;
109
104
  /** Composition of the selected conversion path. */
110
- conversion: ConversionMetadata;
105
+ conversionKind: ConversionKind;
111
106
  /** Optional notice about the selected path. */
112
107
  conversionNotice?: ConversionNotice;
113
108
  /**
@@ -119,19 +114,25 @@ export type ConversionTransactionResult =
119
114
  * The epoch the transaction must be broadcast before for its withdrawal requests to
120
115
  * claim at their sampled dates (`system.lockUp`); absent when it requests none.
121
116
  */
122
- broadcastBefore?: ccc.Epoch;
117
+ broadcastBefore?: BroadcastDeadline;
123
118
  }
124
119
  | {
125
120
  ok: false;
126
- /** Machine-readable failure reason. */
127
- reason: ConversionTransactionFailureReason;
121
+ reason: "amount-too-small";
128
122
  /** Best available maturity estimate from the input context. */
129
123
  estimatedMaturity: bigint;
130
124
  /**
131
- * For `amount-too-small`: the smallest amount this direction accepts at the current fee
132
- * rate, in the request's unit (CKB shannons or iCKB), so a caller can name it.
125
+ * The smallest amount this direction accepts at the current fee rate, in the
126
+ * request's unit (CKB shannons or iCKB), so a caller can name it.
133
127
  */
134
- minimum?: bigint;
128
+ minimum: bigint;
129
+ }
130
+ | {
131
+ ok: false;
132
+ /** Machine-readable failure reason. */
133
+ reason: Exclude<ConversionTransactionFailureReason, "amount-too-small">;
134
+ /** Best available maturity estimate from the input context. */
135
+ estimatedMaturity: bigint;
135
136
  };
136
137
 
137
138
  /**
@@ -149,6 +150,20 @@ export interface CompleteIckbTransactionOptions {
149
150
 
150
151
  /** The signer's known liquid cells, plain CKB and iCKB, from the account state already read. */
151
152
  cells: ccc.Cell[];
153
+
154
+ /**
155
+ * The receipts and iCKB deposits the transaction may spend, valued by the state read;
156
+ * any superset works, since only the spent ones are summed.
157
+ */
158
+ protocolCells: readonly ProtocolCell[];
159
+
160
+ /**
161
+ * The header the plan was priced at, the state read's `system.tip`, named as a header
162
+ * dep: the chain refuses a transaction whose deps are off the main chain, so a header a
163
+ * node invented, whose DAO rate set the exchange ratio, cannot price an accepted
164
+ * transaction.
165
+ */
166
+ tip: ccc.ClientBlockHeader;
152
167
  }
153
168
 
154
169
  /**
@@ -244,7 +259,7 @@ export interface ConversionTransactionContextProjection {
244
259
  export interface SystemState {
245
260
  /** The fee rate for transactions. */
246
261
  feeRate: ccc.Num;
247
- /** Sampled tip used for this L1 scan. It may be stale after scans complete. */
262
+ /** The sampled tip's parent, this L1 scan's anchor. It may be stale after scans complete. */
248
263
  tip: ccc.ClientBlockHeader;
249
264
  /** The exchange ratio between CKB and UDT. */
250
265
  exchangeRatio: Ratio;
@@ -1,5 +1,5 @@
1
- import type { ccc } from "@ckb-ccc/core";
2
- import { DAO_CYCLE_EPOCHS, DAO_OUTPUT_LIMIT } from "../dao.ts";
1
+ import { ccc } from "@ckb-ccc/core";
2
+ import { DAO_OUTPUT_LIMIT } from "../dao.ts";
3
3
  import type { IckbDepositCell } from "../logic.ts";
4
4
  import { ICKB_DEPOSIT_CAP } from "../udt.ts";
5
5
 
@@ -7,9 +7,6 @@ import { ICKB_DEPOSIT_CAP } from "../udt.ts";
7
7
  * Ring segment of pool deposits grouped by maturity around the DAO cycle.
8
8
  */
9
9
  export interface RingSegment {
10
- /** Segment index in the ring. */
11
- index: number;
12
-
13
10
  /** Deposits assigned to this segment. */
14
11
  deposits: IckbDepositCell[];
15
12
 
@@ -29,8 +26,7 @@ export function ringSegments(poolDeposits: readonly IckbDepositCell[]): RingSegm
29
26
  while (BigInt(segmentCount) * 2n * ICKB_DEPOSIT_CAP < totalUdt) {
30
27
  segmentCount *= 2;
31
28
  }
32
- const segments = Array.from({ length: segmentCount }, (_, index): RingSegment => ({
33
- index,
29
+ const segments = Array.from({ length: segmentCount }, (): RingSegment => ({
34
30
  deposits: [],
35
31
  udtValue: 0n,
36
32
  }));
@@ -53,7 +49,7 @@ export function ringSegmentIndex(epoch: ccc.Epoch, segmentCount: number): number
53
49
  throw new Error("Epoch denominator must be positive");
54
50
  }
55
51
  const scaled = epoch.integer * denominator + epoch.numerator;
56
- const ring = DAO_CYCLE_EPOCHS * denominator;
52
+ const ring = ccc.Epoch.OneNervosDaoCycle.integer * denominator;
57
53
  const wrapped = ((scaled % ring) + ring) % ring;
58
54
  return Number((wrapped * BigInt(segmentCount)) / ring);
59
55
  }
package/src/dao.ts CHANGED
@@ -1,5 +1,4 @@
1
1
  import { ccc } from "@ckb-ccc/core";
2
- import { CheckedUint64LE } from "./utils/codec.ts";
3
2
 
4
3
  /**
5
4
  * Maximum outputs accepted by deployed dao.c, not a general CKB consensus cap.
@@ -15,9 +14,6 @@ export const DAO_OUTPUT_LIMIT = 64;
15
14
  */
16
15
  export const DAO_HEADER_INDEX_LIMIT = 256;
17
16
 
18
- /** One DAO cycle, the lock-up a deposit renews by when it is not withdrawn. */
19
- export const DAO_CYCLE_EPOCHS = 180n;
20
-
21
17
  /**
22
18
  * Error thrown when a transaction exceeds the deployed DAO script's output limit.
23
19
  */
@@ -76,15 +72,13 @@ export function assertDaoOutputLimit(
76
72
  }
77
73
  }
78
74
 
79
- /** The canonical DAO deposit data payload. */
80
- export function depositData(): ccc.Hex {
81
- return ccc.hexFrom(CheckedUint64LE.encode(0n));
82
- }
75
+ /** The canonical DAO deposit data payload: a zero Uint64, eight zero bytes. */
76
+ export const DAO_DEPOSIT_DATA: ccc.Hex = "0x0000000000000000";
83
77
 
84
78
  /** Whether the cell is a DAO deposit of the given DAO script. */
85
79
  export function isDaoDeposit(cell: ccc.CellAny, daoScript: ccc.Script): boolean {
86
80
  return (
87
- cell.outputData === depositData() && cell.cellOutput.type?.eq(daoScript) === true
81
+ cell.outputData === DAO_DEPOSIT_DATA && cell.cellOutput.type?.eq(daoScript) === true
88
82
  );
89
83
  }
90
84
 
@@ -97,7 +91,7 @@ export function isDaoWithdrawalRequest(
97
91
  daoScript: ccc.Script,
98
92
  ): boolean {
99
93
  return (
100
- cell.outputData !== depositData() && cell.cellOutput.type?.eq(daoScript) === true
94
+ cell.outputData !== DAO_DEPOSIT_DATA && cell.cellOutput.type?.eq(daoScript) === true
101
95
  );
102
96
  }
103
97
 
@@ -136,6 +130,25 @@ export const WALLET_LOCK_UP: LockUpPolicy = {
136
130
  broadcastReserve: ccc.Epoch.from([0n, 3n, 8n]),
137
131
  };
138
132
 
133
+ /**
134
+ * How far a tip may trail the local clock under a policy: the selection margin less the
135
+ * broadcast reserve, the slack a stale tip would eat before a request commits after its
136
+ * claim. Five minutes for the bot, thirty for a wallet.
137
+ */
138
+ export function tipMaxAgeMs(policy: LockUpPolicy): number {
139
+ // The span from the reserve to the margin, on CCC's nominal four-hour epoch.
140
+ return Number(
141
+ policy.minLockUp.toUnix({ epoch: policy.broadcastReserve, timestamp: 0n }),
142
+ );
143
+ }
144
+
145
+ /** Throws when `header` trails the local clock by more than `maxAgeMs`. */
146
+ export function assertFresh(header: ccc.ClientBlockHeader, maxAgeMs: number): void {
147
+ if (BigInt(Date.now()) - header.timestamp > BigInt(maxAgeMs)) {
148
+ throw new Error(`Block ${header.hash} is more than ${String(maxAgeMs)} ms old`);
149
+ }
150
+ }
151
+
139
152
  /** A deposit's maturity, rolled a cycle when its claim is too close, and whether it is ready. */
140
153
  export function depositMaturity(
141
154
  claim: ccc.Epoch,
@@ -144,7 +157,7 @@ export function depositMaturity(
144
157
  ): { maturity: ccc.Epoch; isReady: boolean } {
145
158
  const maturity =
146
159
  claim.compare(minLockUp.add(tip.epoch)) <= 0
147
- ? claim.add([DAO_CYCLE_EPOCHS, 0n, 1n])
160
+ ? claim.add(ccc.Epoch.OneNervosDaoCycle)
148
161
  : claim;
149
162
  return { maturity, isReady: maxLockUp.add(tip.epoch).compare(maturity) > 0 };
150
163
  }
@@ -155,13 +168,24 @@ export function depositMaturity(
155
168
  */
156
169
  export function broadcastDeadline(
157
170
  claims: readonly ccc.Epoch[],
158
- { broadcastReserve }: LockUpPolicy,
159
- ): ccc.Epoch | undefined {
171
+ policy: LockUpPolicy,
172
+ ): BroadcastDeadline | undefined {
160
173
  const earliest = claims.reduce<ccc.Epoch | undefined>(
161
174
  (best, claim) => (best === undefined || claim.compare(best) < 0 ? claim : best),
162
175
  undefined,
163
176
  );
164
- return earliest?.sub(broadcastReserve);
177
+ return earliest === undefined
178
+ ? undefined
179
+ : { epoch: earliest.sub(policy.broadcastReserve), tipMaxAgeMs: tipMaxAgeMs(policy) };
180
+ }
181
+
182
+ /**
183
+ * The epoch a withdrawal request must be broadcast before, and how far the tip read to
184
+ * check it may trail the clock, the policy's slack: a stale tip would pass the check late.
185
+ */
186
+ export interface BroadcastDeadline {
187
+ epoch: ccc.Epoch;
188
+ tipMaxAgeMs: number;
165
189
  }
166
190
 
167
191
  /** Adds the header dep once. */
package/src/index.ts CHANGED
@@ -21,17 +21,17 @@ import type { SupportedChain } from "./utils/chain.ts";
21
21
  export interface IckbSdk {
22
22
  /** Builds and completes a conversion, or returns a typed planning failure. */
23
23
  buildConversionTransaction: (
24
- txLike: ccc.TransactionLike,
25
24
  options: ConversionTransactionOptions,
26
25
  ) => Promise<ConversionTransactionResult>;
27
- /** Reads system, user-order, and account state against one sampled tip, under one timing policy. */
26
+ /** Reads system, user-order, and account state at the sampled tip's parent, under one timing policy. */
28
27
  getL1AccountState: (
29
28
  client: ccc.Client,
30
29
  locks: ccc.Script[],
31
30
  lockUp: LockUpPolicy,
31
+ maxFeeRate?: ccc.NumLike,
32
32
  ) => Promise<{
33
33
  system: SystemState;
34
- user: { orders: OrderGroup[] };
34
+ userOrders: OrderGroup[];
35
35
  account: AccountState;
36
36
  }>;
37
37
  }
@@ -46,7 +46,7 @@ export type {
46
46
  AccountAvailabilityProjection,
47
47
  AccountState,
48
48
  ConversionDirection,
49
- ConversionMetadata,
49
+ ConversionKind,
50
50
  ConversionNotice,
51
51
  ConversionTransactionContext,
52
52
  ConversionTransactionContextProjection,
@@ -56,14 +56,13 @@ export type {
56
56
  SystemState,
57
57
  } from "./conversion/types.ts";
58
58
  export { WALLET_LOCK_UP } from "./dao.ts";
59
- export type { LockUpPolicy } from "./dao.ts";
59
+ export type { BroadcastDeadline, LockUpPolicy } from "./dao.ts";
60
60
  export type { IckbDepositCell, ReceiptCell } from "./logic.ts";
61
61
  export type { MasterCell, OrderCell, OrderGroup } from "./order/cells.ts";
62
62
  export {
63
63
  OrderConversionRepresentabilityError,
64
64
  quoteConversion,
65
65
  } from "./order/conversion.ts";
66
- export { isRefused, isStale } from "./order/fill.ts";
67
66
  export type { Info, InfoLike } from "./order/info.ts";
68
67
  export type { Master, MasterLike } from "./order/master.ts";
69
68
  export type { OrderData, OrderDataLike } from "./order/order_data.ts";
@@ -74,7 +73,7 @@ export type {
74
73
  OwnerCell,
75
74
  WithdrawalGroup,
76
75
  } from "./owned_owner.ts";
77
- export { hasTransactionActivity, IckbError } from "./sdk.ts";
76
+ export { IckbError } from "./sdk.ts";
78
77
  export type { IckbErrorCode } from "./sdk.ts";
79
78
  export {
80
79
  signAndSendTransaction,
@@ -86,4 +85,5 @@ export { TransactionWaitError, waitTransaction } from "./send/wait_transaction.t
86
85
  export type { WaitTransactionOptions } from "./send/wait_transaction.ts";
87
86
  export { ickbExchangeRatio } from "./udt.ts";
88
87
  export type { SupportedChain } from "./utils/chain.ts";
89
- export type { ExchangeRatio, TransactionHeader, ValueComponents } from "./utils/utils.ts";
88
+ export { clientCache } from "./utils/utils.ts";
89
+ export type { ExchangeRatio, ValueComponents } from "./utils/utils.ts";
package/src/logic.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { ccc } from "@ckb-ccc/core";
2
2
  import {
3
3
  assertDaoOutputLimit,
4
+ DAO_DEPOSIT_DATA,
4
5
  DaoOutputLimitError,
5
- depositData,
6
6
  depositMaturity,
7
7
  isDaoDeposit,
8
8
  pushHeaderDep,
@@ -16,11 +16,11 @@ import {
16
16
  receiptDataBytes,
17
17
  type ReceiptData,
18
18
  } from "./udt.ts";
19
- import { transactionHeaders } from "./utils/transaction_header.ts";
20
19
  import {
21
20
  findCells,
21
+ headersByNumber,
22
+ type ScannedCell,
22
23
  type ScriptDeps,
23
- type TransactionHeader,
24
24
  type ValueComponents,
25
25
  } from "./utils/utils.ts";
26
26
 
@@ -36,10 +36,8 @@ const phase2TxFeeReserve = ccc.One;
36
36
  export interface IckbDepositCell extends ValueComponents {
37
37
  /** The DAO deposit cell. */
38
38
  cell: ccc.Cell;
39
- /** The deposit header and the tip the readiness was judged against. */
40
- headers: [TransactionHeader, TransactionHeader];
41
- /** The DAO interest accrued between the two headers. */
42
- interests: ccc.Num;
39
+ /** The header of the block that committed the deposit, which values it. */
40
+ header: ccc.ClientBlockHeader;
43
41
  /**
44
42
  * The DAO claim epoch as sampled at the tip, not rolled: each reader judges it against
45
43
  * its own {@link LockUpPolicy} with `depositMaturity`.
@@ -53,8 +51,8 @@ export interface IckbDepositCell extends ValueComponents {
53
51
  export interface ReceiptCell extends ValueComponents {
54
52
  /** The cell associated with the receipt. */
55
53
  cell: ccc.Cell;
56
- /** The header of the transaction that made the deposits. */
57
- header: TransactionHeader;
54
+ /** The header of the block that committed the deposits, which values them. */
55
+ header: ccc.ClientBlockHeader;
58
56
  }
59
57
 
60
58
  /**
@@ -94,8 +92,8 @@ export class LogicManager implements ScriptDeps {
94
92
  * Adds `depositQuantity` DAO deposits of `depositCapacity` locked by the logic script,
95
93
  * then the receipt that mints their iCKB in phase 2, locked by `lock`.
96
94
  *
97
- * @remarks Caller must ensure UDT cellDeps are added to the transaction
98
- * (e.g., via ickbUdt.addCellDeps(tx)).
95
+ * @remarks Completion (`IckbSdk.completeTransaction`) adds the dep group that also
96
+ * resolves the xUDT script.
99
97
  */
100
98
  public deposit(
101
99
  txLike: ccc.TransactionLike,
@@ -103,13 +101,11 @@ export class LogicManager implements ScriptDeps {
103
101
  depositCapacity: ccc.FixedPoint,
104
102
  lock: ccc.Script,
105
103
  ): ccc.Transaction {
106
- const tx = ccc.Transaction.from(txLike);
107
- if (depositQuantity <= 0) {
108
- return tx;
109
- }
110
- if (!Number.isSafeInteger(depositQuantity)) {
111
- throw new TypeError("iCKB deposit quantity must be a safe integer");
104
+ // A zero quantity would mint a receipt for no deposits, which iCKB Logic refuses on chain.
105
+ if (!Number.isSafeInteger(depositQuantity) || depositQuantity <= 0) {
106
+ throw new TypeError("iCKB deposit quantity must be a positive safe integer");
112
107
  }
108
+ const tx = ccc.Transaction.from(txLike);
113
109
  if (depositQuantity > maxDepositQuantity) {
114
110
  // The completion walk steps a plan down on this error.
115
111
  throw new DaoOutputLimitError(tx.outputs.length + depositQuantity + 1);
@@ -118,7 +114,7 @@ export class LogicManager implements ScriptDeps {
118
114
  const depositCell = ccc.Cell.from({
119
115
  previousOutput: { txHash: `0x${"00".repeat(32)}`, index: 0 },
120
116
  cellOutput: { capacity: depositCapacity, lock: this.script, type: this.dao.script },
121
- outputData: depositData(),
117
+ outputData: DAO_DEPOSIT_DATA,
122
118
  });
123
119
  const depositAmount = depositCell.capacityFree;
124
120
  if (depositAmount < ccc.fixedPointFrom(1000)) {
@@ -167,15 +163,10 @@ export class LogicManager implements ScriptDeps {
167
163
  `Receipt ${outPoint} is not an iCKB receipt for this logic script`,
168
164
  );
169
165
  }
170
- if (receipt.header.txHash !== receipt.cell.outPoint.txHash) {
171
- throw new Error(
172
- `Receipt ${outPoint} header txHash ${String(receipt.header.txHash)} does not match cell txHash ${receipt.cell.outPoint.txHash}`,
173
- );
174
- }
175
166
  }
176
167
  tx.addCellDeps(this.cellDeps);
177
168
  for (const receipt of receipts) {
178
- pushHeaderDep(tx, receipt.header.header.hash);
169
+ pushHeaderDep(tx, receipt.header.hash);
179
170
  tx.addInput(receipt.cell);
180
171
  }
181
172
  return tx;
@@ -186,23 +177,22 @@ export class LogicManager implements ScriptDeps {
186
177
  */
187
178
  public async receiptsFrom(
188
179
  client: ccc.Client,
189
- cells: readonly ccc.Cell[],
180
+ cells: readonly ScannedCell[],
190
181
  ): Promise<ReceiptCell[]> {
191
- const receipts = cells.filter((cell) => this.isReceipt(cell));
192
- const headers = await transactionHeaders(
182
+ const receipts = cells.filter(({ cell }) => this.isReceipt(cell));
183
+ const headers = await headersByNumber(
193
184
  client,
194
- receipts.map((cell) => cell.outPoint.txHash),
185
+ receipts.map(({ blockNumber }) => blockNumber),
186
+ );
187
+ return receipts.map(({ cell, blockNumber }) =>
188
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- every receipt's block was read above.
189
+ receiptCell(cell, headers.get(blockNumber)!),
195
190
  );
196
- return receipts.map((cell) => {
197
- const { txHash } = cell.outPoint;
198
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- every receipt's hash was read above.
199
- return receiptCell(cell, { header: headers.get(txHash)!, txHash });
200
- });
201
191
  }
202
192
 
203
193
  /**
204
194
  * Every live iCKB deposit, valued at its deposit header, with its claim epoch at the tip;
205
- * each deposit transaction's header is read once.
195
+ * each deposit block's header is read once.
206
196
  */
207
197
  public async findDeposits(
208
198
  client: ccc.Client,
@@ -214,39 +204,37 @@ export class LogicManager implements ScriptDeps {
214
204
  scriptType: "lock",
215
205
  filter: {
216
206
  script: this.dao.script,
217
- outputData: depositData(),
207
+ outputData: DAO_DEPOSIT_DATA,
218
208
  outputDataSearchMode: "exact",
219
209
  },
220
210
  scriptSearchMode: "exact",
221
211
  withData: true,
222
212
  })
223
- ).filter((cell) => this.isDeposit(cell));
224
- const headers = await transactionHeaders(
213
+ ).filter(({ cell }) => this.isDeposit(cell));
214
+ const headers = await headersByNumber(
225
215
  client,
226
- cells.map((cell) => cell.outPoint.txHash),
216
+ cells.map(({ blockNumber }) => blockNumber),
217
+ );
218
+ return cells.map(({ cell, blockNumber }) =>
219
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- every deposit's block was read above.
220
+ ickbDepositCell(cell, headers.get(blockNumber)!, tip),
227
221
  );
228
- return cells.map((cell) => {
229
- const { txHash } = cell.outPoint;
230
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- every deposit's hash was read above.
231
- return ickbDepositCell(cell, { header: headers.get(txHash)!, txHash }, tip);
232
- });
233
222
  }
234
223
  }
235
224
 
236
225
  /** Values a DAO deposit cell at its deposit header and samples its claim epoch at the tip. */
237
- export function ickbDepositCell(
226
+ function ickbDepositCell(
238
227
  cell: ccc.Cell,
239
- depositHeader: TransactionHeader,
228
+ header: ccc.ClientBlockHeader,
240
229
  tip: ccc.ClientBlockHeader,
241
230
  ): IckbDepositCell {
242
- const interests = ccc.calcDaoProfit(cell.capacityFree, depositHeader.header, tip);
231
+ const interests = ccc.calcDaoProfit(cell.capacityFree, header, tip);
243
232
  return {
244
233
  cell,
245
- headers: [depositHeader, { header: tip }],
246
- interests,
247
- claimEpoch: ccc.calcDaoClaimEpoch(depositHeader.header, tip),
234
+ header,
235
+ claimEpoch: ccc.calcDaoClaimEpoch(header, tip),
248
236
  ckbValue: cell.cellOutput.capacity + interests,
249
- udtValue: ickbValue(cell.capacityFree, depositHeader.header),
237
+ udtValue: ickbValue(cell.capacityFree, header),
250
238
  };
251
239
  }
252
240
 
@@ -262,7 +250,7 @@ export function readyDeposits(
262
250
  }
263
251
 
264
252
  /** Decodes a receipt cell and values it at its deposit header. */
265
- export function receiptCell(cell: ccc.Cell, header: TransactionHeader): ReceiptCell {
253
+ export function receiptCell(cell: ccc.Cell, header: ccc.ClientBlockHeader): ReceiptCell {
266
254
  let receipt: ReceiptData;
267
255
  try {
268
256
  receipt = decodeReceiptData(cell.outputData);
@@ -276,7 +264,7 @@ export function receiptCell(cell: ccc.Cell, header: TransactionHeader): ReceiptC
276
264
  cell,
277
265
  header,
278
266
  ckbValue: cell.cellOutput.capacity,
279
- udtValue: ickbValue(receipt.depositAmount, header.header) * receipt.depositQuantity,
267
+ udtValue: ickbValue(receipt.depositAmount, header) * receipt.depositQuantity,
280
268
  };
281
269
  }
282
270