@ickb/sdk 1000.0.82 → 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 +84 -32
- package/dist/constants.d.ts +9 -22
- package/dist/constants.d.ts.map +1 -1
- package/dist/constants.js +62 -76
- package/dist/constants.js.map +1 -1
- package/dist/conversion/estimate.d.ts +33 -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 +113 -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 +21 -0
- package/dist/conversion/projection.d.ts.map +1 -0
- package/dist/conversion/projection.js +94 -0
- package/dist/conversion/projection.js.map +1 -0
- package/dist/conversion/types.d.ts +264 -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 +34 -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 +100 -0
- package/dist/dao.d.ts.map +1 -0
- package/dist/dao.js +125 -0
- package/dist/dao.js.map +1 -0
- package/dist/index.d.ts +49 -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 +81 -0
- package/dist/logic.d.ts.map +1 -0
- package/dist/logic.js +173 -0
- package/dist/logic.js.map +1 -0
- package/dist/order/cells.d.ts +168 -0
- package/dist/order/cells.d.ts.map +1 -0
- package/dist/order/cells.js +309 -0
- package/dist/order/cells.js.map +1 -0
- package/dist/order/conversion.d.ts +37 -0
- package/dist/order/conversion.d.ts.map +1 -0
- package/dist/order/conversion.js +120 -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 +102 -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 +124 -0
- package/dist/order/matcher.js.map +1 -0
- package/dist/order/order.d.ts +63 -0
- package/dist/order/order.d.ts.map +1 -0
- package/dist/order/order.js +234 -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 +67 -0
- package/dist/order/order_data.js.map +1 -0
- package/dist/order/ratio.d.ts +40 -0
- package/dist/order/ratio.d.ts.map +1 -0
- package/dist/order/ratio.js +74 -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 +57 -0
- package/dist/order/relative.js.map +1 -0
- package/dist/owned_owner.d.ts +108 -0
- package/dist/owned_owner.d.ts.map +1 -0
- package/dist/owned_owner.js +260 -0
- package/dist/owned_owner.js.map +1 -0
- package/dist/sdk.d.ts +113 -41
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +410 -209
- package/dist/sdk.js.map +1 -1
- package/dist/send/sign_and_send_transaction.d.ts +61 -0
- package/dist/send/sign_and_send_transaction.d.ts.map +1 -0
- package/dist/send/sign_and_send_transaction.js +201 -0
- package/dist/send/sign_and_send_transaction.js.map +1 -0
- package/dist/send/wait_transaction.d.ts +42 -0
- package/dist/send/wait_transaction.d.ts.map +1 -0
- package/dist/send/wait_transaction.js +172 -0
- package/dist/send/wait_transaction.js.map +1 -0
- package/dist/udt.d.ts +108 -0
- package/dist/udt.d.ts.map +1 -0
- package/dist/udt.js +180 -0
- package/dist/udt.js.map +1 -0
- package/dist/utils/chain.d.ts +3 -0
- package/dist/utils/chain.d.ts.map +1 -0
- package/dist/utils/chain.js +2 -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 +69 -0
- package/dist/utils/codec.js.map +1 -0
- package/dist/utils/utils.d.ts +99 -0
- package/dist/utils/utils.d.ts.map +1 -0
- package/dist/utils/utils.js +130 -0
- package/dist/utils/utils.js.map +1 -0
- 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 +19 -26
- package/src/constants.ts +65 -181
- package/src/conversion/estimate.ts +155 -0
- package/src/conversion/maturity.ts +158 -0
- package/src/conversion/plans.ts +144 -0
- package/src/conversion/projection.ts +136 -0
- package/src/conversion/types.ts +302 -0
- package/src/conversion/withdrawal_ring.ts +122 -0
- package/src/dao.ts +196 -0
- package/src/index.ts +89 -3
- package/src/logic.ts +283 -0
- package/src/order/cells.ts +383 -0
- package/src/order/conversion.ts +162 -0
- package/src/order/fee.ts +30 -0
- package/src/order/fill.ts +83 -0
- package/src/order/info.ts +181 -0
- package/src/order/master.ts +42 -0
- package/src/order/matcher.ts +191 -0
- package/src/order/order.ts +288 -0
- package/src/order/order_data.ts +131 -0
- package/src/order/ratio.ts +127 -0
- package/src/order/relative.ts +109 -0
- package/src/owned_owner.ts +364 -0
- package/src/sdk.ts +542 -440
- package/src/send/sign_and_send_transaction.ts +256 -0
- package/src/send/wait_transaction.ts +227 -0
- package/src/udt.ts +249 -0
- package/src/utils/chain.ts +2 -0
- package/src/utils/codec.ts +78 -0
- package/src/utils/utils.ts +208 -0
- package/src/utils/verified_client.ts +136 -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 type { ccc } from "@ckb-ccc/core";
|
|
2
|
+
import type { BroadcastDeadline, LockUpPolicy } from "../dao.ts";
|
|
3
|
+
import type { IckbDepositCell, LogicManager, ReceiptCell } from "../logic.ts";
|
|
4
|
+
import type { OrderCell, OrderGroup } from "../order/cells.ts";
|
|
5
|
+
import type { Info } from "../order/info.ts";
|
|
6
|
+
import type { OrderManager } from "../order/order.ts";
|
|
7
|
+
import type { Ratio } from "../order/ratio.ts";
|
|
8
|
+
import type { OwnedOwnerManager, WithdrawalGroup } from "../owned_owner.ts";
|
|
9
|
+
import type { IckbUdt, ProtocolCell } from "../udt.ts";
|
|
10
|
+
import type { ValueComponents } from "../utils/utils.ts";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Direction requested by a conversion transaction.
|
|
14
|
+
*/
|
|
15
|
+
export type ConversionDirection = "ckb-to-ickb" | "ickb-to-ckb";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Snapshot used to plan one wallet conversion transaction.
|
|
19
|
+
*/
|
|
20
|
+
export interface ConversionTransactionContext {
|
|
21
|
+
/** Public system state sampled for conversion planning. */
|
|
22
|
+
system: SystemState;
|
|
23
|
+
/** User receipt cells available for deposit completion. */
|
|
24
|
+
receipts: ReceiptCell[];
|
|
25
|
+
/** User withdrawal groups ready to complete. */
|
|
26
|
+
readyWithdrawals: WithdrawalGroup[];
|
|
27
|
+
/** Order groups available for collection or budgeting as account value. */
|
|
28
|
+
availableOrders: OrderGroup[];
|
|
29
|
+
/** The wallet's liquid cells, plain CKB and iCKB, that completion funds from and sweeps. */
|
|
30
|
+
cells: ccc.Cell[];
|
|
31
|
+
/** Projected CKB available to the wallet after pending state is considered. */
|
|
32
|
+
ckbAvailable: bigint;
|
|
33
|
+
/** Projected iCKB available to the wallet after pending state is considered. */
|
|
34
|
+
ickbAvailable: bigint;
|
|
35
|
+
/**
|
|
36
|
+
* The latest date at which everything the wallet has converting, plus this request, is
|
|
37
|
+
* collectable; an amount of zero is the collection itself.
|
|
38
|
+
*/
|
|
39
|
+
estimatedMaturity: bigint;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Inputs and policy limits for building one conversion transaction.
|
|
44
|
+
*/
|
|
45
|
+
export interface ConversionTransactionOptions {
|
|
46
|
+
/** Conversion direction to build. */
|
|
47
|
+
direction: ConversionDirection;
|
|
48
|
+
|
|
49
|
+
/** Requested input amount in the source asset. */
|
|
50
|
+
amount: bigint;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Lock that owns every cell the transaction creates for the user, the conversion outputs
|
|
54
|
+
* and the change; the signer's recommended lock by default. Completion sweeps the liquid
|
|
55
|
+
* cells along within its input limit, so a foreign lock redirects those funds too.
|
|
56
|
+
*/
|
|
57
|
+
lock?: ccc.Script;
|
|
58
|
+
|
|
59
|
+
/** Signer whose committed cells fund the completed transaction. */
|
|
60
|
+
signer: ccc.Signer;
|
|
61
|
+
|
|
62
|
+
/** Sampled state used to plan this conversion. */
|
|
63
|
+
context: ConversionTransactionContext;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Reason a conversion transaction could not be built without throwing.
|
|
68
|
+
*/
|
|
69
|
+
export type ConversionTransactionFailureReason =
|
|
70
|
+
| "amount-negative"
|
|
71
|
+
| "insufficient-ckb"
|
|
72
|
+
| "insufficient-ickb"
|
|
73
|
+
| "amount-too-small"
|
|
74
|
+
| "nothing-to-do";
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* A dust iCKB-to-CKB order: too small for the default fee to clear the estimate threshold,
|
|
78
|
+
* so it pays the smallest fee that does. For callers to surface in UI or logs.
|
|
79
|
+
*/
|
|
80
|
+
export interface ConversionNotice {
|
|
81
|
+
/** iCKB input amount that triggered the notice. */
|
|
82
|
+
inputIckb: bigint;
|
|
83
|
+
|
|
84
|
+
/** CKB output estimate for the noticed path. */
|
|
85
|
+
outputCkb: bigint;
|
|
86
|
+
|
|
87
|
+
/** CKB incentive associated with the noticed path. */
|
|
88
|
+
incentiveCkb: bigint;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** Composition category selected for a built conversion. */
|
|
92
|
+
export type ConversionKind = "direct" | "order" | "direct-plus-order" | "collect-only";
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Result of attempting to build a conversion transaction.
|
|
96
|
+
*/
|
|
97
|
+
export type ConversionTransactionResult =
|
|
98
|
+
| {
|
|
99
|
+
ok: true;
|
|
100
|
+
/** Completed transaction, funded from the signer's committed cells. Callers own signing and send. */
|
|
101
|
+
tx: ccc.Transaction;
|
|
102
|
+
/** Estimated maturity timestamp for the conversion result. */
|
|
103
|
+
estimatedMaturity: bigint;
|
|
104
|
+
/** Composition of the selected conversion path. */
|
|
105
|
+
conversionKind: ConversionKind;
|
|
106
|
+
/** Optional notice about the selected path. */
|
|
107
|
+
conversionNotice?: ConversionNotice;
|
|
108
|
+
/**
|
|
109
|
+
* True when every liquid cell of the context is an input. False when the input limit
|
|
110
|
+
* left some behind: a move to another lock then needs another transaction.
|
|
111
|
+
*/
|
|
112
|
+
isSweepComplete: boolean;
|
|
113
|
+
/**
|
|
114
|
+
* The epoch the transaction must be broadcast before for its withdrawal requests to
|
|
115
|
+
* claim at their sampled dates (`system.lockUp`); absent when it requests none.
|
|
116
|
+
*/
|
|
117
|
+
broadcastBefore?: BroadcastDeadline;
|
|
118
|
+
}
|
|
119
|
+
| {
|
|
120
|
+
ok: false;
|
|
121
|
+
reason: "amount-too-small";
|
|
122
|
+
/** Best available maturity estimate from the input context. */
|
|
123
|
+
estimatedMaturity: bigint;
|
|
124
|
+
/**
|
|
125
|
+
* The smallest amount this direction accepts at the current fee rate, in the
|
|
126
|
+
* request's unit (CKB shannons or iCKB), so a caller can name it.
|
|
127
|
+
*/
|
|
128
|
+
minimum: bigint;
|
|
129
|
+
}
|
|
130
|
+
| {
|
|
131
|
+
ok: false;
|
|
132
|
+
/** Machine-readable failure reason. */
|
|
133
|
+
reason: Exclude<ConversionTransactionFailureReason, "amount-too-small">;
|
|
134
|
+
/** Best available maturity estimate from the input context. */
|
|
135
|
+
estimatedMaturity: bigint;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Options for completing a partial iCKB transaction before signing and sending.
|
|
140
|
+
*/
|
|
141
|
+
export interface CompleteIckbTransactionOptions {
|
|
142
|
+
/** Signer that prepares the transaction; its recommended lock owns the change by default. */
|
|
143
|
+
signer: ccc.Signer;
|
|
144
|
+
|
|
145
|
+
/** Lock that owns the change, iCKB and plain, when not the signer's recommended lock. */
|
|
146
|
+
lock?: ccc.Script;
|
|
147
|
+
|
|
148
|
+
/** Fee rate passed to CCC fee completion. */
|
|
149
|
+
feeRate: ccc.Num;
|
|
150
|
+
|
|
151
|
+
/** The signer's known liquid cells, plain CKB and iCKB, from the account state already read. */
|
|
152
|
+
cells: ccc.Cell[];
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The receipts and iCKB deposits the transaction may spend, valued by the state read;
|
|
156
|
+
* any superset works, since only the spent ones are summed.
|
|
157
|
+
*/
|
|
158
|
+
protocolCells: readonly ProtocolCell[];
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* The header the plan was priced at, the state read's `system.tip`, named as a header
|
|
162
|
+
* dep: the chain refuses a transaction whose deps are off the main chain, so a header a
|
|
163
|
+
* node invented, whose DAO rate set the exchange ratio, cannot price an accepted
|
|
164
|
+
* transaction.
|
|
165
|
+
*/
|
|
166
|
+
tip: ccc.ClientBlockHeader;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Quote details for one order-based conversion path.
|
|
171
|
+
*/
|
|
172
|
+
export interface ConversionOrderEstimate {
|
|
173
|
+
/** Output amount after applying the order ratio and fee. */
|
|
174
|
+
convertedAmount: ccc.FixedPoint;
|
|
175
|
+
/** CKB fee component embedded in the order conversion. */
|
|
176
|
+
ckbFee: ccc.FixedPoint;
|
|
177
|
+
/** Order info that should be encoded into a created order. */
|
|
178
|
+
info: Info;
|
|
179
|
+
/** Estimated maturity timestamp, or `undefined` when it cannot be estimated. */
|
|
180
|
+
maturity: ccc.Num | undefined;
|
|
181
|
+
/** A non-fatal notice about the path this estimate took, for the caller to surface. */
|
|
182
|
+
notice?: ConversionNotice;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Input accepted by maturity estimation, either a live order cell or raw order data plus values.
|
|
187
|
+
*/
|
|
188
|
+
export type MaturityOrderInput =
|
|
189
|
+
| OrderCell
|
|
190
|
+
| {
|
|
191
|
+
info: Info;
|
|
192
|
+
amounts: ValueComponents;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Raw wallet-owned cells and grouped iCKB state sampled from L1.
|
|
197
|
+
*/
|
|
198
|
+
export interface AccountState {
|
|
199
|
+
/** Plain capacity cells owned by the account locks. */
|
|
200
|
+
capacityCells: ccc.Cell[];
|
|
201
|
+
/** Native iCKB xUDT cells owned by the account locks. */
|
|
202
|
+
nativeUdtCells: ccc.Cell[];
|
|
203
|
+
/** Receipt cells owned by the account. */
|
|
204
|
+
receipts: ReceiptCell[];
|
|
205
|
+
/** Withdrawal groups owned by the account. */
|
|
206
|
+
withdrawalGroups: WithdrawalGroup[];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Account balances split into available, pending, and order/withdrawal buckets.
|
|
211
|
+
*/
|
|
212
|
+
export interface AccountAvailabilityProjection {
|
|
213
|
+
/**
|
|
214
|
+
* CKB in the account's liquid cells, plain and iCKB alike: what the wallet shows and what
|
|
215
|
+
* the next transaction spends, before the change cells, an order's master cell and the
|
|
216
|
+
* fee it must fund on top.
|
|
217
|
+
*/
|
|
218
|
+
ckbNative: bigint;
|
|
219
|
+
/** iCKB in the account's native iCKB cells. */
|
|
220
|
+
ickbNative: bigint;
|
|
221
|
+
/** `ckbNative` plus the CKB the receipts, ready withdrawals and collectable orders return. */
|
|
222
|
+
ckbAvailable: bigint;
|
|
223
|
+
/** `ickbNative` plus the iCKB the receipts and collectable orders return. */
|
|
224
|
+
ickbAvailable: bigint;
|
|
225
|
+
/** CKB pending in withdrawals or orders. */
|
|
226
|
+
ckbPending: bigint;
|
|
227
|
+
/** iCKB pending in withdrawals or orders. */
|
|
228
|
+
ickbPending: bigint;
|
|
229
|
+
/** Total CKB balance including pending positions. */
|
|
230
|
+
ckbBalance: bigint;
|
|
231
|
+
/** Total iCKB balance including pending positions. */
|
|
232
|
+
ickbBalance: bigint;
|
|
233
|
+
/**
|
|
234
|
+
* Matured withdrawal groups one transaction can complete: at most the deposit-header slots
|
|
235
|
+
* the DAO script addresses (`DAO_HEADER_INDEX_LIMIT`), which the deposit headers hold alone.
|
|
236
|
+
*/
|
|
237
|
+
readyWithdrawals: WithdrawalGroup[];
|
|
238
|
+
/** Withdrawal groups still waiting for DAO maturity or for a header slot in a later turn. */
|
|
239
|
+
pendingWithdrawals: WithdrawalGroup[];
|
|
240
|
+
/** Order groups available for collection or budgeting as account value. */
|
|
241
|
+
availableOrders: OrderGroup[];
|
|
242
|
+
/** Order groups that are owned but unavailable or unresolved. */
|
|
243
|
+
pendingOrders: OrderGroup[];
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Combined projection and transaction-planning context for one sampled account state.
|
|
248
|
+
*/
|
|
249
|
+
export interface ConversionTransactionContextProjection {
|
|
250
|
+
/** User-facing availability projection. */
|
|
251
|
+
projection: AccountAvailabilityProjection;
|
|
252
|
+
/** Builder-facing transaction context derived from the same sampled state. */
|
|
253
|
+
context: ConversionTransactionContext;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Public sampled system state used for quotes, maturity, and conversion planning.
|
|
258
|
+
*/
|
|
259
|
+
export interface SystemState {
|
|
260
|
+
/** The fee rate for transactions. */
|
|
261
|
+
feeRate: ccc.Num;
|
|
262
|
+
/** The sampled tip's parent, this L1 scan's anchor. It may be stale after scans complete. */
|
|
263
|
+
tip: ccc.ClientBlockHeader;
|
|
264
|
+
/** The exchange ratio between CKB and UDT. */
|
|
265
|
+
exchangeRatio: Ratio;
|
|
266
|
+
/** Every order past par on the book, the wallet's own included: what the bot can fill. */
|
|
267
|
+
orderPool: OrderGroup[];
|
|
268
|
+
/** Every iCKB pool deposit with its claim epoch at this tip: the CKB supply. */
|
|
269
|
+
poolDeposits: IckbDepositCell[];
|
|
270
|
+
/** The caller's withdrawal timing rules, which every builder and the sender follow. */
|
|
271
|
+
lockUp: LockUpPolicy;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/** Manager set that builds one iCKB SDK instance. */
|
|
275
|
+
export interface SdkManagers {
|
|
276
|
+
/** iCKB xUDT manager with receipt and DAO deposit aware completion. */
|
|
277
|
+
ickbUdt: IckbUdt;
|
|
278
|
+
/** Owned-owner manager for withdrawal request and claim cells. */
|
|
279
|
+
ownedOwner: OwnedOwnerManager;
|
|
280
|
+
/** iCKB Logic manager for deposits and receipts. */
|
|
281
|
+
ickbLogic: LogicManager;
|
|
282
|
+
/** Limit order manager. */
|
|
283
|
+
order: OrderManager;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export interface ConversionOrder {
|
|
287
|
+
amounts: ValueComponents;
|
|
288
|
+
estimate: ConversionOrderEstimate;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export interface CkbToIckbConversionPlan {
|
|
292
|
+
depositCapacity: bigint;
|
|
293
|
+
depositCount: number;
|
|
294
|
+
estimatedMaturity: bigint;
|
|
295
|
+
order?: ConversionOrder;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
export interface IckbToCkbConversionPlan {
|
|
299
|
+
estimatedMaturity: bigint;
|
|
300
|
+
order?: ConversionOrder;
|
|
301
|
+
selectedDeposits: IckbDepositCell[];
|
|
302
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { ccc } from "@ckb-ccc/core";
|
|
2
|
+
import { DAO_OUTPUT_LIMIT } from "../dao.ts";
|
|
3
|
+
import type { IckbDepositCell } from "../logic.ts";
|
|
4
|
+
import { ICKB_DEPOSIT_CAP } from "../udt.ts";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Ring segment of pool deposits grouped by maturity around the DAO cycle.
|
|
8
|
+
*/
|
|
9
|
+
export interface RingSegment {
|
|
10
|
+
/** Deposits assigned to this segment. */
|
|
11
|
+
deposits: IckbDepositCell[];
|
|
12
|
+
|
|
13
|
+
/** Total iCKB value of deposits in this segment. */
|
|
14
|
+
udtValue: bigint;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Splits pool deposits into power-of-two maturity ring segments, one segment per two
|
|
19
|
+
* deposit caps of pool iCKB.
|
|
20
|
+
*/
|
|
21
|
+
export function ringSegments(poolDeposits: readonly IckbDepositCell[]): RingSegment[] {
|
|
22
|
+
// Value, not count: zero-value deposits, made without iCKB Logic running, must not add segments.
|
|
23
|
+
// Two caps per segment, not one: the room the bot's own fills need, see node/docs/policy.md.
|
|
24
|
+
const totalUdt = poolDeposits.reduce((sum, deposit) => sum + deposit.udtValue, 0n);
|
|
25
|
+
let segmentCount = 1;
|
|
26
|
+
while (BigInt(segmentCount) * 2n * ICKB_DEPOSIT_CAP < totalUdt) {
|
|
27
|
+
segmentCount *= 2;
|
|
28
|
+
}
|
|
29
|
+
const segments = Array.from({ length: segmentCount }, (): RingSegment => ({
|
|
30
|
+
deposits: [],
|
|
31
|
+
udtValue: 0n,
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
// The ring is one DAO cycle round, so a claim rolled a cycle lands in the same segment.
|
|
35
|
+
for (const deposit of poolDeposits) {
|
|
36
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- ringSegmentIndex returns 0 <= index < segmentCount.
|
|
37
|
+
const segment = segments[ringSegmentIndex(deposit.claimEpoch, segmentCount)]!;
|
|
38
|
+
segment.deposits.push(deposit);
|
|
39
|
+
segment.udtValue += deposit.udtValue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return segments;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The segment index an epoch falls in, for a deposit's claim or the tip. */
|
|
46
|
+
export function ringSegmentIndex(epoch: ccc.Epoch, segmentCount: number): number {
|
|
47
|
+
const { denominator } = epoch;
|
|
48
|
+
if (denominator <= 0n) {
|
|
49
|
+
throw new Error("Epoch denominator must be positive");
|
|
50
|
+
}
|
|
51
|
+
const scaled = epoch.integer * denominator + epoch.numerator;
|
|
52
|
+
const ring = ccc.Epoch.OneNervosDaoCycle.integer * denominator;
|
|
53
|
+
const wrapped = ((scaled % ring) + ring) % ring;
|
|
54
|
+
return Number((wrapped * BigInt(segmentCount)) / ring);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Returns a filter that excludes the ring anchor deposits from surplus selection: each
|
|
59
|
+
* segment keeps its anchor, the largest deposit not in `ready`, else the largest one.
|
|
60
|
+
*/
|
|
61
|
+
export function ringSurplusDepositFilter(
|
|
62
|
+
poolDeposits: readonly IckbDepositCell[],
|
|
63
|
+
ready: readonly IckbDepositCell[],
|
|
64
|
+
): (deposit: IckbDepositCell) => boolean {
|
|
65
|
+
const isReady = new Set(ready.map((deposit) => deposit.cell.outPoint.toHex()));
|
|
66
|
+
const anchors = new Set(
|
|
67
|
+
ringSegments(poolDeposits).flatMap((segment) => {
|
|
68
|
+
const anchor = segment.deposits.reduce<IckbDepositCell | undefined>(
|
|
69
|
+
(best, deposit) =>
|
|
70
|
+
best === undefined || isBetterRingAnchor(deposit, best, isReady)
|
|
71
|
+
? deposit
|
|
72
|
+
: best,
|
|
73
|
+
undefined,
|
|
74
|
+
);
|
|
75
|
+
return anchor === undefined ? [] : [anchor.cell.outPoint.toHex()];
|
|
76
|
+
}),
|
|
77
|
+
);
|
|
78
|
+
return (deposit) => !anchors.has(deposit.cell.outPoint.toHex());
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isBetterRingAnchor(
|
|
82
|
+
candidate: IckbDepositCell,
|
|
83
|
+
current: IckbDepositCell,
|
|
84
|
+
isReady: ReadonlySet<string>,
|
|
85
|
+
): boolean {
|
|
86
|
+
const candidateReady = isReady.has(candidate.cell.outPoint.toHex());
|
|
87
|
+
if (candidateReady !== isReady.has(current.cell.outPoint.toHex())) {
|
|
88
|
+
return !candidateReady;
|
|
89
|
+
}
|
|
90
|
+
return candidate.udtValue > current.udtValue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Walks the candidates in the given order and takes each one that still fits under
|
|
95
|
+
* `maxAmount`; the caller chooses the order, earliest claim first
|
|
96
|
+
* for a user (the wait they feel), surplus before anchors for the bot. The chain stops at
|
|
97
|
+
* the most requests one transaction can carry, a request and its owner marker per deposit;
|
|
98
|
+
* completion steps down from there when change and fees need more room.
|
|
99
|
+
*/
|
|
100
|
+
export function fitWithdrawalDeposits(
|
|
101
|
+
candidates: readonly IckbDepositCell[],
|
|
102
|
+
maxAmount: bigint,
|
|
103
|
+
): IckbDepositCell[] {
|
|
104
|
+
const deposits: IckbDepositCell[] = [];
|
|
105
|
+
let total = 0n;
|
|
106
|
+
for (const deposit of candidates) {
|
|
107
|
+
if (deposits.length === DAO_OUTPUT_LIMIT / 2) {
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
if (total + deposit.udtValue > maxAmount) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
total += deposit.udtValue;
|
|
114
|
+
deposits.push(deposit);
|
|
115
|
+
}
|
|
116
|
+
return deposits;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Earliest claim first: the deposit that turns into CKB soonest. */
|
|
120
|
+
export function sortByClaim(deposits: readonly IckbDepositCell[]): IckbDepositCell[] {
|
|
121
|
+
return deposits.toSorted((left, right) => left.claimEpoch.compare(right.claimEpoch));
|
|
122
|
+
}
|
package/src/dao.ts
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { ccc } from "@ckb-ccc/core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Maximum outputs accepted by deployed dao.c, not a general CKB consensus cap.
|
|
5
|
+
* A node upgrade cannot lift a limit in that deployed script.
|
|
6
|
+
*/
|
|
7
|
+
export const DAO_OUTPUT_LIMIT = 64;
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Deposit-header indices the deployed DAO script can address in a phase-2 witness.
|
|
11
|
+
*
|
|
12
|
+
* @remarks The script reads one byte of the u64 field (RFC 0023 erratum,
|
|
13
|
+
* nervosnetwork/rfcs pull 456).
|
|
14
|
+
*/
|
|
15
|
+
export const DAO_HEADER_INDEX_LIMIT = 256;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Error thrown when a transaction exceeds the deployed DAO script's output limit.
|
|
19
|
+
*/
|
|
20
|
+
export class DaoOutputLimitError extends Error {
|
|
21
|
+
/**
|
|
22
|
+
* Creates an output-limit error for a transaction with too many outputs.
|
|
23
|
+
*/
|
|
24
|
+
constructor(outputCount: number, options?: ErrorOptions) {
|
|
25
|
+
super(
|
|
26
|
+
`NervosDAO transaction has ${String(outputCount)} output cells, exceeding the limit of ${String(DAO_OUTPUT_LIMIT)}`,
|
|
27
|
+
options,
|
|
28
|
+
);
|
|
29
|
+
this.name = "DaoOutputLimitError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Error thrown when a phase-2 withdrawal would reference a deposit header the
|
|
35
|
+
* deployed DAO script cannot address.
|
|
36
|
+
*/
|
|
37
|
+
export class DaoHeaderIndexError extends Error {
|
|
38
|
+
/**
|
|
39
|
+
* Creates a header-index error for one unrepresentable index.
|
|
40
|
+
*/
|
|
41
|
+
constructor(headerIndex: number, options?: ErrorOptions) {
|
|
42
|
+
super(
|
|
43
|
+
`NervosDAO deposit header index ${String(headerIndex)} is not below ${String(DAO_HEADER_INDEX_LIMIT)}`,
|
|
44
|
+
options,
|
|
45
|
+
);
|
|
46
|
+
this.name = "DaoHeaderIndexError";
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Throws when a transaction using the DAO script exceeds the Nervos DAO output limit. An
|
|
52
|
+
* oversized transaction with an unresolved input is refused too: whether it uses the DAO
|
|
53
|
+
* cannot be known, and the completion walk steps a plan down on this error either way.
|
|
54
|
+
*/
|
|
55
|
+
export function assertDaoOutputLimit(
|
|
56
|
+
txLike: ccc.TransactionLike,
|
|
57
|
+
daoScriptLike: ccc.ScriptLike,
|
|
58
|
+
): void {
|
|
59
|
+
const tx = ccc.Transaction.from(txLike);
|
|
60
|
+
if (tx.outputs.length <= DAO_OUTPUT_LIMIT) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const daoScript = ccc.Script.from(daoScriptLike);
|
|
64
|
+
if (
|
|
65
|
+
tx.inputs.some(
|
|
66
|
+
(input) =>
|
|
67
|
+
input.cellOutput === undefined || input.cellOutput.type?.eq(daoScript) === true,
|
|
68
|
+
) ||
|
|
69
|
+
tx.outputs.some((output) => output.type?.eq(daoScript) === true)
|
|
70
|
+
) {
|
|
71
|
+
throw new DaoOutputLimitError(tx.outputs.length);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** The canonical DAO deposit data payload: a zero Uint64, eight zero bytes. */
|
|
76
|
+
export const DAO_DEPOSIT_DATA: ccc.Hex = "0x0000000000000000";
|
|
77
|
+
|
|
78
|
+
/** Whether the cell is a DAO deposit of the given DAO script. */
|
|
79
|
+
export function isDaoDeposit(cell: ccc.CellAny, daoScript: ccc.Script): boolean {
|
|
80
|
+
return (
|
|
81
|
+
cell.outputData === DAO_DEPOSIT_DATA && cell.cellOutput.type?.eq(daoScript) === true
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Whether the cell uses the DAO script and is not deposit-shaped. This structural check
|
|
87
|
+
* does not decode the withdrawal request's deposit block number payload.
|
|
88
|
+
*/
|
|
89
|
+
export function isDaoWithdrawalRequest(
|
|
90
|
+
cell: ccc.CellAny,
|
|
91
|
+
daoScript: ccc.Script,
|
|
92
|
+
): boolean {
|
|
93
|
+
return (
|
|
94
|
+
cell.outputData !== DAO_DEPOSIT_DATA && cell.cellOutput.type?.eq(daoScript) === true
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* A caller's timing rules for withdrawal requests, in epochs (the chain's clock, so no
|
|
100
|
+
* wall-clock drift). Selection: a deposit may be requested when its claim epoch is past
|
|
101
|
+
* `tip + minLockUp` (closer, the request would commit after the claim and lock for another
|
|
102
|
+
* cycle, so the deposit is judged on its next cycle) and before `tip + maxLockUp`.
|
|
103
|
+
* Broadcast: the signed request is sent only while the tip is before the earliest selected
|
|
104
|
+
* claim less `broadcastReserve`, else it is refused before broadcast.
|
|
105
|
+
*/
|
|
106
|
+
export interface LockUpPolicy {
|
|
107
|
+
minLockUp: ccc.Epoch;
|
|
108
|
+
maxLockUp: ccc.Epoch;
|
|
109
|
+
broadcastReserve: ccc.Epoch;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The bot's rules on the nominal four-hour epoch: claims twenty minutes to an hour out,
|
|
114
|
+
* sent while fifteen minutes remain. The bot signs at once, so a short window suffices, and
|
|
115
|
+
* the estimate models the pool's supply on it (`sdk/docs/pool_maturity_estimates.md`).
|
|
116
|
+
*/
|
|
117
|
+
export const BOT_LOCK_UP: LockUpPolicy = {
|
|
118
|
+
minLockUp: ccc.Epoch.from([0n, 1n, 12n]),
|
|
119
|
+
maxLockUp: ccc.Epoch.from([0n, 4n, 16n]),
|
|
120
|
+
broadcastReserve: ccc.Epoch.from([0n, 1n, 16n]),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* A wallet's rules on the nominal four-hour epoch: claims two hours to three days out,
|
|
125
|
+
* sent while ninety minutes remain. A user may leave the request in a wallet popup.
|
|
126
|
+
*/
|
|
127
|
+
export const WALLET_LOCK_UP: LockUpPolicy = {
|
|
128
|
+
minLockUp: ccc.Epoch.from([0n, 1n, 2n]),
|
|
129
|
+
maxLockUp: ccc.Epoch.from([18n, 0n, 1n]),
|
|
130
|
+
broadcastReserve: ccc.Epoch.from([0n, 3n, 8n]),
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* How far a tip may trail the local clock under a policy: the selection margin less the
|
|
135
|
+
* broadcast reserve, the slack a stale tip would eat before a request commits after its
|
|
136
|
+
* claim. Five minutes for the bot, thirty for a wallet.
|
|
137
|
+
*/
|
|
138
|
+
export function tipMaxAgeMs(policy: LockUpPolicy): number {
|
|
139
|
+
// The span from the reserve to the margin, on CCC's nominal four-hour epoch.
|
|
140
|
+
return Number(
|
|
141
|
+
policy.minLockUp.toUnix({ epoch: policy.broadcastReserve, timestamp: 0n }),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Throws when `header` trails the local clock by more than `maxAgeMs`. */
|
|
146
|
+
export function assertFresh(header: ccc.ClientBlockHeader, maxAgeMs: number): void {
|
|
147
|
+
if (BigInt(Date.now()) - header.timestamp > BigInt(maxAgeMs)) {
|
|
148
|
+
throw new Error(`Block ${header.hash} is more than ${String(maxAgeMs)} ms old`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** A deposit's maturity, rolled a cycle when its claim is too close, and whether it is ready. */
|
|
153
|
+
export function depositMaturity(
|
|
154
|
+
claim: ccc.Epoch,
|
|
155
|
+
tip: ccc.ClientBlockHeader,
|
|
156
|
+
{ minLockUp, maxLockUp }: LockUpPolicy,
|
|
157
|
+
): { maturity: ccc.Epoch; isReady: boolean } {
|
|
158
|
+
const maturity =
|
|
159
|
+
claim.compare(minLockUp.add(tip.epoch)) <= 0
|
|
160
|
+
? claim.add(ccc.Epoch.OneNervosDaoCycle)
|
|
161
|
+
: claim;
|
|
162
|
+
return { maturity, isReady: maxLockUp.add(tip.epoch).compare(maturity) > 0 };
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* The epoch a transaction requesting these deposits must be broadcast before: the earliest
|
|
167
|
+
* claim less the reserve; undefined when it requests none.
|
|
168
|
+
*/
|
|
169
|
+
export function broadcastDeadline(
|
|
170
|
+
claims: readonly ccc.Epoch[],
|
|
171
|
+
policy: LockUpPolicy,
|
|
172
|
+
): BroadcastDeadline | undefined {
|
|
173
|
+
const earliest = claims.reduce<ccc.Epoch | undefined>(
|
|
174
|
+
(best, claim) => (best === undefined || claim.compare(best) < 0 ? claim : best),
|
|
175
|
+
undefined,
|
|
176
|
+
);
|
|
177
|
+
return earliest === undefined
|
|
178
|
+
? undefined
|
|
179
|
+
: { epoch: earliest.sub(policy.broadcastReserve), tipMaxAgeMs: tipMaxAgeMs(policy) };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* The epoch a withdrawal request must be broadcast before, and how far the tip read to
|
|
184
|
+
* check it may trail the clock, the policy's slack: a stale tip would pass the check late.
|
|
185
|
+
*/
|
|
186
|
+
export interface BroadcastDeadline {
|
|
187
|
+
epoch: ccc.Epoch;
|
|
188
|
+
tipMaxAgeMs: number;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Adds the header dep once. */
|
|
192
|
+
export function pushHeaderDep(tx: ccc.Transaction, hash: ccc.Hex): void {
|
|
193
|
+
if (!tx.headerDeps.includes(hash)) {
|
|
194
|
+
tx.headerDeps.push(hash);
|
|
195
|
+
}
|
|
196
|
+
}
|