@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,520 @@
1
+ import type { ccc } from "@ckb-ccc/core";
2
+ import type { ValueComponents } from "../utils/utils.ts";
3
+ import { OrderData } from "./order_data.ts";
4
+
5
+ // The resolver's own group, keyed by its order cell: matching and transactions use this
6
+ // parse, so a caller's own wrapper is only ever a key into it.
7
+ const resolvedOrderGroups = new WeakMap<ccc.Cell, OrderGroup>();
8
+
9
+ /**
10
+ * Represents a parsed order cell on the blockchain.
11
+ *
12
+ * Implements `ValueComponents` to expose the cell's raw data and its
13
+ * value breakdown (CKB and UDT).
14
+ */
15
+ export class OrderCell implements ValueComponents {
16
+ /** Raw live cell that carries the order lock and UDT type. */
17
+ public readonly cell: ccc.Cell;
18
+ /** Decoded order payload from `cell.outputData`. */
19
+ public readonly data: OrderData;
20
+ /** CKB capacity available for matching after occupied capacity is reserved. */
21
+ public readonly ckbUnoccupied: ccc.FixedPoint;
22
+ /** Absolute total order value in the order's comparison units. */
23
+ public readonly absTotal: ccc.Num;
24
+ /** Absolute matched progress in the order's comparison units. */
25
+ public readonly absProgress: ccc.Num;
26
+
27
+ constructor({
28
+ cell,
29
+ data,
30
+ ckbUnoccupied,
31
+ absTotal,
32
+ absProgress,
33
+ }: {
34
+ cell: ccc.Cell;
35
+ data: OrderData;
36
+ ckbUnoccupied: ccc.FixedPoint;
37
+ absTotal: ccc.Num;
38
+ absProgress: ccc.Num;
39
+ }) {
40
+ this.cell = cell;
41
+ this.data = data;
42
+ this.ckbUnoccupied = ckbUnoccupied;
43
+ this.absTotal = absTotal;
44
+ this.absProgress = absProgress;
45
+ }
46
+
47
+ /**
48
+ * Gets the order CKB amount.
49
+ *
50
+ * @returns The CKB amount as a `ccc.FixedPoint`.
51
+ */
52
+ public get ckbValue(): ccc.FixedPoint {
53
+ return this.cell.cellOutput.capacity;
54
+ }
55
+
56
+ /**
57
+ * Gets the order UDT amount.
58
+ *
59
+ * @returns The UDT amount as a `ccc.FixedPoint`.
60
+ */
61
+ public get udtValue(): ccc.FixedPoint {
62
+ return this.data.udtValue;
63
+ }
64
+
65
+ /**
66
+ * Attempts to parse an OrderCell from a raw cell.
67
+ *
68
+ * Returns `undefined` if parsing or validation fails.
69
+ *
70
+ * @param cell - The raw chain cell to convert.
71
+ * @returns An `OrderCell` instance or `undefined`.
72
+ */
73
+ public static tryFrom(cell: ccc.Cell): OrderCell | undefined {
74
+ try {
75
+ return OrderCell.mustFrom(cell);
76
+ } catch {
77
+ return undefined;
78
+ }
79
+ }
80
+
81
+ /**
82
+ * Parses and validates a raw cell as OrderCell.
83
+ *
84
+ * Throws if the cell is invalid or data validation fails.
85
+ *
86
+ * @param cell - The raw chain cell to convert.
87
+ * @returns A new `OrderCell` instance.
88
+ * @throws When decoding or validation fails.
89
+ */
90
+ public static mustFrom(cell: ccc.Cell): OrderCell {
91
+ let data: OrderData;
92
+ try {
93
+ data = OrderData.decode(cell.outputData);
94
+ data.validate();
95
+ } catch (error) {
96
+ throw new Error(`Invalid order payload at ${cell.outPoint.toHex()}`, {
97
+ cause: error,
98
+ });
99
+ }
100
+
101
+ const udtValue = data.udtValue;
102
+ const ckbUnoccupied = cell.capacityFree;
103
+ const { ckbToUdt, udtToCkb } = data.info;
104
+ const isCkb2Udt = data.info.isCkb2Udt();
105
+ const isUdt2Ckb = data.info.isUdt2Ckb();
106
+ const isDualRatio = isCkb2Udt && isUdt2Ckb;
107
+
108
+ // Compute total values in base units
109
+ const ckb2UdtValue = isCkb2Udt
110
+ ? ckbUnoccupied * ckbToUdt.ckbScale + udtValue * ckbToUdt.udtScale
111
+ : 0n;
112
+ const udt2CkbValue = isUdt2Ckb
113
+ ? ckbUnoccupied * udtToCkb.ckbScale + udtValue * udtToCkb.udtScale
114
+ : 0n;
115
+
116
+ const absTotal = absoluteOrderTotal(ckb2UdtValue, udt2CkbValue, data.info);
117
+ const absProgress = absoluteOrderProgress({
118
+ absTotal,
119
+ ckbUnoccupied,
120
+ isCkb2Udt,
121
+ isDualRatio,
122
+ udtScale: ckbToUdt.udtScale,
123
+ udtValue,
124
+ udtToCkbCkbScale: udtToCkb.ckbScale,
125
+ });
126
+
127
+ return new OrderCell({ cell, data, ckbUnoccupied, absTotal, absProgress });
128
+ }
129
+
130
+ /**
131
+ * Checks whether the CKB-to-UDT side is enabled and has nonzero inventory.
132
+ *
133
+ * @remarks Executable-match checks for fee, minimum output, and ratio bounds happen during matching.
134
+ */
135
+ public isCkb2UdtMatchable(): boolean {
136
+ return this.data.info.isCkb2Udt() && this.ckbUnoccupied > 0n;
137
+ }
138
+
139
+ /**
140
+ * Checks whether the UDT-to-CKB side is enabled and has nonzero inventory.
141
+ *
142
+ * @remarks Executable-match checks for fee, minimum output, and ratio bounds happen during matching.
143
+ */
144
+ public isUdt2CkbMatchable(): boolean {
145
+ return this.data.info.isUdt2Ckb() && this.data.udtValue > 0n;
146
+ }
147
+
148
+ /** Returns true when either side is enabled and has nonzero inventory. */
149
+ public isMatchable(): boolean {
150
+ return this.isCkb2UdtMatchable() || this.isUdt2CkbMatchable();
151
+ }
152
+
153
+ /**
154
+ * Checks if the order is fulfilled.
155
+ *
156
+ * @returns True if the order is fulfilled (not matchable), otherwise false.
157
+ */
158
+ public isFulfilled(): boolean {
159
+ return !this.isMatchable();
160
+ }
161
+
162
+ /**
163
+ * Retrieves the master out point of the order.
164
+ * @returns The master out point associated with the order.
165
+ */
166
+ public getMaster(): ccc.OutPoint {
167
+ return this.data.getMaster(this.cell.outPoint);
168
+ }
169
+
170
+ /**
171
+ * Validates a descendant order against this origin order.
172
+ *
173
+ * @remarks
174
+ * This confusion-attack guard requires the same order script, UDT type,
175
+ * master, and info, while forbidding lower total value or lower progress.
176
+ * See {@link https://github.com/ickb/whitepaper/issues/19}.
177
+ */
178
+ public validate(descendant: OrderCell): void {
179
+ if (!this.data.isMint()) {
180
+ throw new Error("Origin is not a mint order");
181
+ }
182
+
183
+ if (!this.cell.cellOutput.lock.eq(descendant.cell.cellOutput.lock)) {
184
+ throw new Error("Order script different");
185
+ }
186
+
187
+ const udt = this.cell.cellOutput.type;
188
+ if (udt === undefined || descendant.cell.cellOutput.type?.eq(udt) !== true) {
189
+ throw new Error("UDT type is different");
190
+ }
191
+
192
+ if (!descendant.getMaster().eq(this.getMaster())) {
193
+ throw new Error("Master is different");
194
+ }
195
+
196
+ if (!this.data.info.eq(descendant.data.info)) {
197
+ throw new Error("Info is different");
198
+ }
199
+
200
+ if (this.absTotal > descendant.absTotal) {
201
+ throw new Error("Total value is lower than the original one");
202
+ }
203
+
204
+ if (this.absProgress > descendant.absProgress) {
205
+ throw new Error("Progress is lower than the original one");
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Checks if the descendant order is valid against this order.
211
+ * @param descendant - The descendant order to validate.
212
+ * @returns True if the descendant is valid, otherwise false.
213
+ */
214
+ public isValid(descendant: OrderCell): boolean {
215
+ try {
216
+ this.validate(descendant);
217
+ return true;
218
+ } catch {
219
+ return false;
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Resolves the best valid descendant order.
225
+ *
226
+ * @remarks
227
+ * The genuine mint origin is the immutable validation baseline. Resolution
228
+ * prefers greater progress and then greater total value; candidate identity is
229
+ * irrelevant. Equal-score non-mint candidates are ambiguous, while the mint
230
+ * origin wins an equal-score tie when it is still live.
231
+ */
232
+ public resolve(descendants: OrderCell[]): OrderCell | undefined {
233
+ let resolution: OrderResolution | undefined;
234
+ for (const descendant of descendants) {
235
+ if (!this.isValid(descendant)) {
236
+ continue;
237
+ }
238
+
239
+ resolution = betterOrderResolution(resolution, descendant);
240
+ }
241
+
242
+ if (resolution?.isAmbiguous === true) {
243
+ return undefined;
244
+ }
245
+
246
+ return resolution?.order;
247
+ }
248
+ }
249
+
250
+ interface OrderResolution {
251
+ isAmbiguous: boolean;
252
+ order: OrderCell;
253
+ }
254
+
255
+ function absoluteOrderTotal(
256
+ ckb2UdtValue: bigint,
257
+ udt2CkbValue: bigint,
258
+ info: OrderData["info"],
259
+ ): bigint {
260
+ if (ckb2UdtValue === 0n) {
261
+ return udt2CkbValue;
262
+ }
263
+ if (udt2CkbValue === 0n) {
264
+ return ckb2UdtValue;
265
+ }
266
+
267
+ const { ckbToUdt, udtToCkb } = info;
268
+ return (
269
+ (ckb2UdtValue * udtToCkb.ckbScale * udtToCkb.udtScale +
270
+ udt2CkbValue * ckbToUdt.ckbScale * ckbToUdt.udtScale) >>
271
+ 1n
272
+ );
273
+ }
274
+
275
+ function absoluteOrderProgress(options: {
276
+ absTotal: bigint;
277
+ ckbUnoccupied: bigint;
278
+ isCkb2Udt: boolean;
279
+ isDualRatio: boolean;
280
+ udtScale: bigint;
281
+ udtToCkbCkbScale: bigint;
282
+ udtValue: bigint;
283
+ }): bigint {
284
+ if (options.isDualRatio) {
285
+ return options.absTotal;
286
+ }
287
+ if (options.isCkb2Udt) {
288
+ return options.udtValue * options.udtScale;
289
+ }
290
+ return options.ckbUnoccupied * options.udtToCkbCkbScale;
291
+ }
292
+
293
+ function betterOrderResolution(
294
+ current: OrderResolution | undefined,
295
+ candidate: OrderCell,
296
+ ): OrderResolution {
297
+ if (current === undefined || compareOrderScore(current.order, candidate) < 0) {
298
+ return { isAmbiguous: false, order: candidate };
299
+ }
300
+ if (compareOrderScore(current.order, candidate) > 0) {
301
+ return current;
302
+ }
303
+ if (current.order.cell.outPoint.eq(candidate.cell.outPoint)) {
304
+ return current;
305
+ }
306
+ if (candidate.data.isMint() && !current.order.data.isMint()) {
307
+ return { isAmbiguous: false, order: candidate };
308
+ }
309
+ if (!candidate.data.isMint() && current.order.data.isMint()) {
310
+ return current;
311
+ }
312
+ return { ...current, isAmbiguous: true };
313
+ }
314
+
315
+ function compareOrderScore(left: OrderCell, right: OrderCell): number {
316
+ if (left.absProgress !== right.absProgress) {
317
+ return left.absProgress < right.absProgress ? -1 : 1;
318
+ }
319
+ if (left.absTotal !== right.absTotal) {
320
+ return left.absTotal < right.absTotal ? -1 : 1;
321
+ }
322
+ return 0;
323
+ }
324
+
325
+ /**
326
+ * Represents a master cell
327
+ */
328
+ export class MasterCell implements ValueComponents {
329
+ /** Raw live master cell that anchors an order group. */
330
+ public readonly cell: ccc.Cell;
331
+
332
+ /**
333
+ * Gets the UDT value of the cell.
334
+ *
335
+ * For a Master Cell, the UDT amount is always zero.
336
+ *
337
+ * @returns The UDT amount as a `ccc.FixedPoint` (0n).
338
+ */
339
+ public readonly udtValue = 0n;
340
+
341
+ /**
342
+ * Creates an instance of MasterCell.
343
+ * @param cell - The ccc.Cell instance to be wrapped by the MasterCell.
344
+ */
345
+ constructor(cell: ccc.Cell) {
346
+ this.cell = cell;
347
+ }
348
+
349
+ /**
350
+ * Gets the CKB value of the cell.
351
+ *
352
+ * @returns The total CKB amount as a `ccc.FixedPoint`.
353
+ */
354
+ public get ckbValue(): ccc.FixedPoint {
355
+ return this.cell.cellOutput.capacity;
356
+ }
357
+
358
+ /**
359
+ * Validates the MasterCell against an OrderCell.
360
+ * @param order - The OrderCell to validate against.
361
+ * @throws Error if the order script is different or if the master is different.
362
+ */
363
+ public validate(order: OrderCell): void {
364
+ if (this.cell.cellOutput.type?.eq(order.cell.cellOutput.lock) !== true) {
365
+ throw new Error("Order script different");
366
+ }
367
+
368
+ if (!order.getMaster().eq(this.cell.outPoint)) {
369
+ throw new Error("Master is different");
370
+ }
371
+ }
372
+ }
373
+
374
+ /**
375
+ * Represents a group of orders associated with a master cell.
376
+ */
377
+ export class OrderGroup implements ValueComponents {
378
+ /** Master cell that authorizes and anchors the current order. */
379
+ public readonly master: MasterCell;
380
+ /** Current resolved order cell. */
381
+ public readonly order: OrderCell;
382
+ /** Origin order used to validate descendant progress and identity. */
383
+ public readonly origin: OrderCell;
384
+ /** The block that committed the origin, when known: how long the order has been on the book. */
385
+ public readonly blockNumber: ccc.Num | undefined;
386
+
387
+ /**
388
+ * Creates an instance of OrderGroup.
389
+ * @param master - The master cell associated with the order group.
390
+ * @param order - The order within the group.
391
+ * @param origin - The original order associated with the group.
392
+ * @param blockNumber - The block that committed the origin, if committed.
393
+ */
394
+ constructor(
395
+ master: MasterCell,
396
+ order: OrderCell,
397
+ origin: OrderCell,
398
+ blockNumber?: ccc.Num,
399
+ ) {
400
+ this.master = master;
401
+ this.order = order;
402
+ this.origin = origin;
403
+ this.blockNumber = blockNumber;
404
+ }
405
+
406
+ /**
407
+ * Gets the CKB value of the group.
408
+ *
409
+ * @returns The total CKB amount as a `ccc.FixedPoint`, which is the sum of the CKB values of the order cell and the master cell.
410
+ */
411
+ public get ckbValue(): ccc.FixedPoint {
412
+ return this.order.cell.cellOutput.capacity + this.master.cell.cellOutput.capacity;
413
+ }
414
+
415
+ /**
416
+ * Gets the UDT value of the group.
417
+ *
418
+ * @returns The UDT amount as a `ccc.FixedPoint`, derived from the order cell.
419
+ */
420
+ public get udtValue(): ccc.FixedPoint {
421
+ return this.order.data.udtValue;
422
+ }
423
+
424
+ /**
425
+ * Tries to create an OrderGroup from the provided parameters.
426
+ * @param master - The master cell.
427
+ * @param order - The order within the group.
428
+ * @param origin - The original order.
429
+ * @returns An OrderGroup instance or undefined if creation fails.
430
+ */
431
+ public static tryFrom(
432
+ master: MasterCell,
433
+ order: OrderCell,
434
+ origin: OrderCell,
435
+ blockNumber?: ccc.Num,
436
+ ): OrderGroup | undefined {
437
+ const og = new OrderGroup(master, order, origin, blockNumber);
438
+ if (og.isValid()) {
439
+ return og;
440
+ }
441
+ return undefined;
442
+ }
443
+
444
+ /**
445
+ * Validates the order group against its master and origin orders.
446
+ * @throws Will throw an error if validation fails.
447
+ */
448
+ public validate(): void {
449
+ this.master.validate(this.origin);
450
+ this.master.validate(this.order);
451
+ this.origin.validate(this.order);
452
+ }
453
+
454
+ /**
455
+ * Checks if the order group is valid.
456
+ * @returns True if the order group is valid, otherwise false.
457
+ */
458
+ public isValid(): boolean {
459
+ try {
460
+ this.validate();
461
+ return true;
462
+ } catch {
463
+ return false;
464
+ }
465
+ }
466
+
467
+ /**
468
+ * Checks if the specified lock is the owner of the master cell.
469
+ *
470
+ * @param locks - The locks to check ownership against.
471
+ * @returns True if the lock is the owner, otherwise false.
472
+ */
473
+ public isOwner(...locks: ccc.Script[]): boolean {
474
+ const lock = this.master.cell.cellOutput.lock;
475
+ return locks.some((l) => lock.eq(l));
476
+ }
477
+ }
478
+
479
+ /**
480
+ * Records canonical resolver output without exposing forgeable provenance data. The
481
+ * group and its parsed parts are frozen, since matching and transactions build on them.
482
+ */
483
+ export function attestResolvedOrderGroup(group: OrderGroup): OrderGroup {
484
+ group.validate();
485
+ for (const order of [group.order, group.origin]) {
486
+ const { info } = order.data;
487
+ for (const part of [
488
+ order,
489
+ order.data,
490
+ order.data.master,
491
+ info,
492
+ info.ckbToUdt,
493
+ info.udtToCkb,
494
+ ]) {
495
+ Object.freeze(part);
496
+ }
497
+ }
498
+ Object.freeze(group.master);
499
+ resolvedOrderGroups.set(group.order.cell, Object.freeze(group));
500
+ return group;
501
+ }
502
+
503
+ /** Returns the resolver's group at a matching or transaction boundary. */
504
+ export function validatedOrderGroup(group: OrderGroup): OrderGroup {
505
+ if (!(group instanceof OrderGroup)) {
506
+ throw new TypeError("Matching requires resolved OrderGroups from findOrders()");
507
+ }
508
+
509
+ const attested = resolvedOrderGroups.get(group.order.cell);
510
+ if (attested === undefined) {
511
+ throw new TypeError("OrderGroup was not produced by the order resolver");
512
+ }
513
+ if (
514
+ group.master.cell !== attested.master.cell ||
515
+ group.origin.cell !== attested.origin.cell
516
+ ) {
517
+ throw new TypeError("OrderGroup does not match its resolver attestation");
518
+ }
519
+ return attested;
520
+ }
@@ -0,0 +1,173 @@
1
+ import { ccc } from "@ckb-ccc/core";
2
+ import {
3
+ ceilDiv,
4
+ minBigInt,
5
+ type ExchangeRatio,
6
+ type ValueComponents,
7
+ } from "../utils/utils.ts";
8
+ import { Info } from "./info.ts";
9
+ import { Ratio } from "./ratio.ts";
10
+
11
+ const maxUint64 = (1n << 64n) - 1n;
12
+
13
+ /**
14
+ * Error thrown when an exact quote cannot be represented by the order ratio format.
15
+ */
16
+ export class OrderConversionRepresentabilityError extends Error {
17
+ /** Creates a representability error with the default public conversion message. */
18
+ constructor(
19
+ message = "Order conversion quote cannot be represented as Uint64 ratio",
20
+ options?: ErrorOptions,
21
+ ) {
22
+ super(message, options);
23
+ this.name = "OrderConversionRepresentabilityError";
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Quotes a new order: the output-side amount, the CKB fee, and the order info.
29
+ *
30
+ * @remarks
31
+ * The returned `Info` preserves the quoted amount after fee adjustment so the
32
+ * minted order records the executable limit price.
33
+ */
34
+ export function quoteConversion(
35
+ isCkb2Udt: boolean,
36
+ midpoint: ExchangeRatio,
37
+ amounts: ValueComponents,
38
+ options?: {
39
+ fee?: ccc.Num;
40
+ feeBase?: ccc.Num;
41
+ },
42
+ ): { convertedAmount: ccc.FixedPoint; ckbFee: ccc.FixedPoint; info: Info } {
43
+ const fee = options?.fee ?? 0n;
44
+ // Generic denominator for callers that pass a fee without a scale; Stack's
45
+ // own default pair is owned publicly by the SDK, not by this entity.
46
+ const feeBase = options?.feeBase ?? 100000n;
47
+ const base = Ratio.from(midpoint);
48
+ const amount = isCkb2Udt ? amounts.ckbValue : amounts.udtValue;
49
+ const { aScale, bScale } = feeAdjustedScales(base, isCkb2Udt, fee, feeBase);
50
+ const convertedAmount = ceilDiv(amount * aScale, bScale);
51
+ let ckbFee = 0n;
52
+
53
+ if (amount > 0n && fee !== 0n) {
54
+ ckbFee = isCkb2Udt
55
+ ? amount - base.convert(false, convertedAmount, false)
56
+ : base.convert(false, amount, false) - convertedAmount;
57
+ }
58
+
59
+ // Every order this SDK creates carries the default minimum match, the one the bot's
60
+ // remainder rule is sized to.
61
+ const info = Info.create(
62
+ isCkb2Udt,
63
+ quotePreservingRatio(amount, convertedAmount, isCkb2Udt),
64
+ );
65
+ return { convertedAmount, ckbFee, info };
66
+ }
67
+
68
+ /** The direction's scales with the fee taken out of the output side, reduced. */
69
+ function feeAdjustedScales(
70
+ ratio: Ratio,
71
+ isCkb2Udt: boolean,
72
+ fee: ccc.Num,
73
+ feeBase: ccc.Num,
74
+ ): { aScale: ccc.Num; bScale: ccc.Num } {
75
+ if (fee < 0n) {
76
+ throw new Error("Fee cannot be negative");
77
+ }
78
+ if (feeBase <= 0n) {
79
+ throw new Error("Fee base must be positive");
80
+ }
81
+ if (fee >= feeBase) {
82
+ throw new Error("Fee too big relative to feeBase");
83
+ }
84
+ if (!ratio.isPopulated()) {
85
+ throw new Error("Invalid ExchangeRatio");
86
+ }
87
+
88
+ let { ckbScale: aScale, udtScale: bScale } = ratio;
89
+ if (!isCkb2Udt) {
90
+ [aScale, bScale] = [bScale, aScale];
91
+ }
92
+ aScale *= feeBase - fee;
93
+ bScale *= feeBase;
94
+ const divisor = ccc.gcd(aScale, bScale);
95
+ return { aScale: aScale / divisor, bScale: bScale / divisor };
96
+ }
97
+
98
+ /**
99
+ * The ratio that reproduces the quote exactly: the greatest Uint64 fraction at most
100
+ * `quotedOutput / inputAmount`, which must still round the input up to the quoted output.
101
+ */
102
+ export function quotePreservingRatio(
103
+ inputAmount: ccc.FixedPoint,
104
+ quotedOutput: ccc.FixedPoint,
105
+ isCkb2Udt: boolean,
106
+ ): Ratio {
107
+ if (inputAmount < 0n || quotedOutput < 0n) {
108
+ throw new Error("Order conversion amounts cannot be negative");
109
+ }
110
+ if (inputAmount === 0n || quotedOutput === 0n) {
111
+ throw new OrderConversionRepresentabilityError(
112
+ "Order conversion quote must have positive input and output",
113
+ );
114
+ }
115
+
116
+ const { numerator, denominator } = greatestBoundedFractionAtMost(
117
+ quotedOutput,
118
+ inputAmount,
119
+ maxUint64,
120
+ );
121
+ if (numerator <= 0n || (quotedOutput - 1n) * denominator >= inputAmount * numerator) {
122
+ throw new OrderConversionRepresentabilityError();
123
+ }
124
+
125
+ return Ratio.from({
126
+ ckbScale: isCkb2Udt ? numerator : denominator,
127
+ udtScale: isCkb2Udt ? denominator : numerator,
128
+ });
129
+ }
130
+
131
+ /**
132
+ * The greatest fraction at most `numerator / denominator` whose terms are at most
133
+ * `maxTerm`, reduced. It walks the continued fraction's convergents while they fit; the
134
+ * first that does not is replaced by the largest semiconvergent the bound allows, which
135
+ * is closer to the target than the last fitting convergent on the same side of it. Every
136
+ * other convergent lands above the target and is skipped.
137
+ */
138
+ export function greatestBoundedFractionAtMost(
139
+ numerator: bigint,
140
+ denominator: bigint,
141
+ maxTerm: bigint,
142
+ ): { numerator: bigint; denominator: bigint } {
143
+ let best = { numerator: 0n, denominator: 1n };
144
+ let [previousP, previousQ, p, q] = [0n, 1n, 1n, 0n];
145
+ let [n, d] = [numerator, denominator];
146
+ while (d !== 0n) {
147
+ const quotient = n / d;
148
+ let term = quotient;
149
+ if (p !== 0n) {
150
+ term = minBigInt(term, (maxTerm - previousP) / p);
151
+ }
152
+ if (q !== 0n) {
153
+ term = minBigInt(term, (maxTerm - previousQ) / q);
154
+ }
155
+ const candidate = {
156
+ numerator: term * p + previousP,
157
+ denominator: term * q + previousQ,
158
+ };
159
+ if (
160
+ candidate.denominator > 0n &&
161
+ candidate.numerator * denominator <= candidate.denominator * numerator &&
162
+ candidate.numerator * best.denominator > best.numerator * candidate.denominator
163
+ ) {
164
+ best = candidate;
165
+ }
166
+ if (term < quotient) {
167
+ break;
168
+ }
169
+ [previousP, previousQ, p, q] = [p, q, candidate.numerator, candidate.denominator];
170
+ [n, d] = [d, n % d];
171
+ }
172
+ return best;
173
+ }