@ickb/sdk 1000.0.81 → 9000.0.1

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 (157) hide show
  1. package/README.md +81 -32
  2. package/dist/constants.d.ts +13 -22
  3. package/dist/constants.d.ts.map +1 -1
  4. package/dist/constants.js +80 -76
  5. package/dist/constants.js.map +1 -1
  6. package/dist/conversion/estimate.d.ts +39 -0
  7. package/dist/conversion/estimate.d.ts.map +1 -0
  8. package/dist/conversion/estimate.js +117 -0
  9. package/dist/conversion/estimate.js.map +1 -0
  10. package/dist/conversion/maturity.d.ts +15 -0
  11. package/dist/conversion/maturity.d.ts.map +1 -0
  12. package/dist/conversion/maturity.js +112 -0
  13. package/dist/conversion/maturity.js.map +1 -0
  14. package/dist/conversion/plans.d.ts +20 -0
  15. package/dist/conversion/plans.d.ts.map +1 -0
  16. package/dist/conversion/plans.js +106 -0
  17. package/dist/conversion/plans.js.map +1 -0
  18. package/dist/conversion/projection.d.ts +22 -0
  19. package/dist/conversion/projection.d.ts.map +1 -0
  20. package/dist/conversion/projection.js +91 -0
  21. package/dist/conversion/projection.js.map +1 -0
  22. package/dist/conversion/types.d.ts +252 -0
  23. package/dist/conversion/types.d.ts.map +1 -0
  24. package/dist/conversion/types.js +2 -0
  25. package/dist/conversion/types.js.map +1 -0
  26. package/dist/conversion/withdrawal_ring.d.ts +36 -0
  27. package/dist/conversion/withdrawal_ring.d.ts.map +1 -0
  28. package/dist/conversion/withdrawal_ring.js +87 -0
  29. package/dist/conversion/withdrawal_ring.js.map +1 -0
  30. package/dist/dao.d.ts +86 -0
  31. package/dist/dao.d.ts.map +1 -0
  32. package/dist/dao.js +113 -0
  33. package/dist/dao.js.map +1 -0
  34. package/dist/index.d.ts +51 -3
  35. package/dist/index.d.ts.map +1 -1
  36. package/dist/index.js +20 -3
  37. package/dist/index.js.map +1 -1
  38. package/dist/logic.d.ts +85 -0
  39. package/dist/logic.d.ts.map +1 -0
  40. package/dist/logic.js +184 -0
  41. package/dist/logic.js.map +1 -0
  42. package/dist/order/cells.d.ts +207 -0
  43. package/dist/order/cells.d.ts.map +1 -0
  44. package/dist/order/cells.js +427 -0
  45. package/dist/order/cells.js.map +1 -0
  46. package/dist/order/conversion.d.ts +43 -0
  47. package/dist/order/conversion.d.ts.map +1 -0
  48. package/dist/order/conversion.js +123 -0
  49. package/dist/order/conversion.js.map +1 -0
  50. package/dist/order/fee.d.ts +9 -0
  51. package/dist/order/fee.d.ts.map +1 -0
  52. package/dist/order/fee.js +24 -0
  53. package/dist/order/fee.js.map +1 -0
  54. package/dist/order/fill.d.ts +36 -0
  55. package/dist/order/fill.d.ts.map +1 -0
  56. package/dist/order/fill.js +53 -0
  57. package/dist/order/fill.js.map +1 -0
  58. package/dist/order/info.d.ts +62 -0
  59. package/dist/order/info.d.ts.map +1 -0
  60. package/dist/order/info.js +99 -0
  61. package/dist/order/info.js.map +1 -0
  62. package/dist/order/master.d.ts +49 -0
  63. package/dist/order/master.d.ts.map +1 -0
  64. package/dist/order/master.js +27 -0
  65. package/dist/order/master.js.map +1 -0
  66. package/dist/order/matcher.d.ts +43 -0
  67. package/dist/order/matcher.d.ts.map +1 -0
  68. package/dist/order/matcher.js +123 -0
  69. package/dist/order/matcher.js.map +1 -0
  70. package/dist/order/order.d.ts +59 -0
  71. package/dist/order/order.d.ts.map +1 -0
  72. package/dist/order/order.js +239 -0
  73. package/dist/order/order.js.map +1 -0
  74. package/dist/order/order_data.d.ts +51 -0
  75. package/dist/order/order_data.d.ts.map +1 -0
  76. package/dist/order/order_data.js +64 -0
  77. package/dist/order/order_data.js.map +1 -0
  78. package/dist/order/ratio.d.ts +42 -0
  79. package/dist/order/ratio.d.ts.map +1 -0
  80. package/dist/order/ratio.js +82 -0
  81. package/dist/order/ratio.js.map +1 -0
  82. package/dist/order/relative.d.ts +43 -0
  83. package/dist/order/relative.d.ts.map +1 -0
  84. package/dist/order/relative.js +54 -0
  85. package/dist/order/relative.js.map +1 -0
  86. package/dist/owned_owner.d.ts +113 -0
  87. package/dist/owned_owner.d.ts.map +1 -0
  88. package/dist/owned_owner.js +280 -0
  89. package/dist/owned_owner.js.map +1 -0
  90. package/dist/sdk.d.ts +71 -42
  91. package/dist/sdk.d.ts.map +1 -1
  92. package/dist/sdk.js +388 -209
  93. package/dist/sdk.js.map +1 -1
  94. package/dist/send/sign_and_send_transaction.d.ts +46 -0
  95. package/dist/send/sign_and_send_transaction.d.ts.map +1 -0
  96. package/dist/send/sign_and_send_transaction.js +101 -0
  97. package/dist/send/sign_and_send_transaction.js.map +1 -0
  98. package/dist/send/wait_transaction.d.ts +41 -0
  99. package/dist/send/wait_transaction.d.ts.map +1 -0
  100. package/dist/send/wait_transaction.js +204 -0
  101. package/dist/send/wait_transaction.js.map +1 -0
  102. package/dist/udt.d.ts +123 -0
  103. package/dist/udt.d.ts.map +1 -0
  104. package/dist/udt.js +244 -0
  105. package/dist/udt.js.map +1 -0
  106. package/dist/utils/chain.d.ts +38 -0
  107. package/dist/utils/chain.d.ts.map +1 -0
  108. package/dist/utils/chain.js +20 -0
  109. package/dist/utils/chain.js.map +1 -0
  110. package/dist/utils/codec.d.ts +19 -0
  111. package/dist/utils/codec.d.ts.map +1 -0
  112. package/dist/utils/codec.js +66 -0
  113. package/dist/utils/codec.js.map +1 -0
  114. package/dist/utils/transaction_header.d.ts +17 -0
  115. package/dist/utils/transaction_header.d.ts.map +1 -0
  116. package/dist/utils/transaction_header.js +44 -0
  117. package/dist/utils/transaction_header.js.map +1 -0
  118. package/dist/utils/utils.d.ts +126 -0
  119. package/dist/utils/utils.d.ts.map +1 -0
  120. package/dist/utils/utils.js +151 -0
  121. package/dist/utils/utils.js.map +1 -0
  122. package/package.json +19 -26
  123. package/src/constants.ts +84 -181
  124. package/src/conversion/estimate.ts +157 -0
  125. package/src/conversion/maturity.ts +159 -0
  126. package/src/conversion/plans.ts +144 -0
  127. package/src/conversion/projection.ts +128 -0
  128. package/src/conversion/types.ts +287 -0
  129. package/src/conversion/withdrawal_ring.ts +126 -0
  130. package/src/dao.ts +172 -0
  131. package/src/index.ts +89 -3
  132. package/src/logic.ts +295 -0
  133. package/src/order/cells.ts +520 -0
  134. package/src/order/conversion.ts +173 -0
  135. package/src/order/fee.ts +30 -0
  136. package/src/order/fill.ts +83 -0
  137. package/src/order/info.ts +178 -0
  138. package/src/order/master.ts +42 -0
  139. package/src/order/matcher.ts +191 -0
  140. package/src/order/order.ts +302 -0
  141. package/src/order/order_data.ts +128 -0
  142. package/src/order/ratio.ts +144 -0
  143. package/src/order/relative.ts +106 -0
  144. package/src/owned_owner.ts +394 -0
  145. package/src/sdk.ts +555 -440
  146. package/src/send/sign_and_send_transaction.ts +120 -0
  147. package/src/send/wait_transaction.ts +271 -0
  148. package/src/udt.ts +317 -0
  149. package/src/utils/chain.ts +43 -0
  150. package/src/utils/codec.ts +74 -0
  151. package/src/utils/transaction_header.ts +66 -0
  152. package/src/utils/utils.ts +237 -0
  153. package/dist/codec.d.ts +0 -3
  154. package/dist/codec.d.ts.map +0 -1
  155. package/dist/codec.js +0 -58
  156. package/dist/codec.js.map +0 -1
  157. package/src/codec.ts +0 -138
