@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.
- package/README.md +14 -11
- package/dist/constants.d.ts +0 -4
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +6 -24
- package/dist/constants.js.map +1 -1
- package/dist/conversion/estimate.d.ts +0 -6
- package/dist/conversion/estimate.d.ts.map +1 -1
- package/dist/conversion/estimate.js +1 -1
- package/dist/conversion/estimate.js.map +1 -1
- package/dist/conversion/maturity.d.ts +1 -1
- package/dist/conversion/maturity.js +13 -12
- package/dist/conversion/maturity.js.map +1 -1
- package/dist/conversion/plans.d.ts.map +1 -1
- package/dist/conversion/plans.js +6 -6
- package/dist/conversion/plans.js.map +1 -1
- package/dist/conversion/projection.d.ts +5 -6
- package/dist/conversion/projection.d.ts.map +1 -1
- package/dist/conversion/projection.js +11 -8
- package/dist/conversion/projection.js.map +1 -1
- package/dist/conversion/types.d.ts +29 -17
- package/dist/conversion/types.d.ts.map +1 -1
- package/dist/conversion/withdrawal_ring.d.ts +1 -3
- package/dist/conversion/withdrawal_ring.d.ts.map +1 -1
- package/dist/conversion/withdrawal_ring.js +4 -4
- package/dist/conversion/withdrawal_ring.js.map +1 -1
- package/dist/dao.d.ts +19 -5
- package/dist/dao.d.ts.map +1 -1
- package/dist/dao.js +24 -12
- package/dist/dao.js.map +1 -1
- package/dist/index.d.ts +9 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/logic.d.ts +10 -14
- package/dist/logic.d.ts.map +1 -1
- package/dist/logic.js +28 -39
- package/dist/logic.js.map +1 -1
- package/dist/order/cells.d.ts +10 -49
- package/dist/order/cells.d.ts.map +1 -1
- package/dist/order/cells.js +28 -146
- package/dist/order/cells.js.map +1 -1
- package/dist/order/conversion.d.ts +3 -9
- package/dist/order/conversion.d.ts.map +1 -1
- package/dist/order/conversion.js +8 -11
- package/dist/order/conversion.js.map +1 -1
- package/dist/order/fill.d.ts +2 -2
- package/dist/order/fill.js +3 -3
- package/dist/order/fill.js.map +1 -1
- package/dist/order/info.d.ts.map +1 -1
- package/dist/order/info.js +3 -0
- package/dist/order/info.js.map +1 -1
- package/dist/order/matcher.d.ts +1 -1
- package/dist/order/matcher.d.ts.map +1 -1
- package/dist/order/matcher.js +3 -2
- package/dist/order/matcher.js.map +1 -1
- package/dist/order/order.d.ts +6 -2
- package/dist/order/order.d.ts.map +1 -1
- package/dist/order/order.js +32 -37
- package/dist/order/order.js.map +1 -1
- package/dist/order/order_data.d.ts.map +1 -1
- package/dist/order/order_data.js +3 -0
- package/dist/order/order_data.js.map +1 -1
- package/dist/order/ratio.d.ts +0 -2
- package/dist/order/ratio.d.ts.map +1 -1
- package/dist/order/ratio.js +3 -11
- package/dist/order/ratio.js.map +1 -1
- package/dist/order/relative.d.ts.map +1 -1
- package/dist/order/relative.js +3 -0
- package/dist/order/relative.js.map +1 -1
- package/dist/owned_owner.d.ts +10 -15
- package/dist/owned_owner.d.ts.map +1 -1
- package/dist/owned_owner.js +27 -47
- package/dist/owned_owner.js.map +1 -1
- package/dist/sdk.d.ts +74 -31
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +81 -59
- package/dist/sdk.js.map +1 -1
- package/dist/send/sign_and_send_transaction.d.ts +32 -17
- package/dist/send/sign_and_send_transaction.d.ts.map +1 -1
- package/dist/send/sign_and_send_transaction.js +128 -28
- package/dist/send/sign_and_send_transaction.js.map +1 -1
- package/dist/send/wait_transaction.d.ts +7 -6
- package/dist/send/wait_transaction.d.ts.map +1 -1
- package/dist/send/wait_transaction.js +29 -61
- package/dist/send/wait_transaction.js.map +1 -1
- package/dist/udt.d.ts +20 -35
- package/dist/udt.d.ts.map +1 -1
- package/dist/udt.js +39 -103
- package/dist/udt.js.map +1 -1
- package/dist/utils/chain.d.ts +0 -35
- package/dist/utils/chain.d.ts.map +1 -1
- package/dist/utils/chain.js +1 -19
- package/dist/utils/chain.js.map +1 -1
- package/dist/utils/codec.d.ts.map +1 -1
- package/dist/utils/codec.js +3 -0
- package/dist/utils/codec.js.map +1 -1
- package/dist/utils/utils.d.ts +19 -46
- package/dist/utils/utils.d.ts.map +1 -1
- package/dist/utils/utils.js +53 -74
- package/dist/utils/utils.js.map +1 -1
- package/dist/utils/verified_client.d.ts +19 -0
- package/dist/utils/verified_client.d.ts.map +1 -0
- package/dist/utils/verified_client.js +92 -0
- package/dist/utils/verified_client.js.map +1 -0
- package/package.json +2 -2
- package/src/constants.ts +6 -25
- package/src/conversion/estimate.ts +1 -3
- package/src/conversion/maturity.ts +13 -14
- package/src/conversion/plans.ts +6 -6
- package/src/conversion/projection.ts +15 -7
- package/src/conversion/types.ts +32 -17
- package/src/conversion/withdrawal_ring.ts +4 -8
- package/src/dao.ts +38 -14
- package/src/index.ts +8 -8
- package/src/logic.ts +40 -52
- package/src/order/cells.ts +37 -174
- package/src/order/conversion.ts +8 -19
- package/src/order/fill.ts +3 -3
- package/src/order/info.ts +3 -0
- package/src/order/matcher.ts +3 -3
- package/src/order/order.ts +38 -52
- package/src/order/order_data.ts +3 -0
- package/src/order/ratio.ts +3 -20
- package/src/order/relative.ts +3 -0
- package/src/owned_owner.ts +47 -77
- package/src/sdk.ts +95 -108
- package/src/send/sign_and_send_transaction.ts +164 -28
- package/src/send/wait_transaction.ts +44 -88
- package/src/udt.ts +56 -124
- package/src/utils/chain.ts +0 -41
- package/src/utils/codec.ts +4 -0
- package/src/utils/utils.ts +76 -105
- package/src/utils/verified_client.ts +136 -0
- package/dist/utils/transaction_header.d.ts +0 -17
- package/dist/utils/transaction_header.d.ts.map +0 -1
- package/dist/utils/transaction_header.js +0 -44
- package/dist/utils/transaction_header.js.map +0 -1
- package/src/utils/transaction_header.ts +0 -66
package/src/owned_owner.ts
CHANGED
|
@@ -9,8 +9,12 @@ import {
|
|
|
9
9
|
import type { IckbDepositCell } from "./logic.ts";
|
|
10
10
|
import { ickbValue } from "./udt.ts";
|
|
11
11
|
import { CheckedInt32LE, CheckedUint64LE } from "./utils/codec.ts";
|
|
12
|
-
import {
|
|
13
|
-
|
|
12
|
+
import {
|
|
13
|
+
headersByNumber,
|
|
14
|
+
type ScannedCell,
|
|
15
|
+
type ScriptDeps,
|
|
16
|
+
type ValueComponents,
|
|
17
|
+
} from "./utils/utils.ts";
|
|
14
18
|
|
|
15
19
|
/**
|
|
16
20
|
* A live Nervos DAO withdrawal request cell, valued at the two headers it spans.
|
|
@@ -19,9 +23,7 @@ export interface DaoWithdrawalRequestCell {
|
|
|
19
23
|
/** The DAO withdrawal request cell. */
|
|
20
24
|
cell: ccc.Cell;
|
|
21
25
|
/** The deposit header and the request's own header. */
|
|
22
|
-
headers: [
|
|
23
|
-
/** The DAO interest accrued between the two headers. */
|
|
24
|
-
interests: ccc.Num;
|
|
26
|
+
headers: [ccc.ClientBlockHeader, ccc.ClientBlockHeader];
|
|
25
27
|
/** The DAO claim epoch of the request. */
|
|
26
28
|
maturity: ccc.Epoch;
|
|
27
29
|
/** Whether the claim epoch is at or before the tip: the request can be withdrawn. */
|
|
@@ -64,23 +66,15 @@ export function decodeOwnerData(outputData: ccc.Hex): OwnerData {
|
|
|
64
66
|
/**
|
|
65
67
|
* Wraps an owner marker cell that references an owned withdrawal request.
|
|
66
68
|
*/
|
|
67
|
-
export class OwnerCell
|
|
69
|
+
export class OwnerCell {
|
|
68
70
|
/** The live owner marker cell whose output data points to the owned request. */
|
|
69
71
|
public cell: ccc.Cell;
|
|
70
72
|
|
|
71
|
-
/** Owner marker cells carry no UDT value. */
|
|
72
|
-
public readonly udtValue = 0n;
|
|
73
|
-
|
|
74
73
|
/** Creates an owner marker wrapper for a live cell. */
|
|
75
74
|
constructor(cell: ccc.Cell) {
|
|
76
75
|
this.cell = cell;
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
/** Returns the CKB capacity held by the owner marker cell. */
|
|
80
|
-
public get ckbValue(): ccc.FixedPoint {
|
|
81
|
-
return this.cell.cellOutput.capacity;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
78
|
/**
|
|
85
79
|
* Returns the owned withdrawal request out point referenced by this owner marker.
|
|
86
80
|
*
|
|
@@ -131,7 +125,7 @@ export class WithdrawalGroup implements ValueComponents {
|
|
|
131
125
|
|
|
132
126
|
/** Returns the iCKB amount represented by the owned withdrawal request. */
|
|
133
127
|
public get udtValue(): ccc.FixedPoint {
|
|
134
|
-
return ickbValue(this.owned.cell.capacityFree, this.owned.headers[0]
|
|
128
|
+
return ickbValue(this.owned.cell.capacityFree, this.owned.headers[0]);
|
|
135
129
|
}
|
|
136
130
|
}
|
|
137
131
|
|
|
@@ -180,8 +174,8 @@ export class OwnedOwnerManager implements ScriptDeps {
|
|
|
180
174
|
* Spends the deposits into DAO withdrawal requests locked by the Owned Owner script,
|
|
181
175
|
* each followed by an owner marker locked by `lock` that points back at it.
|
|
182
176
|
*
|
|
183
|
-
* @remarks
|
|
184
|
-
*
|
|
177
|
+
* @remarks Completion (`IckbSdk.completeTransaction`) adds the dep group that also
|
|
178
|
+
* resolves the xUDT script.
|
|
185
179
|
*/
|
|
186
180
|
public requestWithdrawal(
|
|
187
181
|
txLike: ccc.TransactionLike,
|
|
@@ -202,21 +196,15 @@ export class OwnedOwnerManager implements ScriptDeps {
|
|
|
202
196
|
tx.addCellDeps(this.cellDeps);
|
|
203
197
|
tx.addCellDeps(this.dao.cellDeps);
|
|
204
198
|
const requestStart = tx.outputs.length;
|
|
205
|
-
for (const { cell,
|
|
206
|
-
const depositHeader = headers[0];
|
|
207
|
-
if (depositHeader.txHash !== cell.outPoint.txHash) {
|
|
208
|
-
throw new Error(
|
|
209
|
-
`DAO deposit ${cell.outPoint.toHex()} header txHash ${String(depositHeader.txHash)} does not match cell txHash ${cell.outPoint.txHash}`,
|
|
210
|
-
);
|
|
211
|
-
}
|
|
199
|
+
for (const { cell, header } of deposits) {
|
|
212
200
|
if (cell.cellOutput.lock.args.length !== this.script.args.length) {
|
|
213
201
|
throw new Error("Withdrawal request lock args has different size from deposit");
|
|
214
202
|
}
|
|
215
|
-
pushHeaderDep(tx,
|
|
203
|
+
pushHeaderDep(tx, header.hash);
|
|
216
204
|
tx.addInput(cell);
|
|
217
205
|
tx.addOutput(
|
|
218
206
|
{ capacity: cell.cellOutput.capacity, lock: this.script, type: this.dao.script },
|
|
219
|
-
CheckedUint64LE.encode(
|
|
207
|
+
CheckedUint64LE.encode(header.number),
|
|
220
208
|
);
|
|
221
209
|
}
|
|
222
210
|
for (let index = 0; index < deposits.length; index += 1) {
|
|
@@ -256,12 +244,6 @@ export class OwnedOwnerManager implements ScriptDeps {
|
|
|
256
244
|
`Withdrawal owner ${group.owner.cell.outPoint.toHex()} points to ${linkedOutPoint.toHex()} but group owned cell is ${ownedOutPoint.toHex()}`,
|
|
257
245
|
);
|
|
258
246
|
}
|
|
259
|
-
const { cell, headers } = group.owned;
|
|
260
|
-
if (headers[1].txHash !== cell.outPoint.txHash) {
|
|
261
|
-
throw new Error(
|
|
262
|
-
`DAO withdrawal request ${cell.outPoint.toHex()} header txHash ${String(headers[1].txHash)} does not match cell txHash ${cell.outPoint.txHash}`,
|
|
263
|
-
);
|
|
264
|
-
}
|
|
265
247
|
}
|
|
266
248
|
tx.addCellDeps(this.cellDeps);
|
|
267
249
|
tx.addCellDeps(this.dao.cellDeps);
|
|
@@ -270,17 +252,17 @@ export class OwnedOwnerManager implements ScriptDeps {
|
|
|
270
252
|
// pushed: the limit then counts these alone. Safe because nothing else in the stack reads
|
|
271
253
|
// a header by position and every built transaction passes here once.
|
|
272
254
|
const depositHashes = [
|
|
273
|
-
...new Set(withdrawalGroups.map(({ owned }) => owned.headers[0].
|
|
255
|
+
...new Set(withdrawalGroups.map(({ owned }) => owned.headers[0].hash)),
|
|
274
256
|
];
|
|
275
257
|
tx.headerDeps = [
|
|
276
258
|
...depositHashes,
|
|
277
259
|
...tx.headerDeps.filter((hash) => !depositHashes.includes(hash)),
|
|
278
260
|
];
|
|
279
261
|
for (const { owned } of withdrawalGroups) {
|
|
280
|
-
pushHeaderDep(tx, owned.headers[1].
|
|
262
|
+
pushHeaderDep(tx, owned.headers[1].hash);
|
|
281
263
|
}
|
|
282
264
|
for (const { owned } of withdrawalGroups) {
|
|
283
|
-
const headerIndex = tx.headerDeps.indexOf(owned.headers[0].
|
|
265
|
+
const headerIndex = tx.headerDeps.indexOf(owned.headers[0].hash);
|
|
284
266
|
// The deployed dao.c reads one byte of the u64 index field (RFC 0023 erratum,
|
|
285
267
|
// nervosnetwork/rfcs pull 456), so index 256 would be read as 0.
|
|
286
268
|
if (headerIndex >= DAO_HEADER_INDEX_LIMIT) {
|
|
@@ -309,52 +291,45 @@ export class OwnedOwnerManager implements ScriptDeps {
|
|
|
309
291
|
|
|
310
292
|
/**
|
|
311
293
|
* The owner markers among `cells` paired with their owned withdrawal requests, which live
|
|
312
|
-
* in the marker's own transaction at the encoded distance
|
|
313
|
-
* owned withdrawal request are skipped. Each
|
|
294
|
+
* in the marker's own transaction at the encoded distance, so the marker's block is the
|
|
295
|
+
* request's. Markers whose target is not an owned withdrawal request are skipped. Each
|
|
296
|
+
* header is read once per batch.
|
|
314
297
|
*/
|
|
315
298
|
public async withdrawalGroupsFrom(
|
|
316
299
|
client: ccc.Client,
|
|
317
|
-
cells: readonly
|
|
300
|
+
cells: readonly ScannedCell[],
|
|
318
301
|
tip: ccc.ClientBlockHeader,
|
|
319
302
|
): Promise<WithdrawalGroup[]> {
|
|
320
303
|
const owners = cells
|
|
321
|
-
.filter((cell) => this.isOwner(cell))
|
|
322
|
-
.map((cell) => new OwnerCell(cell));
|
|
304
|
+
.filter(({ cell }) => this.isOwner(cell))
|
|
305
|
+
.map(({ cell, blockNumber }) => ({ owner: new OwnerCell(cell), blockNumber }));
|
|
323
306
|
const ownedCells = await Promise.all(
|
|
324
|
-
owners.map(async (owner) => client.getCell(owner.getOwned())),
|
|
307
|
+
owners.map(async ({ owner }) => client.getCell(owner.getOwned())),
|
|
325
308
|
);
|
|
326
|
-
const pairs = owners.flatMap((owner, index) => {
|
|
309
|
+
const pairs = owners.flatMap(({ owner, blockNumber }, index) => {
|
|
327
310
|
const owned = ownedCells[index];
|
|
328
311
|
return owned !== undefined && this.isOwned(owned)
|
|
329
|
-
? [{ owner, owned, depositBlockNumber: depositBlockNumberOf(owned) }]
|
|
312
|
+
? [{ owner, owned, blockNumber, depositBlockNumber: depositBlockNumberOf(owned) }]
|
|
330
313
|
: [];
|
|
331
314
|
});
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
return new WithdrawalGroup(
|
|
349
|
-
withdrawalRequestCell(
|
|
350
|
-
owned,
|
|
351
|
-
depositHeader,
|
|
352
|
-
{ header: requestHeader, txHash },
|
|
353
|
-
tip,
|
|
315
|
+
const headers = await headersByNumber(
|
|
316
|
+
client,
|
|
317
|
+
pairs.flatMap((pair) => [pair.depositBlockNumber, pair.blockNumber]),
|
|
318
|
+
);
|
|
319
|
+
return pairs.map(
|
|
320
|
+
({ owner, owned, blockNumber, depositBlockNumber }) =>
|
|
321
|
+
new WithdrawalGroup(
|
|
322
|
+
withdrawalRequestCell(
|
|
323
|
+
owned,
|
|
324
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- both headers were read above.
|
|
325
|
+
headers.get(depositBlockNumber)!,
|
|
326
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- both headers were read above.
|
|
327
|
+
headers.get(blockNumber)!,
|
|
328
|
+
tip,
|
|
329
|
+
),
|
|
330
|
+
owner,
|
|
354
331
|
),
|
|
355
|
-
|
|
356
|
-
);
|
|
357
|
-
});
|
|
332
|
+
);
|
|
358
333
|
}
|
|
359
334
|
}
|
|
360
335
|
|
|
@@ -362,19 +337,14 @@ export class OwnedOwnerManager implements ScriptDeps {
|
|
|
362
337
|
export function withdrawalRequestCell(
|
|
363
338
|
cell: ccc.Cell,
|
|
364
339
|
depositHeader: ccc.ClientBlockHeader,
|
|
365
|
-
requestHeader:
|
|
340
|
+
requestHeader: ccc.ClientBlockHeader,
|
|
366
341
|
tip: ccc.ClientBlockHeader,
|
|
367
342
|
): DaoWithdrawalRequestCell {
|
|
368
|
-
const interests = ccc.calcDaoProfit(
|
|
369
|
-
|
|
370
|
-
depositHeader,
|
|
371
|
-
requestHeader.header,
|
|
372
|
-
);
|
|
373
|
-
const maturity = ccc.calcDaoClaimEpoch(depositHeader, requestHeader.header);
|
|
343
|
+
const interests = ccc.calcDaoProfit(cell.capacityFree, depositHeader, requestHeader);
|
|
344
|
+
const maturity = ccc.calcDaoClaimEpoch(depositHeader, requestHeader);
|
|
374
345
|
return {
|
|
375
346
|
cell,
|
|
376
|
-
headers: [
|
|
377
|
-
interests,
|
|
347
|
+
headers: [depositHeader, requestHeader],
|
|
378
348
|
maturity,
|
|
379
349
|
isReady: maturity.compare(tip.epoch) <= 0,
|
|
380
350
|
ckbValue: cell.cellOutput.capacity + interests,
|
package/src/sdk.ts
CHANGED
|
@@ -17,52 +17,23 @@ import type {
|
|
|
17
17
|
} from "./conversion/types.ts";
|
|
18
18
|
import {
|
|
19
19
|
assertDaoOutputLimit,
|
|
20
|
+
assertFresh,
|
|
20
21
|
broadcastDeadline,
|
|
21
22
|
DaoHeaderIndexError,
|
|
22
23
|
DaoOutputLimitError,
|
|
23
24
|
type LockUpPolicy,
|
|
25
|
+
pushHeaderDep,
|
|
26
|
+
tipMaxAgeMs,
|
|
24
27
|
} from "./dao.ts";
|
|
25
28
|
import type { IckbDepositCell, LogicManager } from "./logic.ts";
|
|
26
29
|
import type { OrderGroup } from "./order/cells.ts";
|
|
27
30
|
import type { OrderManager } from "./order/order.ts";
|
|
28
31
|
import { Ratio } from "./order/ratio.ts";
|
|
29
32
|
import type { OwnedOwnerManager } from "./owned_owner.ts";
|
|
30
|
-
import { ickbExchangeRatio, type IckbUdt } from "./udt.ts";
|
|
33
|
+
import { ickbExchangeRatio, type IckbUdt, type ProtocolCell } from "./udt.ts";
|
|
31
34
|
import type { SupportedChain } from "./utils/chain.ts";
|
|
32
35
|
import { compareBigInt, findCells, isPlainCapacityCell, unique } from "./utils/utils.ts";
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* The whole SDK, which the Node actors drive by relative import; the package barrel
|
|
36
|
-
* exposes the two conversion-workflow methods, and its assignment checks they agree.
|
|
37
|
-
*/
|
|
38
|
-
export interface IckbSdk {
|
|
39
|
-
/** Builds and completes a conversion, or returns a typed planning failure. */
|
|
40
|
-
buildConversionTransaction(
|
|
41
|
-
txLike: ccc.TransactionLike,
|
|
42
|
-
options: ConversionTransactionOptions,
|
|
43
|
-
): Promise<ConversionTransactionResult>;
|
|
44
|
-
/** Reads system, user-order, and account state against one sampled tip, under one timing policy. */
|
|
45
|
-
getL1AccountState(
|
|
46
|
-
client: ccc.Client,
|
|
47
|
-
locks: ccc.Script[],
|
|
48
|
-
lockUp: LockUpPolicy,
|
|
49
|
-
): Promise<{
|
|
50
|
-
system: SystemState;
|
|
51
|
-
user: { orders: OrderGroup[] };
|
|
52
|
-
account: AccountState;
|
|
53
|
-
}>;
|
|
54
|
-
/** Adds requested withdrawal, collection, receipt, and ready-withdrawal steps. */
|
|
55
|
-
buildBaseTransaction(
|
|
56
|
-
txLike: ccc.TransactionLike,
|
|
57
|
-
steps: CollectSteps,
|
|
58
|
-
withdrawalRequest?: { deposits: IckbDepositCell[]; lock: ccc.Script },
|
|
59
|
-
): ccc.Transaction;
|
|
60
|
-
/** Completes iCKB inputs and fees without signing or sending the transaction. */
|
|
61
|
-
completeTransaction(
|
|
62
|
-
txLike: ccc.TransactionLike,
|
|
63
|
-
options: CompleteIckbTransactionOptions,
|
|
64
|
-
): Promise<ccc.Transaction>;
|
|
65
|
-
}
|
|
36
|
+
import { verifiedClient } from "./utils/verified_client.ts";
|
|
66
37
|
|
|
67
38
|
/**
|
|
68
39
|
* Input count up to which one own transaction sweeps liquid cells.
|
|
@@ -80,13 +51,16 @@ export const SWEEP_INPUT_LIMIT = 1000;
|
|
|
80
51
|
type ResolvedConversionOptions = ConversionTransactionOptions & { lock: ccc.Script };
|
|
81
52
|
|
|
82
53
|
/** What every own transaction collects along: the context's collectable positions. */
|
|
83
|
-
|
|
54
|
+
type CollectSteps = Pick<
|
|
84
55
|
ConversionTransactionContext,
|
|
85
56
|
"availableOrders" | "receipts" | "readyWithdrawals"
|
|
86
57
|
>;
|
|
87
58
|
|
|
88
|
-
|
|
89
|
-
|
|
59
|
+
/**
|
|
60
|
+
* The whole SDK, which the Node actors drive by relative import; the package barrel
|
|
61
|
+
* exposes the two conversion-workflow methods, and its assignment checks they agree.
|
|
62
|
+
*/
|
|
63
|
+
export class IckbSdk {
|
|
90
64
|
private readonly ickbUdt: IckbUdt;
|
|
91
65
|
private readonly ownedOwner: OwnedOwnerManager;
|
|
92
66
|
private readonly ickbLogic: LogicManager;
|
|
@@ -107,28 +81,49 @@ const IckbSdkImplementation = class IckbSdk {
|
|
|
107
81
|
// ---- State reads -------------------------------------------------------------------
|
|
108
82
|
|
|
109
83
|
/**
|
|
110
|
-
* Reads system, user-order, and account state
|
|
111
|
-
* caller's withdrawal timing rules (`BOT_LOCK_UP` or `WALLET_LOCK_UP`)
|
|
84
|
+
* Reads system, user-order, and account state at the sampled tip's parent, under the
|
|
85
|
+
* caller's withdrawal timing rules (`BOT_LOCK_UP` or `WALLET_LOCK_UP`) and, when given, a
|
|
86
|
+
* positive fee rate cap that replaces CCC's ceiling; a cap of zero or less throws.
|
|
112
87
|
*
|
|
113
|
-
* @remarks Every read
|
|
114
|
-
*
|
|
115
|
-
*
|
|
88
|
+
* @remarks Every read goes through `verifiedClient` and the tip's parent is bounded by the
|
|
89
|
+
* policy's slack, so a caller passes a plain client. Every read is complete and uncapped: a large
|
|
90
|
+
* book or pool costs a slower read, never a partial or failed one. Each account lock is
|
|
91
|
+
* enumerated by one uncached unfiltered exact-lock scan and classified client-side.
|
|
116
92
|
*/
|
|
117
93
|
public async getL1AccountState(
|
|
118
|
-
|
|
94
|
+
plainClient: ccc.Client,
|
|
119
95
|
locks: ccc.Script[],
|
|
120
96
|
lockUp: LockUpPolicy,
|
|
97
|
+
maxFeeRate?: ccc.NumLike,
|
|
121
98
|
): Promise<{
|
|
122
99
|
system: SystemState;
|
|
123
|
-
|
|
100
|
+
userOrders: OrderGroup[];
|
|
124
101
|
account: AccountState;
|
|
125
102
|
}> {
|
|
126
|
-
|
|
103
|
+
// CCC reads a zero cap as no cap at all and would plan at a negative one.
|
|
104
|
+
if (maxFeeRate !== undefined && ccc.numFrom(maxFeeRate) <= ccc.Zero) {
|
|
105
|
+
throw new RangeError("maxFeeRate must be positive");
|
|
106
|
+
}
|
|
107
|
+
const client = verifiedClient(plainClient);
|
|
108
|
+
const live = await client.getTipHeader();
|
|
109
|
+
// The live tip can still lose a one-block race, and a transaction naming it as a header
|
|
110
|
+
// dep is then refused after signing; every recorded fork was one block deep.
|
|
111
|
+
const tip = await client.getHeaderByNumber(live.number - 1n);
|
|
112
|
+
if (tip?.hash !== live.parentHash) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`Block ${String(live.number - 1n)} is not the parent of tip ${live.hash}`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
// The age bound is on the header the read is priced at: a fresh tip a node invented can
|
|
118
|
+
// name any old block as its parent.
|
|
119
|
+
assertFresh(tip, tipMaxAgeMs(lockUp));
|
|
127
120
|
const exchangeRatio = Ratio.from(ickbExchangeRatio(tip));
|
|
128
121
|
const [poolDeposits, orders, feeRate, accounts] = await Promise.all([
|
|
129
122
|
this.ickbLogic.findDeposits(client, tip),
|
|
130
123
|
this.order.findOrders(client),
|
|
131
|
-
|
|
124
|
+
// CCC clamps the sampled median at the cap, the connector's chosen rate too. A given cap
|
|
125
|
+
// replaces CCC's default ceiling, on purpose: the caller's cap is the ceiling.
|
|
126
|
+
client.getFeeRate(undefined, maxFeeRate === undefined ? undefined : { maxFeeRate }),
|
|
132
127
|
Promise.all(
|
|
133
128
|
[...unique(locks)].map(async (lock) => this.readLockCells(client, lock, tip)),
|
|
134
129
|
),
|
|
@@ -144,7 +139,7 @@ const IckbSdkImplementation = class IckbSdk {
|
|
|
144
139
|
poolDeposits,
|
|
145
140
|
lockUp,
|
|
146
141
|
},
|
|
147
|
-
|
|
142
|
+
userOrders: orders.filter((group) => group.isOwner(...locks)),
|
|
148
143
|
account: {
|
|
149
144
|
capacityCells: accounts.flatMap((account) => account.capacityCells),
|
|
150
145
|
nativeUdtCells: accounts.flatMap((account) => account.nativeUdtCells),
|
|
@@ -160,15 +155,16 @@ const IckbSdkImplementation = class IckbSdk {
|
|
|
160
155
|
lock: ccc.Script,
|
|
161
156
|
tip: ccc.ClientBlockHeader,
|
|
162
157
|
): Promise<AccountState> {
|
|
163
|
-
const
|
|
158
|
+
const scanned = await findCells(client, {
|
|
164
159
|
script: lock,
|
|
165
160
|
scriptType: "lock",
|
|
166
161
|
scriptSearchMode: "exact",
|
|
167
162
|
withData: true,
|
|
168
163
|
});
|
|
164
|
+
const cells = scanned.map(({ cell }) => cell);
|
|
169
165
|
const [receipts, withdrawalGroups] = await Promise.all([
|
|
170
|
-
this.ickbLogic.receiptsFrom(client,
|
|
171
|
-
this.ownedOwner.withdrawalGroupsFrom(client,
|
|
166
|
+
this.ickbLogic.receiptsFrom(client, scanned),
|
|
167
|
+
this.ownedOwner.withdrawalGroupsFrom(client, scanned, tip),
|
|
172
168
|
]);
|
|
173
169
|
return {
|
|
174
170
|
capacityCells: cells.filter(isPlainCapacityCell),
|
|
@@ -192,7 +188,6 @@ const IckbSdkImplementation = class IckbSdk {
|
|
|
192
188
|
* expected planning outcomes; when no plan can be funded the last completion error throws.
|
|
193
189
|
*/
|
|
194
190
|
public async buildConversionTransaction(
|
|
195
|
-
txLike: ccc.TransactionLike,
|
|
196
191
|
options: ConversionTransactionOptions,
|
|
197
192
|
): Promise<ConversionTransactionResult> {
|
|
198
193
|
const { amount, context, direction } = options;
|
|
@@ -206,25 +201,23 @@ const IckbSdkImplementation = class IckbSdk {
|
|
|
206
201
|
return conversionFailure("insufficient-ickb", context.estimatedMaturity);
|
|
207
202
|
}
|
|
208
203
|
|
|
209
|
-
const baseTx = ccc.Transaction.from(txLike);
|
|
210
204
|
const resolved = {
|
|
211
205
|
...options,
|
|
212
206
|
lock: options.lock ?? (await options.signer.getRecommendedAddressObj()).script,
|
|
213
207
|
};
|
|
214
208
|
if (amount === 0n) {
|
|
215
|
-
return this.buildCollectOnlyConversion(
|
|
209
|
+
return this.buildCollectOnlyConversion(resolved);
|
|
216
210
|
}
|
|
217
211
|
return direction === "ckb-to-ickb"
|
|
218
|
-
? this.buildCkbToIckbConversion(
|
|
219
|
-
: this.buildIckbToCkbConversion(
|
|
212
|
+
? this.buildCkbToIckbConversion(resolved)
|
|
213
|
+
: this.buildIckbToCkbConversion(resolved);
|
|
220
214
|
}
|
|
221
215
|
|
|
222
216
|
private async buildCollectOnlyConversion(
|
|
223
|
-
baseTx: ccc.Transaction,
|
|
224
217
|
options: ResolvedConversionOptions,
|
|
225
218
|
): Promise<ConversionTransactionResult> {
|
|
226
219
|
const { context, lock, signer } = options;
|
|
227
|
-
const tx = this.buildBaseTransaction(
|
|
220
|
+
const tx = this.buildBaseTransaction(ccc.Transaction.default(), context);
|
|
228
221
|
// An empty collection to a lock of the signer's own would only compact the account;
|
|
229
222
|
// to another lock it moves the liquid cells, which completion builds from the sweep.
|
|
230
223
|
if (
|
|
@@ -238,29 +231,29 @@ const IckbSdkImplementation = class IckbSdk {
|
|
|
238
231
|
ok: true,
|
|
239
232
|
tx: completed,
|
|
240
233
|
estimatedMaturity: context.estimatedMaturity,
|
|
241
|
-
|
|
234
|
+
conversionKind: "collect-only",
|
|
242
235
|
isSweepComplete: sweepsAll(completed, context.cells),
|
|
243
236
|
};
|
|
244
237
|
}
|
|
245
238
|
|
|
246
239
|
private async buildCkbToIckbConversion(
|
|
247
|
-
baseTx: ccc.Transaction,
|
|
248
240
|
options: ResolvedConversionOptions,
|
|
249
241
|
): Promise<ConversionTransactionResult> {
|
|
250
242
|
const { context, lock } = options;
|
|
251
243
|
// A plan is dropped only for an unrepresentable remainder order.
|
|
252
244
|
const plans = ckbToIckbConversionPlans(options);
|
|
253
245
|
if (plans.length === 0) {
|
|
254
|
-
return
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
246
|
+
return {
|
|
247
|
+
ok: false,
|
|
248
|
+
reason: "amount-too-small",
|
|
249
|
+
estimatedMaturity: context.estimatedMaturity,
|
|
250
|
+
minimum: minimumOrderAmount(true, context.system),
|
|
251
|
+
};
|
|
259
252
|
}
|
|
260
253
|
const { candidate: plan, tx } = await completeFirstFundable(
|
|
261
254
|
plans,
|
|
262
255
|
(candidate) => {
|
|
263
|
-
let partial = this.buildBaseTransaction(
|
|
256
|
+
let partial = this.buildBaseTransaction(ccc.Transaction.default(), context);
|
|
264
257
|
if (candidate.depositCount > 0) {
|
|
265
258
|
partial = this.ickbLogic.deposit(
|
|
266
259
|
partial,
|
|
@@ -285,30 +278,28 @@ const IckbSdkImplementation = class IckbSdk {
|
|
|
285
278
|
ok: true,
|
|
286
279
|
tx,
|
|
287
280
|
estimatedMaturity: plan.estimatedMaturity,
|
|
288
|
-
|
|
289
|
-
kind: conversionKind(plan.depositCount > 0, plan.order !== undefined),
|
|
290
|
-
},
|
|
281
|
+
conversionKind: conversionKind(plan.depositCount > 0, plan.order !== undefined),
|
|
291
282
|
isSweepComplete: sweepsAll(tx, context.cells),
|
|
292
283
|
};
|
|
293
284
|
}
|
|
294
285
|
|
|
295
286
|
private async buildIckbToCkbConversion(
|
|
296
|
-
baseTx: ccc.Transaction,
|
|
297
287
|
options: ResolvedConversionOptions,
|
|
298
288
|
): Promise<ConversionTransactionResult> {
|
|
299
289
|
const { context, lock } = options;
|
|
300
290
|
const plans = ickbToCkbConversionPlans(options, context.system.poolDeposits);
|
|
301
291
|
if (plans.length === 0) {
|
|
302
|
-
return
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
292
|
+
return {
|
|
293
|
+
ok: false,
|
|
294
|
+
reason: "amount-too-small",
|
|
295
|
+
estimatedMaturity: context.estimatedMaturity,
|
|
296
|
+
minimum: minimumOrderAmount(false, context.system),
|
|
297
|
+
};
|
|
307
298
|
}
|
|
308
299
|
const { candidate: plan, tx } = await completeFirstFundable(
|
|
309
300
|
plans,
|
|
310
301
|
(candidate) => {
|
|
311
|
-
let partial = this.buildBaseTransaction(
|
|
302
|
+
let partial = this.buildBaseTransaction(ccc.Transaction.default(), context, {
|
|
312
303
|
deposits: candidate.selectedDeposits,
|
|
313
304
|
lock,
|
|
314
305
|
});
|
|
@@ -333,9 +324,10 @@ const IckbSdkImplementation = class IckbSdk {
|
|
|
333
324
|
ok: true,
|
|
334
325
|
tx,
|
|
335
326
|
estimatedMaturity: plan.estimatedMaturity,
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
327
|
+
conversionKind: conversionKind(
|
|
328
|
+
plan.selectedDeposits.length > 0,
|
|
329
|
+
plan.order !== undefined,
|
|
330
|
+
),
|
|
339
331
|
...(notice === undefined ? {} : { conversionNotice: notice }),
|
|
340
332
|
isSweepComplete: sweepsAll(tx, context.cells),
|
|
341
333
|
...(broadcastBefore === undefined ? {} : { broadcastBefore }),
|
|
@@ -351,6 +343,11 @@ const IckbSdkImplementation = class IckbSdk {
|
|
|
351
343
|
lock: options.lock,
|
|
352
344
|
feeRate: options.context.system.feeRate,
|
|
353
345
|
cells: options.context.cells,
|
|
346
|
+
protocolCells: [
|
|
347
|
+
...options.context.receipts,
|
|
348
|
+
...options.context.system.poolDeposits,
|
|
349
|
+
],
|
|
350
|
+
tip: options.context.system.tip,
|
|
354
351
|
});
|
|
355
352
|
}
|
|
356
353
|
|
|
@@ -405,7 +402,8 @@ const IckbSdkImplementation = class IckbSdk {
|
|
|
405
402
|
options: CompleteIckbTransactionOptions,
|
|
406
403
|
): Promise<ccc.Transaction> {
|
|
407
404
|
const { signer, feeRate } = options;
|
|
408
|
-
const tx =
|
|
405
|
+
const tx = ccc.Transaction.from(txLike).clone();
|
|
406
|
+
tx.addCellDeps(this.ickbUdt.cellDeps);
|
|
409
407
|
const changeLock = options.lock ?? (await signer.getRecommendedAddressObj()).script;
|
|
410
408
|
// Inputs come from one state read, each cell once, and each action spends its own
|
|
411
409
|
// cell kind, so a repeated out point is a builder bug: this is the one place that
|
|
@@ -428,9 +426,18 @@ const IckbSdkImplementation = class IckbSdk {
|
|
|
428
426
|
compareBigInt(right.cellOutput.capacity, left.cellOutput.capacity),
|
|
429
427
|
);
|
|
430
428
|
|
|
431
|
-
await this.completeIckb(
|
|
429
|
+
await this.completeIckb(
|
|
430
|
+
tx,
|
|
431
|
+
signer.client,
|
|
432
|
+
changeLock,
|
|
433
|
+
ickbCells,
|
|
434
|
+
options.protocolCells,
|
|
435
|
+
);
|
|
432
436
|
// Plain CKB: the sweep first, then whatever the fee still needs beyond the limit.
|
|
433
437
|
const swept = sweep(tx, plainCells);
|
|
438
|
+
// Last of the header deps, so no index another step names shifts, and before the fee so
|
|
439
|
+
// its 32 bytes are paid for.
|
|
440
|
+
pushHeaderDep(tx, options.tip.hash);
|
|
434
441
|
await completeFee(tx, signer, changeLock, feeRate, plainCells.slice(swept));
|
|
435
442
|
assertDaoOutputLimit(tx, this.ickbLogic.dao.script);
|
|
436
443
|
return tx;
|
|
@@ -442,9 +449,10 @@ const IckbSdkImplementation = class IckbSdk {
|
|
|
442
449
|
client: ccc.Client,
|
|
443
450
|
changeLock: ccc.Script,
|
|
444
451
|
ickbCells: readonly ccc.Cell[],
|
|
452
|
+
protocolCells: readonly ProtocolCell[],
|
|
445
453
|
): Promise<void> {
|
|
446
|
-
const required = this.ickbUdt.
|
|
447
|
-
let balance = await this.ickbUdt.inputBalance(tx, client);
|
|
454
|
+
const required = tx.getOutputsUdtBalance(this.ickbUdt.script);
|
|
455
|
+
let balance = await this.ickbUdt.inputBalance(tx, client, protocolCells);
|
|
448
456
|
for (const cell of ickbCells) {
|
|
449
457
|
if (balance >= required && tx.inputs.length >= SWEEP_INPUT_LIMIT) {
|
|
450
458
|
break;
|
|
@@ -459,17 +467,10 @@ const IckbSdkImplementation = class IckbSdk {
|
|
|
459
467
|
}
|
|
460
468
|
this.ickbUdt.addChange(tx, changeLock, balance - required);
|
|
461
469
|
}
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
/** Concrete iCKB SDK constructor and static estimators. */
|
|
465
|
-
// eslint-disable-next-line @typescript-eslint/no-redeclare -- The public type and runtime constructor intentionally share a name.
|
|
466
|
-
export const IckbSdk: {
|
|
467
|
-
new (managers: SdkManagers): IckbSdk;
|
|
468
|
-
fromChain: (chain: SupportedChain) => IckbSdk;
|
|
469
|
-
} = IckbSdkImplementation;
|
|
470
|
+
}
|
|
470
471
|
|
|
471
472
|
/** Whether the transaction already carries any input or output. */
|
|
472
|
-
|
|
473
|
+
function hasTransactionActivity(tx: ccc.Transaction): boolean {
|
|
473
474
|
return tx.inputs.length > 0 || tx.outputs.length > 0;
|
|
474
475
|
}
|
|
475
476
|
|
|
@@ -484,16 +485,10 @@ function isPastPar(group: OrderGroup, exchangeRatio: Ratio): boolean {
|
|
|
484
485
|
}
|
|
485
486
|
|
|
486
487
|
function conversionFailure(
|
|
487
|
-
reason: ConversionTransactionFailureReason,
|
|
488
|
+
reason: Exclude<ConversionTransactionFailureReason, "amount-too-small">,
|
|
488
489
|
estimatedMaturity: bigint,
|
|
489
|
-
minimum?: bigint,
|
|
490
490
|
): ConversionTransactionResult {
|
|
491
|
-
return {
|
|
492
|
-
ok: false,
|
|
493
|
-
reason,
|
|
494
|
-
estimatedMaturity,
|
|
495
|
-
...(minimum === undefined ? {} : { minimum }),
|
|
496
|
-
};
|
|
491
|
+
return { ok: false, reason, estimatedMaturity };
|
|
497
492
|
}
|
|
498
493
|
|
|
499
494
|
/** A plan always carries a deposit or an order, else it would be a collection. */
|
|
@@ -517,14 +512,6 @@ async function isOwnLock(signer: ccc.Signer, lock: ccc.Script): Promise<boolean>
|
|
|
517
512
|
return (await signer.getAddressObjs()).some(({ script }) => script.eq(lock));
|
|
518
513
|
}
|
|
519
514
|
|
|
520
|
-
async function getFeeRate(client: ccc.Client): Promise<ccc.Num> {
|
|
521
|
-
const feeRate = await client.getFeeRate();
|
|
522
|
-
if (feeRate < 0n) {
|
|
523
|
-
throw new Error("Client fee rate must be non-negative");
|
|
524
|
-
}
|
|
525
|
-
return feeRate;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
515
|
function udtBalance(cell: ccc.Cell): ccc.Num {
|
|
529
516
|
return ccc.udtBalanceFrom(cell.outputData);
|
|
530
517
|
}
|
|
@@ -569,7 +556,7 @@ async function completeFee(
|
|
|
569
556
|
}
|
|
570
557
|
}
|
|
571
558
|
|
|
572
|
-
/** Stable machine-readable failures
|
|
559
|
+
/** Stable machine-readable SDK failures. */
|
|
573
560
|
export type IckbErrorCode = "insufficient_capacity" | "insufficient_ickb";
|
|
574
561
|
|
|
575
562
|
/** Typed SDK failure with a stable machine-readable code. */
|