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