@@ -0,0 +1,159 @@
1
+ import type { ccc } from "@ckb-ccc/core";
2
+ import { BOT_LOCK_UP, depositMaturity } from "../dao.ts";
3
+ import type { IckbDepositCell } from "../logic.ts";
4
+ import type { OrderGroup } from "../order/cells.ts";
5
+ import { fillsWhole } from "../order/fill.ts";
6
+ import type { Info } from "../order/info.ts";
7
+ import { convert, ICKB_DEPOSIT_CAP } from "../udt.ts";
8
+ import { compareBigInt, type ValueComponents } from "../utils/utils.ts";
9
+ import type { MaturityOrderInput, SystemState } from "./types.ts";
10
+
11
+ /**
12
+ * Assumed turn duration, allowing for restart delay, confirmation and slow reads.
13
+ * This is a modeling interval, not a bound on the runtime; see sdk/docs/pool_maturity_estimates.md.
14
+ */
15
+ export const BOT_TURN_MS = 10n * 60n * 1000n;
16
+
17
+ /**
18
+ * The estimated fill time of an order on the book or about to be placed, zero when it is
19
+ * already fulfilled. `takenDeposits` are the pool deposits the same plan withdraws
20
+ * directly, which cannot fill its order leg too. Dual-ratio orders never reach here: the
21
+ * scan drops them.
22
+ */
23
+ export function maturity(
24
+ o: MaturityOrderInput,
25
+ system: SystemState,
26
+ takenDeposits: readonly IckbDepositCell[] = [],
27
+ ): bigint {
28
+ const { info, amounts, self } = maturityOrderParts(o);
29
+ const isCkb2Udt = info.isCkb2Udt();
30
+ const amount = isCkb2Udt ? amounts.ckbValue : amounts.udtValue;
31
+ if (amount === 0n) {
32
+ return 0n;
33
+ }
34
+
35
+ return isCkb2Udt
36
+ ? ckbToIckbOrderMaturity(info, amount, system, self)
37
+ : ickbToCkbOrderMaturity(info, amount, system, takenDeposits, self);
38
+ }
39
+
40
+ /** The order's terms, and its own out point when it is already on the book. */
41
+ function maturityOrderParts(o: MaturityOrderInput): {
42
+ info: Info;
43
+ amounts: ValueComponents;
44
+ self: ccc.OutPoint | undefined;
45
+ } {
46
+ if ("info" in o) {
47
+ return { ...o, self: undefined };
48
+ }
49
+
50
+ return {
51
+ info: o.data.info,
52
+ amounts: { ckbValue: o.ckbUnoccupied, udtValue: o.udtValue },
53
+ self: o.cell.outPoint,
54
+ };
55
+ }
56
+
57
+ /**
58
+ * A buyer waits for the bot to mint: the bot mints one cap-sized deposit per turn once its
59
+ * iCKB inventory is spent, and the inventory is unknown here, so the wait is one turn plus
60
+ * one per whole cap of positive net CKB demand (this request and buyers priced at least
61
+ * as well, less the iCKB sellers bring in). The model does not measure the bot's funds.
62
+ */
63
+ function ckbToIckbOrderMaturity(
64
+ info: Info,
65
+ amount: bigint,
66
+ system: SystemState,
67
+ self: ccc.OutPoint | undefined,
68
+ ): bigint {
69
+ // An equal price counts as ahead: the bot fills ties in an order of its own choosing.
70
+ const buyersAhead = fillableOrders(system, true, self)
71
+ .filter((group) => group.order.data.info.ckbToUdt.compare(info.ckbToUdt) <= 0)
72
+ .reduce((ckb, group) => ckb + group.order.ckbUnoccupied, 0n);
73
+ const sellers = fillableOrders(system, false, self).reduce(
74
+ (udt, group) => udt + group.udtValue,
75
+ 0n,
76
+ );
77
+ const demand = amount + buyersAhead - convert(false, sellers, system.exchangeRatio);
78
+ const capCkb = convert(false, ICKB_DEPOSIT_CAP, system.exchangeRatio);
79
+ const turns = demand > 0n ? 1n + demand / capCkb : 1n;
80
+ return system.tip.timestamp + BOT_TURN_MS * turns;
81
+ }
82
+
83
+ /**
84
+ * A seller waits for CKB: one deposit's worth of assumed working capital, zeroed when
85
+ * another fillable seller has sat beyond the threshold, plus dated pool supply. Return
86
+ * the first date covering demand, plus a turn. Insufficient supply falls back to the last
87
+ * date; this can understate an above-par ask's wait for further DAO growth.
88
+ */
89
+ function ickbToCkbOrderMaturity(
90
+ info: Info,
91
+ amount: bigint,
92
+ system: SystemState,
93
+ takenDeposits: readonly IckbDepositCell[],
94
+ self: ccc.OutPoint | undefined,
95
+ ): bigint {
96
+ const sellers = fillableOrders(system, false, self);
97
+ const sellersAhead = sellers
98
+ .filter((group) => info.udtToCkb.compare(group.order.data.info.udtToCkb) <= 0)
99
+ .reduce((udt, group) => udt + group.udtValue, 0n);
100
+ // The CKB an order has already received belongs to its earlier fills; what it still
101
+ // needs is the remaining iCKB at its price.
102
+ const needed =
103
+ info.udtToCkb.convert(false, amount, true) +
104
+ convert(false, sellersAhead, system.exchangeRatio);
105
+ const taken = new Set(takenDeposits.map((deposit) => deposit.cell.outPoint.toHex()));
106
+ const steps = [
107
+ {
108
+ ckbValue: sellers.some((group) => sits(group, system))
109
+ ? 0n
110
+ : convert(false, ICKB_DEPOSIT_CAP, system.exchangeRatio),
111
+ at: system.tip.timestamp,
112
+ },
113
+ // A claim the bot can no longer request in time rolls a cycle: the bot's rule, not the
114
+ // caller's, since the bot is the one making the requests.
115
+ ...system.poolDeposits
116
+ .filter((deposit) => !taken.has(deposit.cell.outPoint.toHex()))
117
+ .map((deposit) => ({
118
+ ckbValue: deposit.ckbValue,
119
+ at: depositMaturity(deposit.claimEpoch, system.tip, BOT_LOCK_UP).maturity.toUnix(
120
+ system.tip,
121
+ ),
122
+ }))
123
+ .toSorted((left, right) => compareBigInt(left.at, right.at)),
124
+ ];
125
+ let supply = 0n;
126
+ let at = system.tip.timestamp;
127
+ for (const step of steps) {
128
+ supply += step.ckbValue;
129
+ at = step.at;
130
+ if (supply >= needed) {
131
+ break;
132
+ }
133
+ }
134
+ return at + BOT_TURN_MS;
135
+ }
136
+
137
+ /** The book orders the bot would take whole in the given direction, this one aside. */
138
+ function fillableOrders(
139
+ system: SystemState,
140
+ isCkb2Udt: boolean,
141
+ self: ccc.OutPoint | undefined,
142
+ ): OrderGroup[] {
143
+ return system.orderPool.filter(
144
+ (group) =>
145
+ (self === undefined || !group.order.cell.outPoint.eq(self)) &&
146
+ fillsWhole(group, isCkb2Udt, system.exchangeRatio, system.feeRate),
147
+ );
148
+ }
149
+
150
+ /**
151
+ * Whether the order has been on the book for more than a turn, a twenty-fourth of an
152
+ * epoch in blocks: the bot has seen it and left it. An uncommitted origin is fresh.
153
+ */
154
+ function sits(group: OrderGroup, { tip }: SystemState): boolean {
155
+ return (
156
+ group.blockNumber !== undefined &&
157
+ tip.number - group.blockNumber > tip.epoch.denominator / 24n
158
+ );
159
+ }
@@ -0,0 +1,144 @@
1
+ import { DAO_OUTPUT_LIMIT } from "../dao.ts";
2
+ import { readyDeposits, type IckbDepositCell } from "../logic.ts";
3
+ import { ICKB_DEPOSIT_CAP, convert } from "../udt.ts";
4
+ import { maxBigInt } from "../utils/utils.ts";
5
+ import {
6
+ DEFAULT_ORDER_FEE,
7
+ DEFAULT_ORDER_FEE_BASE,
8
+ estimateConversionOrder,
9
+ estimateIckbToCkbOrder,
10
+ } from "./estimate.ts";
11
+ import { sumUdt } from "./projection.ts";
12
+ import type {
13
+ CkbToIckbConversionPlan,
14
+ ConversionOrder,
15
+ ConversionTransactionOptions,
16
+ IckbToCkbConversionPlan,
17
+ } from "./types.ts";
18
+ import {
19
+ fitWithdrawalDeposits,
20
+ ringSurplusDepositFilter,
21
+ sortByClaim,
22
+ } from "./withdrawal_ring.ts";
23
+
24
+ // The planner starts at the most `DAO_OUTPUT_LIMIT` can hold, one change output beside the
25
+ // deposits, and the completion walk steps down from there when change and fees need more room.
26
+ const MAX_PLANNED_DEPOSITS = DAO_OUTPUT_LIMIT - 1;
27
+
28
+ /**
29
+ * Plans every deposit count from the most the amount covers down to zero, skipping
30
+ * unrepresentable remainders. Cap-sized deposits plus an order for the rest is the cheapest
31
+ * shape at every amount: a deposit's 82 CKB of occupied capacity goes to its withdrawer
32
+ * (0.082% at the cap, 8.2% at the contract's 1,000 CKB minimum), the excess above the cap
33
+ * is discounted 10%, and an order costs 0.01% at any size; the cap's CKB value grows with
34
+ * the DAO rate, so the order stays cheaper until the cap is worth 820,000 CKB.
35
+ */
36
+ export function ckbToIckbConversionPlans(
37
+ options: ConversionTransactionOptions,
38
+ ): CkbToIckbConversionPlan[] {
39
+ const { amount, context } = options;
40
+ const depositCapacity = convert(false, ICKB_DEPOSIT_CAP, context.system.exchangeRatio);
41
+ const depositQuotient = depositCapacity === 0n ? 0n : amount / depositCapacity;
42
+ const maxDeposits =
43
+ depositQuotient > BigInt(MAX_PLANNED_DEPOSITS)
44
+ ? MAX_PLANNED_DEPOSITS
45
+ : Number(depositQuotient);
46
+ const plans: CkbToIckbConversionPlan[] = [];
47
+ for (let depositCount = maxDeposits; depositCount >= 0; depositCount -= 1) {
48
+ const plan = ckbToIckbConversionPlan(options, depositCapacity, depositCount);
49
+ if (plan !== undefined) {
50
+ plans.push(plan);
51
+ }
52
+ }
53
+ return plans;
54
+ }
55
+
56
+ /**
57
+ * Plans every prefix of the greedy ready-deposit selection, longest first, the remainder as
58
+ * an order: direct withdrawals are free and their claim date certain, an order pays a fee
59
+ * and waits for the bot, so the completion walk takes the most direct plan it can fund.
60
+ * The prefix shape keeps ring classification frozen: a prefix of surplus deposits stays
61
+ * surplus with the same anchors pinned.
62
+ */
63
+ export function ickbToCkbConversionPlans(
64
+ options: ConversionTransactionOptions,
65
+ poolDeposits: readonly IckbDepositCell[],
66
+ ): IckbToCkbConversionPlan[] {
67
+ const { amount, context } = options;
68
+ const { tip, lockUp } = context.system;
69
+ const ready = readyDeposits(poolDeposits, tip, lockUp);
70
+ const isSurplus = ringSurplusDepositFilter(poolDeposits, ready);
71
+ const deposits = fitWithdrawalDeposits(sortByClaim(ready.filter(isSurplus)), amount);
72
+ const plans: IckbToCkbConversionPlan[] = [];
73
+ for (let count = deposits.length; count >= 0; count -= 1) {
74
+ const plan = ickbToCkbConversionPlan(options, deposits.slice(0, count));
75
+ if (plan !== undefined) {
76
+ plans.push(plan);
77
+ }
78
+ }
79
+ return plans;
80
+ }
81
+
82
+ function ckbToIckbConversionPlan(
83
+ options: ConversionTransactionOptions,
84
+ depositCapacity: bigint,
85
+ depositCount: number,
86
+ ): CkbToIckbConversionPlan | undefined {
87
+ const { amount, context } = options;
88
+ const remainder = amount - depositCapacity * BigInt(depositCount);
89
+ let estimatedMaturity = context.estimatedMaturity;
90
+ let order: ConversionOrder | undefined;
91
+
92
+ if (remainder > 0n) {
93
+ const amounts = { ckbValue: remainder, udtValue: 0n };
94
+ const estimate = estimateConversionOrder(true, amounts, context.system, {
95
+ fee: DEFAULT_ORDER_FEE,
96
+ feeBase: DEFAULT_ORDER_FEE_BASE,
97
+ });
98
+ if (estimate?.maturity === undefined) {
99
+ return undefined;
100
+ }
101
+ estimatedMaturity = maxBigInt(estimatedMaturity, estimate.maturity);
102
+ order = { amounts, estimate };
103
+ }
104
+
105
+ return {
106
+ depositCapacity,
107
+ depositCount,
108
+ estimatedMaturity,
109
+ ...(order === undefined ? {} : { order }),
110
+ };
111
+ }
112
+
113
+ function ickbToCkbConversionPlan(
114
+ options: ConversionTransactionOptions,
115
+ selectedDeposits: IckbDepositCell[],
116
+ ): IckbToCkbConversionPlan | undefined {
117
+ const { amount, context } = options;
118
+ let estimatedMaturity = context.estimatedMaturity;
119
+ // Selected deposits are ready, so their sampled claim is their date.
120
+ for (const deposit of selectedDeposits) {
121
+ estimatedMaturity = maxBigInt(
122
+ estimatedMaturity,
123
+ deposit.claimEpoch.toUnix(context.system.tip),
124
+ );
125
+ }
126
+ const remainder = amount - sumUdt(selectedDeposits);
127
+ let order: ConversionOrder | undefined;
128
+ if (remainder > 0n) {
129
+ const amounts = { ckbValue: 0n, udtValue: remainder };
130
+ // The deposits this plan withdraws directly cannot fill its order leg too.
131
+ const estimate = estimateIckbToCkbOrder(amounts, context.system, selectedDeposits);
132
+ if (estimate === undefined) {
133
+ return undefined;
134
+ }
135
+ estimatedMaturity = maxBigInt(estimatedMaturity, estimate.maturity);
136
+ order = { amounts, estimate };
137
+ }
138
+
139
+ return {
140
+ estimatedMaturity,
141
+ ...(order === undefined ? {} : { order }),
142
+ selectedDeposits,
143
+ };
144
+ }
@@ -0,0 +1,128 @@
1
+ import { ccc } from "@ckb-ccc/core";
2
+ import { DAO_HEADER_INDEX_LIMIT } from "../dao.ts";
3
+ import type { OrderGroup } from "../order/cells.ts";
4
+ import type { WithdrawalGroup } from "../owned_owner.ts";
5
+ import { maxBigInt } from "../utils/utils.ts";
6
+ import { maturity } from "./maturity.ts";
7
+ import type {
8
+ AccountAvailabilityProjection,
9
+ AccountState,
10
+ ConversionTransactionContextProjection,
11
+ SystemState,
12
+ } from "./types.ts";
13
+
14
+ /**
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.
18
+ */
19
+ export function projectConversionTransactionContext(
20
+ system: SystemState,
21
+ account: AccountState,
22
+ orders: { available: OrderGroup[]; pending: OrderGroup[] },
23
+ ): ConversionTransactionContextProjection {
24
+ const projection = projectAccountAvailability(account, orders);
25
+ const estimatedMaturity = [
26
+ ...projection.pendingWithdrawals.map((group) =>
27
+ group.owned.maturity.toUnix(system.tip),
28
+ ),
29
+ ...projection.pendingOrders.map((group) => maturity(group.order, system)),
30
+ ].reduce(maxBigInt, system.tip.timestamp);
31
+
32
+ return {
33
+ projection,
34
+ context: {
35
+ system,
36
+ receipts: account.receipts,
37
+ readyWithdrawals: projection.readyWithdrawals,
38
+ availableOrders: projection.availableOrders,
39
+ cells: [...account.capacityCells, ...account.nativeUdtCells],
40
+ ckbAvailable: projection.ckbAvailable,
41
+ ickbAvailable: projection.ickbAvailable,
42
+ estimatedMaturity,
43
+ },
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Splits wallet-owned CKB and iCKB into immediately available and pending value.
49
+ */
50
+ export function projectAccountAvailability(
51
+ account: AccountState,
52
+ {
53
+ available: availableOrders,
54
+ pending: pendingOrders,
55
+ }: { available: OrderGroup[]; pending: OrderGroup[] },
56
+ ): AccountAvailabilityProjection {
57
+ const { readyWithdrawals, pendingWithdrawals } = splitWithdrawals(
58
+ account.withdrawalGroups,
59
+ );
60
+ // An iCKB cell's capacity is the owner's CKB too: completion spends it like a plain cell.
61
+ const ckbNative = sumValues(
62
+ [...account.capacityCells, ...account.nativeUdtCells],
63
+ (cell) => cell.cellOutput.capacity,
64
+ );
65
+ const ickbNative = sumValues(account.nativeUdtCells, (cell) =>
66
+ ccc.udtBalanceFrom(cell.outputData),
67
+ );
68
+ const ckbAvailable =
69
+ ckbNative +
70
+ sumCkb(account.receipts) +
71
+ sumCkb(readyWithdrawals) +
72
+ sumCkb(availableOrders);
73
+ const ickbAvailable = ickbNative + sumUdt(account.receipts) + sumUdt(availableOrders);
74
+ const ckbPending = sumCkb(pendingWithdrawals) + sumCkb(pendingOrders);
75
+ const ickbPending = sumUdt(pendingOrders);
76
+
77
+ return {
78
+ ckbNative,
79
+ ickbNative,
80
+ ckbAvailable,
81
+ ickbAvailable,
82
+ ckbPending,
83
+ ickbPending,
84
+ ckbBalance: ckbAvailable + ckbPending,
85
+ ickbBalance: ickbAvailable + ickbPending,
86
+ readyWithdrawals,
87
+ pendingWithdrawals,
88
+ availableOrders,
89
+ pendingOrders,
90
+ };
91
+ }
92
+
93
+ /**
94
+ * `ready` is the batch one transaction can complete: the deployed DAO script addresses a
95
+ * withdrawal's deposit header only below `DAO_HEADER_INDEX_LIMIT`, and the builder puts
96
+ * those headers first, so matured withdrawals past that count wait a turn.
97
+ */
98
+ function splitWithdrawals(withdrawalGroups: readonly WithdrawalGroup[]): {
99
+ readyWithdrawals: WithdrawalGroup[];
100
+ pendingWithdrawals: WithdrawalGroup[];
101
+ } {
102
+ const readyWithdrawals: WithdrawalGroup[] = [];
103
+ const pendingWithdrawals: WithdrawalGroup[] = [];
104
+ for (const group of withdrawalGroups) {
105
+ if (group.owned.isReady && readyWithdrawals.length < DAO_HEADER_INDEX_LIMIT) {
106
+ readyWithdrawals.push(group);
107
+ } else {
108
+ pendingWithdrawals.push(group);
109
+ }
110
+ }
111
+ return { readyWithdrawals, pendingWithdrawals };
112
+ }
113
+
114
+ function sumCkb(items: ReadonlyArray<{ ckbValue: bigint }>): bigint {
115
+ return sumValues(items, (item) => item.ckbValue);
116
+ }
117
+
118
+ export function sumUdt(items: ReadonlyArray<{ udtValue: bigint }>): bigint {
119
+ return sumValues(items, (item) => item.udtValue);
120
+ }
121
+
122
+ function sumValues<T>(items: readonly T[], project: (item: T) => bigint): bigint {
123
+ let total = 0n;
124
+ for (const item of items) {
125
+ total += project(item);
126
+ }
127
+ return total;
128
+ }