@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.
- package/README.md +81 -32
- package/dist/constants.d.ts +13 -22
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +80 -76
- package/dist/constants.js.map +1 -1
- package/dist/conversion/estimate.d.ts +39 -0
- package/dist/conversion/estimate.d.ts.map +1 -0
- package/dist/conversion/estimate.js +117 -0
- package/dist/conversion/estimate.js.map +1 -0
- package/dist/conversion/maturity.d.ts +15 -0
- package/dist/conversion/maturity.d.ts.map +1 -0
- package/dist/conversion/maturity.js +112 -0
- package/dist/conversion/maturity.js.map +1 -0
- package/dist/conversion/plans.d.ts +20 -0
- package/dist/conversion/plans.d.ts.map +1 -0
- package/dist/conversion/plans.js +106 -0
- package/dist/conversion/plans.js.map +1 -0
- package/dist/conversion/projection.d.ts +22 -0
- package/dist/conversion/projection.d.ts.map +1 -0
- package/dist/conversion/projection.js +91 -0
- package/dist/conversion/projection.js.map +1 -0
- package/dist/conversion/types.d.ts +252 -0
- package/dist/conversion/types.d.ts.map +1 -0
- package/dist/conversion/types.js +2 -0
- package/dist/conversion/types.js.map +1 -0
- package/dist/conversion/withdrawal_ring.d.ts +36 -0
- package/dist/conversion/withdrawal_ring.d.ts.map +1 -0
- package/dist/conversion/withdrawal_ring.js +87 -0
- package/dist/conversion/withdrawal_ring.js.map +1 -0
- package/dist/dao.d.ts +86 -0
- package/dist/dao.d.ts.map +1 -0
- package/dist/dao.js +113 -0
- package/dist/dao.js.map +1 -0
- package/dist/index.d.ts +51 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -3
- package/dist/index.js.map +1 -1
- package/dist/logic.d.ts +85 -0
- package/dist/logic.d.ts.map +1 -0
- package/dist/logic.js +184 -0
- package/dist/logic.js.map +1 -0
- package/dist/order/cells.d.ts +207 -0
- package/dist/order/cells.d.ts.map +1 -0
- package/dist/order/cells.js +427 -0
- package/dist/order/cells.js.map +1 -0
- package/dist/order/conversion.d.ts +43 -0
- package/dist/order/conversion.d.ts.map +1 -0
- package/dist/order/conversion.js +123 -0
- package/dist/order/conversion.js.map +1 -0
- package/dist/order/fee.d.ts +9 -0
- package/dist/order/fee.d.ts.map +1 -0
- package/dist/order/fee.js +24 -0
- package/dist/order/fee.js.map +1 -0
- package/dist/order/fill.d.ts +36 -0
- package/dist/order/fill.d.ts.map +1 -0
- package/dist/order/fill.js +53 -0
- package/dist/order/fill.js.map +1 -0
- package/dist/order/info.d.ts +62 -0
- package/dist/order/info.d.ts.map +1 -0
- package/dist/order/info.js +99 -0
- package/dist/order/info.js.map +1 -0
- package/dist/order/master.d.ts +49 -0
- package/dist/order/master.d.ts.map +1 -0
- package/dist/order/master.js +27 -0
- package/dist/order/master.js.map +1 -0
- package/dist/order/matcher.d.ts +43 -0
- package/dist/order/matcher.d.ts.map +1 -0
- package/dist/order/matcher.js +123 -0
- package/dist/order/matcher.js.map +1 -0
- package/dist/order/order.d.ts +59 -0
- package/dist/order/order.d.ts.map +1 -0
- package/dist/order/order.js +239 -0
- package/dist/order/order.js.map +1 -0
- package/dist/order/order_data.d.ts +51 -0
- package/dist/order/order_data.d.ts.map +1 -0
- package/dist/order/order_data.js +64 -0
- package/dist/order/order_data.js.map +1 -0
- package/dist/order/ratio.d.ts +42 -0
- package/dist/order/ratio.d.ts.map +1 -0
- package/dist/order/ratio.js +82 -0
- package/dist/order/ratio.js.map +1 -0
- package/dist/order/relative.d.ts +43 -0
- package/dist/order/relative.d.ts.map +1 -0
- package/dist/order/relative.js +54 -0
- package/dist/order/relative.js.map +1 -0
- package/dist/owned_owner.d.ts +113 -0
- package/dist/owned_owner.d.ts.map +1 -0
- package/dist/owned_owner.js +280 -0
- package/dist/owned_owner.js.map +1 -0
- package/dist/sdk.d.ts +71 -42
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +388 -209
- package/dist/sdk.js.map +1 -1
- package/dist/send/sign_and_send_transaction.d.ts +46 -0
- package/dist/send/sign_and_send_transaction.d.ts.map +1 -0
- package/dist/send/sign_and_send_transaction.js +101 -0
- package/dist/send/sign_and_send_transaction.js.map +1 -0
- package/dist/send/wait_transaction.d.ts +41 -0
- package/dist/send/wait_transaction.d.ts.map +1 -0
- package/dist/send/wait_transaction.js +204 -0
- package/dist/send/wait_transaction.js.map +1 -0
- package/dist/udt.d.ts +123 -0
- package/dist/udt.d.ts.map +1 -0
- package/dist/udt.js +244 -0
- package/dist/udt.js.map +1 -0
- package/dist/utils/chain.d.ts +38 -0
- package/dist/utils/chain.d.ts.map +1 -0
- package/dist/utils/chain.js +20 -0
- package/dist/utils/chain.js.map +1 -0
- package/dist/utils/codec.d.ts +19 -0
- package/dist/utils/codec.d.ts.map +1 -0
- package/dist/utils/codec.js +66 -0
- package/dist/utils/codec.js.map +1 -0
- package/dist/utils/transaction_header.d.ts +17 -0
- package/dist/utils/transaction_header.d.ts.map +1 -0
- package/dist/utils/transaction_header.js +44 -0
- package/dist/utils/transaction_header.js.map +1 -0
- package/dist/utils/utils.d.ts +126 -0
- package/dist/utils/utils.d.ts.map +1 -0
- package/dist/utils/utils.js +151 -0
- package/dist/utils/utils.js.map +1 -0
- package/package.json +19 -26
- package/src/constants.ts +84 -181
- package/src/conversion/estimate.ts +157 -0
- package/src/conversion/maturity.ts +159 -0
- package/src/conversion/plans.ts +144 -0
- package/src/conversion/projection.ts +128 -0
- package/src/conversion/types.ts +287 -0
- package/src/conversion/withdrawal_ring.ts +126 -0
- package/src/dao.ts +172 -0
- package/src/index.ts +89 -3
- package/src/logic.ts +295 -0
- package/src/order/cells.ts +520 -0
- package/src/order/conversion.ts +173 -0
- package/src/order/fee.ts +30 -0
- package/src/order/fill.ts +83 -0
- package/src/order/info.ts +178 -0
- package/src/order/master.ts +42 -0
- package/src/order/matcher.ts +191 -0
- package/src/order/order.ts +302 -0
- package/src/order/order_data.ts +128 -0
- package/src/order/ratio.ts +144 -0
- package/src/order/relative.ts +106 -0
- package/src/owned_owner.ts +394 -0
- package/src/sdk.ts +555 -440
- package/src/send/sign_and_send_transaction.ts +120 -0
- package/src/send/wait_transaction.ts +271 -0
- package/src/udt.ts +317 -0
- package/src/utils/chain.ts +43 -0
- package/src/utils/codec.ts +74 -0
- package/src/utils/transaction_header.ts +66 -0
- package/src/utils/utils.ts +237 -0
- package/dist/codec.d.ts +0 -3
- package/dist/codec.d.ts.map +0 -1
- package/dist/codec.js +0 -58
- package/dist/codec.js.map +0 -1
- package/src/codec.ts +0 -138
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
import { ccc } from "@ckb-ccc/core";
|
|
2
|
+
import { findCells, type ScriptDeps, type ValueComponents } from "../utils/utils.ts";
|
|
3
|
+
import {
|
|
4
|
+
attestResolvedOrderGroup,
|
|
5
|
+
MasterCell,
|
|
6
|
+
OrderCell,
|
|
7
|
+
OrderGroup,
|
|
8
|
+
validatedOrderGroup,
|
|
9
|
+
} from "./cells.ts";
|
|
10
|
+
import { Info, type InfoLike } from "./info.ts";
|
|
11
|
+
import type { Match } from "./matcher.ts";
|
|
12
|
+
import { OrderData } from "./order_data.ts";
|
|
13
|
+
import { Relative } from "./relative.ts";
|
|
14
|
+
|
|
15
|
+
type TransactionResponse = Awaited<ReturnType<ccc.Client["getTransaction"]>>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Builds and scans iCKB Stack order cells for one order script deployment.
|
|
19
|
+
*/
|
|
20
|
+
export class OrderManager implements ScriptDeps {
|
|
21
|
+
/** Order lock script this manager scans and builds for. */
|
|
22
|
+
public readonly script: ccc.Script;
|
|
23
|
+
/** Cell deps required to execute the order script. */
|
|
24
|
+
public readonly cellDeps: ccc.CellDep[];
|
|
25
|
+
/** UDT type script accepted by this order market. */
|
|
26
|
+
public readonly udtScript: ccc.Script;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Creates an order manager for one order script, its cell deps, and UDT type.
|
|
30
|
+
*/
|
|
31
|
+
constructor(script: ccc.Script, cellDeps: ccc.CellDep[], udtScript: ccc.Script) {
|
|
32
|
+
this.script = script;
|
|
33
|
+
this.cellDeps = cellDeps;
|
|
34
|
+
this.udtScript = udtScript;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Returns true when the cell is an order cell for this manager's scripts. */
|
|
38
|
+
public isOrder(cell: ccc.Cell): boolean {
|
|
39
|
+
return (
|
|
40
|
+
cell.cellOutput.lock.eq(this.script) &&
|
|
41
|
+
Boolean(cell.cellOutput.type?.eq(this.udtScript))
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Adds a new order cell and its master cell to a partial transaction.
|
|
47
|
+
*
|
|
48
|
+
* @remarks
|
|
49
|
+
* The order output is locked by the order script and typed by the configured
|
|
50
|
+
* UDT. The following master output is typed by the order script and locked by
|
|
51
|
+
* the caller-provided lock.
|
|
52
|
+
*/
|
|
53
|
+
public mint(
|
|
54
|
+
txLike: ccc.TransactionLike,
|
|
55
|
+
lock: ccc.Script,
|
|
56
|
+
infoLike: InfoLike,
|
|
57
|
+
{ ckbValue, udtValue }: ValueComponents,
|
|
58
|
+
): ccc.Transaction {
|
|
59
|
+
const tx = ccc.Transaction.from(txLike);
|
|
60
|
+
const data = OrderData.from({
|
|
61
|
+
udtValue,
|
|
62
|
+
master: { type: "relative", value: Relative.create(1n) },
|
|
63
|
+
info: Info.from(infoLike),
|
|
64
|
+
});
|
|
65
|
+
data.validate();
|
|
66
|
+
if (ckbValue < 0n) {
|
|
67
|
+
throw new Error("ckbValue invalid, negative");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
tx.addCellDeps(this.cellDeps);
|
|
71
|
+
const outputCount = tx.addOutput(
|
|
72
|
+
{ lock: this.script, type: this.udtScript },
|
|
73
|
+
data.toBytes(),
|
|
74
|
+
);
|
|
75
|
+
const orderOutput = tx.outputs[outputCount - 1];
|
|
76
|
+
if (orderOutput === undefined) {
|
|
77
|
+
throw new Error("Failed to append order output");
|
|
78
|
+
}
|
|
79
|
+
orderOutput.capacity += ckbValue;
|
|
80
|
+
tx.addOutput({ lock, type: this.script });
|
|
81
|
+
return tx;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Adds inputs and partial outputs for a chosen match.
|
|
86
|
+
*/
|
|
87
|
+
public addMatch(txLike: ccc.TransactionLike, match: Match): ccc.Transaction {
|
|
88
|
+
const tx = ccc.Transaction.from(txLike);
|
|
89
|
+
const partials = match.partials.map((partial) => this.validatedPartial(partial));
|
|
90
|
+
let ckbDelta = 0n;
|
|
91
|
+
let udtDelta = 0n;
|
|
92
|
+
for (const { group, ckbOut, udtOut } of partials) {
|
|
93
|
+
ckbDelta += group.order.ckbValue - ckbOut;
|
|
94
|
+
udtDelta += group.order.udtValue - udtOut;
|
|
95
|
+
}
|
|
96
|
+
if (match.ckbDelta !== ckbDelta || match.udtDelta !== udtDelta) {
|
|
97
|
+
throw new Error("Match deltas do not match partial order accounting");
|
|
98
|
+
}
|
|
99
|
+
if (partials.length === 0) {
|
|
100
|
+
return tx;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
tx.addCellDeps(this.cellDeps);
|
|
104
|
+
for (const { group, ckbOut, udtOut } of partials) {
|
|
105
|
+
const { order } = group;
|
|
106
|
+
tx.addInput(order.cell);
|
|
107
|
+
tx.addOutput(
|
|
108
|
+
{ lock: this.script, type: this.udtScript, capacity: ckbOut },
|
|
109
|
+
OrderData.from({
|
|
110
|
+
udtValue: udtOut,
|
|
111
|
+
master: { type: "absolute", value: order.getMaster() },
|
|
112
|
+
info: order.data.info,
|
|
113
|
+
}).toBytes(),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
return tx;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Adds order groups and their master cells as melt inputs.
|
|
121
|
+
*
|
|
122
|
+
* @remarks Melts exactly the groups it is given: callers pass fulfilled groups
|
|
123
|
+
* to collect and a live group to cancel it.
|
|
124
|
+
*/
|
|
125
|
+
public melt(txLike: ccc.TransactionLike, groups: OrderGroup[]): ccc.Transaction {
|
|
126
|
+
const tx = ccc.Transaction.from(txLike);
|
|
127
|
+
const selected = groups.map(validatedOrderGroup);
|
|
128
|
+
if (selected.length === 0) {
|
|
129
|
+
return tx;
|
|
130
|
+
}
|
|
131
|
+
for (const group of selected) {
|
|
132
|
+
if (!this.isOrder(group.order.cell)) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
`Melt order ${group.order.cell.outPoint.toHex()} does not match this order manager`,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
tx.addCellDeps(this.cellDeps);
|
|
139
|
+
for (const group of selected) {
|
|
140
|
+
tx.addInput(group.order.cell);
|
|
141
|
+
tx.addInput(group.master.cell);
|
|
142
|
+
}
|
|
143
|
+
return tx;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Finds valid order groups by scanning order cells and master cells. Unresolved or
|
|
148
|
+
* invalid groups are skipped.
|
|
149
|
+
*/
|
|
150
|
+
public async findOrders(client: ccc.Client): Promise<OrderGroup[]> {
|
|
151
|
+
const [orders, masters] = await Promise.all([
|
|
152
|
+
this.findSimpleOrders(client),
|
|
153
|
+
this.findAllMasters(client),
|
|
154
|
+
]);
|
|
155
|
+
const rawGroups = new Map(
|
|
156
|
+
masters.map((master) => [
|
|
157
|
+
master.cell.outPoint.toHex(),
|
|
158
|
+
{ master, orders: new Array<OrderCell>() },
|
|
159
|
+
]),
|
|
160
|
+
);
|
|
161
|
+
for (const order of orders) {
|
|
162
|
+
rawGroups.get(order.getMaster().toHex())?.orders.push(order);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Each group's origin is one transaction read; resolving them together turns a book of
|
|
166
|
+
// fifty orders from seconds of round trips into one.
|
|
167
|
+
const groups = await Promise.all(
|
|
168
|
+
[...rawGroups.values()]
|
|
169
|
+
.filter((rawGroup) => rawGroup.orders.length > 0)
|
|
170
|
+
.map(async ({ master, orders: candidates }) =>
|
|
171
|
+
this.resolveOrderGroup(client, master, candidates),
|
|
172
|
+
),
|
|
173
|
+
);
|
|
174
|
+
return groups.filter((group) => group !== undefined);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
private async findSimpleOrders(client: ccc.Client): Promise<OrderCell[]> {
|
|
178
|
+
const cells = await findCells(client, {
|
|
179
|
+
script: this.script,
|
|
180
|
+
scriptType: "lock",
|
|
181
|
+
filter: { script: this.udtScript },
|
|
182
|
+
scriptSearchMode: "exact",
|
|
183
|
+
withData: true,
|
|
184
|
+
});
|
|
185
|
+
return cells.flatMap((cell) => {
|
|
186
|
+
const order = OrderCell.tryFrom(cell);
|
|
187
|
+
return order !== undefined && this.isOrder(cell) ? [order] : [];
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private async findAllMasters(client: ccc.Client): Promise<MasterCell[]> {
|
|
192
|
+
// An exact type search returns master cells only, so nothing re-filters here.
|
|
193
|
+
const cells = await findCells(client, {
|
|
194
|
+
script: this.script,
|
|
195
|
+
scriptType: "type",
|
|
196
|
+
scriptSearchMode: "exact",
|
|
197
|
+
withData: true,
|
|
198
|
+
});
|
|
199
|
+
return cells.map((cell) => new MasterCell(cell));
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Resolves one master and its descendant orders into a validated order group. */
|
|
203
|
+
private async resolveOrderGroup(
|
|
204
|
+
client: ccc.Client,
|
|
205
|
+
master: MasterCell,
|
|
206
|
+
orders: OrderCell[],
|
|
207
|
+
): Promise<OrderGroup | undefined> {
|
|
208
|
+
const found = await this.findOrigin(client, master.cell.outPoint);
|
|
209
|
+
const order = found?.origin.resolve(orders);
|
|
210
|
+
if (found === undefined || order === undefined) {
|
|
211
|
+
return undefined;
|
|
212
|
+
}
|
|
213
|
+
const group = OrderGroup.tryFrom(master, order, found.origin, found.blockNumber);
|
|
214
|
+
// A dual-ratio order, valid on chain but placed by nothing in the stack, is left to
|
|
215
|
+
// whoever placed it: neither matched, estimated, shown nor melted here.
|
|
216
|
+
if (group === undefined || group.order.data.info.isDualRatio()) {
|
|
217
|
+
return undefined;
|
|
218
|
+
}
|
|
219
|
+
return attestResolvedOrderGroup(group);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* The mint order created beside the master, read from the master's transaction: the
|
|
224
|
+
* client cache first, then the node, recording the response for the next read. The
|
|
225
|
+
* response's block number dates the order; an uncommitted origin has none.
|
|
226
|
+
*/
|
|
227
|
+
private async findOrigin(
|
|
228
|
+
client: ccc.Client,
|
|
229
|
+
master: ccc.OutPoint,
|
|
230
|
+
): Promise<{ origin: OrderCell; blockNumber: ccc.Num | undefined } | undefined> {
|
|
231
|
+
const { txHash, index: masterIndex } = master;
|
|
232
|
+
const response = await cachedTransactionResponse(client, txHash);
|
|
233
|
+
if (response === undefined) {
|
|
234
|
+
return undefined;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let origin: OrderCell | undefined;
|
|
238
|
+
for (const [i, output] of [...response.transaction.outputCells].entries()) {
|
|
239
|
+
const index = BigInt(i);
|
|
240
|
+
if (index === masterIndex) {
|
|
241
|
+
continue;
|
|
242
|
+
}
|
|
243
|
+
const cell = ccc.Cell.from({
|
|
244
|
+
cellOutput: output.cellOutput,
|
|
245
|
+
outputData: output.outputData,
|
|
246
|
+
outPoint: { txHash, index },
|
|
247
|
+
});
|
|
248
|
+
const order = OrderCell.tryFrom(cell);
|
|
249
|
+
if (
|
|
250
|
+
order === undefined ||
|
|
251
|
+
!this.isOrder(cell) ||
|
|
252
|
+
!order.data.isMint() ||
|
|
253
|
+
!order.getMaster().eq(master)
|
|
254
|
+
) {
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
if (origin !== undefined) {
|
|
258
|
+
// Two mint orders pointing at one master: the group is ambiguous, skip it.
|
|
259
|
+
return undefined;
|
|
260
|
+
}
|
|
261
|
+
origin = order;
|
|
262
|
+
}
|
|
263
|
+
return origin === undefined
|
|
264
|
+
? undefined
|
|
265
|
+
: { origin, blockNumber: response.blockNumber };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
private validatedPartial({
|
|
269
|
+
group,
|
|
270
|
+
ckbOut,
|
|
271
|
+
udtOut,
|
|
272
|
+
}: Match["partials"][number]): Match["partials"][number] {
|
|
273
|
+
const validated = validatedOrderGroup(group);
|
|
274
|
+
const outPoint = validated.order.cell.outPoint.toHex();
|
|
275
|
+
if (!this.isOrder(validated.order.cell)) {
|
|
276
|
+
throw new Error(`Match order ${outPoint} does not match this order manager`);
|
|
277
|
+
}
|
|
278
|
+
if (ckbOut < 0n) {
|
|
279
|
+
throw new Error(`Match order ${outPoint} has negative CKB output`);
|
|
280
|
+
}
|
|
281
|
+
if (udtOut < 0n) {
|
|
282
|
+
throw new Error(`Match order ${outPoint} has negative UDT output`);
|
|
283
|
+
}
|
|
284
|
+
return { group: validated, ckbOut, udtOut };
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async function cachedTransactionResponse(
|
|
289
|
+
client: ccc.Client,
|
|
290
|
+
txHash: ccc.Hex,
|
|
291
|
+
): Promise<TransactionResponse | undefined> {
|
|
292
|
+
// As CCC's own readers do, trust the cache only once the transaction is in a block: the
|
|
293
|
+
// cache also holds what CCC records at broadcast and what a lagging node answered, and a
|
|
294
|
+
// long-lived client would otherwise keep that answer for good.
|
|
295
|
+
const cached = await client.cache.getTransactionResponse(txHash);
|
|
296
|
+
if (cached?.blockNumber !== undefined) {
|
|
297
|
+
return cached;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// CCC's read records a found response in the cache itself.
|
|
301
|
+
return client.getTransaction(txHash);
|
|
302
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { ccc, mol } from "@ckb-ccc/core";
|
|
2
|
+
import { CheckedUint128LE } from "../utils/codec.ts";
|
|
3
|
+
import { Info, type InfoLike } from "./info.ts";
|
|
4
|
+
import {
|
|
5
|
+
MasterCodec,
|
|
6
|
+
masterFrom,
|
|
7
|
+
masterValidate,
|
|
8
|
+
type Master,
|
|
9
|
+
type MasterLike,
|
|
10
|
+
} from "./master.ts";
|
|
11
|
+
|
|
12
|
+
const maxUint128 = (1n << 128n) - 1n;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Wire shape for order cell data.
|
|
16
|
+
*/
|
|
17
|
+
export interface OrderDataLike {
|
|
18
|
+
/** UDT amount held by the order cell. */
|
|
19
|
+
udtValue: ccc.FixedPointLike;
|
|
20
|
+
/** Relative or absolute pointer to the master cell. */
|
|
21
|
+
master: MasterLike;
|
|
22
|
+
/** Price and minimum-match metadata. */
|
|
23
|
+
info: InfoLike;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const OrderDataCodec = mol.struct({
|
|
27
|
+
udtValue: CheckedUint128LE,
|
|
28
|
+
master: MasterCodec,
|
|
29
|
+
info: Info,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const OrderBase = ccc.Entity.Base<OrderDataLike, OrderData>();
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Serialized order cell payload.
|
|
36
|
+
*/
|
|
37
|
+
export interface OrderData {
|
|
38
|
+
/** UDT amount held by the order cell. */
|
|
39
|
+
udtValue: ccc.FixedPoint;
|
|
40
|
+
/** Master-cell pointer. */
|
|
41
|
+
master: Master;
|
|
42
|
+
/** Price and minimum-match metadata. */
|
|
43
|
+
info: Info;
|
|
44
|
+
/** Creates a copy of this order payload. */
|
|
45
|
+
clone(): OrderData;
|
|
46
|
+
/** Returns whether another value has the same order payload. */
|
|
47
|
+
eq(other: OrderDataLike): boolean;
|
|
48
|
+
/** Resolves a relative master against `current`, or returns the absolute master. */
|
|
49
|
+
getMaster(current: ccc.OutPoint): ccc.OutPoint;
|
|
50
|
+
/** Returns the CKB hash of the serialized order payload. */
|
|
51
|
+
hash(): ccc.Hex;
|
|
52
|
+
/** Returns whether the master pointer is relative to the current output. */
|
|
53
|
+
isMint(): boolean;
|
|
54
|
+
/** Serializes the order payload to bytes. */
|
|
55
|
+
toBytes(): ccc.Bytes;
|
|
56
|
+
/** Serializes the order payload to full-width hexadecimal. */
|
|
57
|
+
toHex(): ccc.Hex;
|
|
58
|
+
/** Throws if the UDT value, master pointer, or info is invalid. */
|
|
59
|
+
validate(): void;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// eslint-disable-next-line @typescript-eslint/no-shadow -- Preserve the runtime constructor name.
|
|
63
|
+
const OrderDataImplementation = class OrderData extends OrderBase {
|
|
64
|
+
static {
|
|
65
|
+
ccc.codec(OrderDataCodec)(this);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** UDT amount held by the order cell. */
|
|
69
|
+
public readonly udtValue: ccc.FixedPoint;
|
|
70
|
+
/** Master-cell pointer. */
|
|
71
|
+
public readonly master: Master;
|
|
72
|
+
/** Price and minimum-match metadata. */
|
|
73
|
+
public readonly info: Info;
|
|
74
|
+
|
|
75
|
+
/** Creates normalized order data. */
|
|
76
|
+
constructor(udtValue: ccc.FixedPoint, master: Master, info: Info) {
|
|
77
|
+
super();
|
|
78
|
+
this.udtValue = udtValue;
|
|
79
|
+
this.master = master;
|
|
80
|
+
this.info = info;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Normalizes an `OrderDataLike` wire object or existing entity into `OrderData`. */
|
|
84
|
+
public static override from(data: OrderDataLike): OrderData {
|
|
85
|
+
if (data instanceof OrderData) {
|
|
86
|
+
return data;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const { udtValue, master, info } = data;
|
|
90
|
+
return new OrderData(ccc.numFrom(udtValue), masterFrom(master), Info.from(info));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Throws when the order payload is not internally valid. */
|
|
94
|
+
public validate(): void {
|
|
95
|
+
if (this.udtValue < 0n) {
|
|
96
|
+
throw new Error("udtValue invalid, negative");
|
|
97
|
+
}
|
|
98
|
+
if (this.udtValue > maxUint128) {
|
|
99
|
+
throw new Error("udtValue exceeds Uint128");
|
|
100
|
+
}
|
|
101
|
+
masterValidate(this.master);
|
|
102
|
+
this.info.validate();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Returns true when the master pointer is relative to the current output. */
|
|
106
|
+
public isMint(): boolean {
|
|
107
|
+
return this.master.type === "relative";
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Resolves the master out point relative to the current order out point. */
|
|
111
|
+
public getMaster(current: ccc.OutPoint): ccc.OutPoint {
|
|
112
|
+
const { type, value } = this.master;
|
|
113
|
+
return type === "relative"
|
|
114
|
+
? new ccc.OutPoint(current.txHash, current.index + value.distance)
|
|
115
|
+
: value;
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/** CCC-backed order-data constructor and codec. */
|
|
120
|
+
// eslint-disable-next-line @typescript-eslint/no-redeclare -- The public type and runtime constructor intentionally share a name.
|
|
121
|
+
export const OrderData: {
|
|
122
|
+
byteLength?: number;
|
|
123
|
+
new (udtValue: ccc.FixedPoint, master: Master, info: Info): OrderData;
|
|
124
|
+
decode: (encoded: ccc.BytesLike) => OrderData;
|
|
125
|
+
encode: (data: OrderDataLike) => ccc.Bytes;
|
|
126
|
+
from: (data: OrderDataLike) => OrderData;
|
|
127
|
+
fromBytes: (encoded: ccc.BytesLike) => OrderData;
|
|
128
|
+
} = OrderDataImplementation;
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { ccc, mol } from "@ckb-ccc/core";
|
|
2
|
+
import { CheckedUint64LE } from "../utils/codec.ts";
|
|
3
|
+
import { compareBigInt, type ExchangeRatio } from "../utils/utils.ts";
|
|
4
|
+
|
|
5
|
+
const maxUint64 = (1n << 64n) - 1n;
|
|
6
|
+
|
|
7
|
+
const RatioCodec = mol.struct({
|
|
8
|
+
ckbScale: CheckedUint64LE,
|
|
9
|
+
udtScale: CheckedUint64LE,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const RatioBase = ccc.Entity.Base<ExchangeRatio, Ratio>();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Serialized exchange ratio used by order info.
|
|
16
|
+
*
|
|
17
|
+
* @remarks
|
|
18
|
+
* A ratio is either empty (`0, 0`) or populated with both scales greater than
|
|
19
|
+
* zero. Mixed empty/populated values are invalid.
|
|
20
|
+
*/
|
|
21
|
+
export interface Ratio extends ExchangeRatio {
|
|
22
|
+
/** Creates a copy of this ratio. */
|
|
23
|
+
clone(): Ratio;
|
|
24
|
+
/** Compares effective CKB-to-UDT prices, returning their numeric ordering. */
|
|
25
|
+
compare(other: Ratio): number;
|
|
26
|
+
/** Converts an amount in the selected direction, rounding up only when requested. */
|
|
27
|
+
convert(isCkb2Udt: boolean, amount: ccc.FixedPoint, mustCeil: boolean): ccc.FixedPoint;
|
|
28
|
+
/** Returns whether another value has the same scales. */
|
|
29
|
+
eq(other: ExchangeRatio): boolean;
|
|
30
|
+
/** Returns the CKB hash of the serialized ratio. */
|
|
31
|
+
hash(): ccc.Hex;
|
|
32
|
+
/** Returns whether both scales are zero. */
|
|
33
|
+
isEmpty(): boolean;
|
|
34
|
+
/** Returns whether both scales are positive. */
|
|
35
|
+
isPopulated(): boolean;
|
|
36
|
+
/** Serializes the ratio to bytes. */
|
|
37
|
+
toBytes(): ccc.Bytes;
|
|
38
|
+
/** Serializes the ratio to full-width hexadecimal. */
|
|
39
|
+
toHex(): ccc.Hex;
|
|
40
|
+
/** Throws unless both scales fit Uint64 and are both zero or both positive. */
|
|
41
|
+
validate(): void;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-shadow -- Preserve the runtime constructor name.
|
|
45
|
+
const RatioImplementation = class Ratio extends RatioBase {
|
|
46
|
+
static {
|
|
47
|
+
ccc.codec(RatioCodec)(this);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** CKB-side scale. */
|
|
51
|
+
public readonly ckbScale: ccc.Num;
|
|
52
|
+
/** UDT-side scale. */
|
|
53
|
+
public readonly udtScale: ccc.Num;
|
|
54
|
+
|
|
55
|
+
/** Creates a ratio from raw scales. */
|
|
56
|
+
constructor(ckbScale: ccc.Num, udtScale: ccc.Num) {
|
|
57
|
+
super();
|
|
58
|
+
this.ckbScale = ckbScale;
|
|
59
|
+
this.udtScale = udtScale;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Normalizes an exchange ratio wire object or existing entity into `Ratio`. */
|
|
63
|
+
public static override from(ratio: ExchangeRatio): Ratio {
|
|
64
|
+
if (ratio instanceof Ratio) {
|
|
65
|
+
return ratio;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const { ckbScale, udtScale } = ratio;
|
|
69
|
+
return new Ratio(ckbScale, udtScale);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Throws when the ratio is neither empty nor fully populated. */
|
|
73
|
+
public validate(): void {
|
|
74
|
+
if (
|
|
75
|
+
this.ckbScale < 0n ||
|
|
76
|
+
this.ckbScale > maxUint64 ||
|
|
77
|
+
this.udtScale < 0n ||
|
|
78
|
+
this.udtScale > maxUint64
|
|
79
|
+
) {
|
|
80
|
+
throw new Error("Ratio scale exceeds Uint64");
|
|
81
|
+
}
|
|
82
|
+
if (!this.isEmpty() && !this.isPopulated()) {
|
|
83
|
+
throw new Error("Ratio invalid: not empty, not populated");
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Returns true for the sentinel empty ratio. */
|
|
88
|
+
public isEmpty(): boolean {
|
|
89
|
+
return this.ckbScale === 0n && this.udtScale === 0n;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Returns true when both scales are positive. */
|
|
93
|
+
public isPopulated(): boolean {
|
|
94
|
+
return this.ckbScale > 0n && this.udtScale > 0n;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Creates the sentinel empty ratio. */
|
|
98
|
+
public static empty(): Ratio {
|
|
99
|
+
return new Ratio(0n, 0n);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Compares two populated ratios by effective CKB-to-UDT price. */
|
|
103
|
+
public compare(other: Ratio): number {
|
|
104
|
+
if (this.udtScale === other.udtScale) {
|
|
105
|
+
return compareBigInt(this.ckbScale, other.ckbScale);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (this.ckbScale === other.ckbScale) {
|
|
109
|
+
return compareBigInt(other.udtScale, this.udtScale);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return compareBigInt(this.ckbScale * other.udtScale, other.ckbScale * this.udtScale);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** Converts an amount in the requested direction with optional ceiling. */
|
|
116
|
+
public convert(
|
|
117
|
+
isCkb2Udt: boolean,
|
|
118
|
+
amount: ccc.FixedPoint,
|
|
119
|
+
mustCeil: boolean,
|
|
120
|
+
): ccc.FixedPoint {
|
|
121
|
+
if (!this.isPopulated()) {
|
|
122
|
+
throw new Error("Invalid midpoint ExchangeRatio");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
let { ckbScale: aScale, udtScale: bScale } = this;
|
|
126
|
+
if (!isCkb2Udt) {
|
|
127
|
+
[aScale, bScale] = [bScale, aScale];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return (amount * aScale + (mustCeil ? bScale - 1n : 0n)) / bScale;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
/** CCC-backed ratio constructor and codec. */
|
|
135
|
+
// eslint-disable-next-line @typescript-eslint/no-redeclare -- The public type and runtime constructor intentionally share a name.
|
|
136
|
+
export const Ratio: {
|
|
137
|
+
byteLength?: number;
|
|
138
|
+
new (ckbScale: ccc.Num, udtScale: ccc.Num): Ratio;
|
|
139
|
+
decode: (encoded: ccc.BytesLike) => Ratio;
|
|
140
|
+
empty: () => Ratio;
|
|
141
|
+
encode: (ratio: ExchangeRatio) => ccc.Bytes;
|
|
142
|
+
from: (ratio: ExchangeRatio) => Ratio;
|
|
143
|
+
fromBytes: (encoded: ccc.BytesLike) => Ratio;
|
|
144
|
+
} = RatioImplementation;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { ccc, mol } from "@ckb-ccc/core";
|
|
2
|
+
import { CheckedInt32LE } from "../utils/codec.ts";
|
|
3
|
+
|
|
4
|
+
const minInt32 = -(1n << 31n);
|
|
5
|
+
const maxInt32 = (1n << 31n) - 1n;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Wire shape for a relative master pointer.
|
|
9
|
+
*/
|
|
10
|
+
export interface RelativeLike {
|
|
11
|
+
/** Must be the 32-byte zero padding used by the standard encoding. */
|
|
12
|
+
padding: ccc.BytesLike;
|
|
13
|
+
/** Signed output-index distance from the current order to its master. */
|
|
14
|
+
distance: ccc.NumLike;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const RelativeCodec = mol.struct({
|
|
18
|
+
padding: mol.Byte32,
|
|
19
|
+
distance: CheckedInt32LE,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const RelativeBase = ccc.Entity.Base<RelativeLike, Relative>();
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Relative pointer from an order output to its master output.
|
|
26
|
+
*/
|
|
27
|
+
export interface Relative {
|
|
28
|
+
/** Standard zero padding. */
|
|
29
|
+
padding: ccc.Bytes;
|
|
30
|
+
/** Signed output-index distance to the master output. */
|
|
31
|
+
distance: ccc.Num;
|
|
32
|
+
/** Creates a copy of this relative pointer. */
|
|
33
|
+
clone(): Relative;
|
|
34
|
+
/** Returns whether another value has the same padding and distance. */
|
|
35
|
+
eq(other: RelativeLike): boolean;
|
|
36
|
+
/** Returns the CKB hash of the serialized relative pointer. */
|
|
37
|
+
hash(): ccc.Hex;
|
|
38
|
+
/** Serializes the relative pointer to bytes. */
|
|
39
|
+
toBytes(): ccc.Bytes;
|
|
40
|
+
/** Serializes the relative pointer to full-width hexadecimal. */
|
|
41
|
+
toHex(): ccc.Hex;
|
|
42
|
+
/** Throws unless padding is canonical and distance fits Int32. */
|
|
43
|
+
validate(): void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// eslint-disable-next-line @typescript-eslint/no-shadow -- Preserve the runtime constructor name.
|
|
47
|
+
const RelativeImplementation = class Relative extends RelativeBase {
|
|
48
|
+
static {
|
|
49
|
+
ccc.codec(RelativeCodec)(this);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Standard zero padding. */
|
|
53
|
+
public readonly padding: ccc.Bytes;
|
|
54
|
+
/** Signed output-index distance to the master output. */
|
|
55
|
+
public readonly distance: ccc.Num;
|
|
56
|
+
|
|
57
|
+
/** Creates a normalized relative pointer. */
|
|
58
|
+
constructor(padding: ccc.Bytes, distance: ccc.Num) {
|
|
59
|
+
super();
|
|
60
|
+
this.padding = padding;
|
|
61
|
+
this.distance = distance;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Normalizes a relative pointer wire object or existing entity into `Relative`. */
|
|
65
|
+
public static override from(relative: RelativeLike): Relative {
|
|
66
|
+
if (relative instanceof Relative) {
|
|
67
|
+
return relative;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const { padding, distance } = relative;
|
|
71
|
+
return new Relative(ccc.bytesFrom(padding), ccc.numFrom(distance));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Creates a relative pointer with standard zero padding. */
|
|
75
|
+
public static create(distance: ccc.Num): Relative {
|
|
76
|
+
return new Relative(Relative.padding(), distance);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Returns the standard 32-byte zero padding. */
|
|
80
|
+
public static padding(): ccc.Bytes {
|
|
81
|
+
return new Uint8Array(32);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Throws when padding is not the standard 32-byte zero value. */
|
|
85
|
+
public validate(): void {
|
|
86
|
+
if (this.padding.length !== 32 || this.padding.some((x) => x !== 0)) {
|
|
87
|
+
throw new Error("Relative master invalid, non standard padding");
|
|
88
|
+
}
|
|
89
|
+
if (this.distance < minInt32 || this.distance > maxInt32) {
|
|
90
|
+
throw new Error("Relative master distance exceeds Int32");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/** CCC-backed relative-pointer constructor and codec. */
|
|
96
|
+
// eslint-disable-next-line @typescript-eslint/no-redeclare -- The public type and runtime constructor intentionally share a name.
|
|
97
|
+
export const Relative: {
|
|
98
|
+
byteLength?: number;
|
|
99
|
+
new (padding: ccc.Bytes, distance: ccc.Num): Relative;
|
|
100
|
+
create: (distance: ccc.Num) => Relative;
|
|
101
|
+
decode: (encoded: ccc.BytesLike) => Relative;
|
|
102
|
+
encode: (relative: RelativeLike) => ccc.Bytes;
|
|
103
|
+
from: (relative: RelativeLike) => Relative;
|
|
104
|
+
fromBytes: (encoded: ccc.BytesLike) => Relative;
|
|
105
|
+
padding: () => ccc.Bytes;
|
|
106
|
+
} = RelativeImplementation;
|