@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
package/src/udt.ts
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { ccc, mol } from "@ckb-ccc/core";
|
|
2
|
+
import { isDaoDeposit } from "./dao.ts";
|
|
3
|
+
import { CheckedUint128LE, CheckedUint32LE, CheckedUint64LE } from "./utils/codec.ts";
|
|
4
|
+
import type { ExchangeRatio } from "./utils/utils.ts";
|
|
5
|
+
|
|
6
|
+
const ickbXudtTypeOccupiedSize = 69;
|
|
7
|
+
const udtDataSize = 16;
|
|
8
|
+
const AR_0: ccc.Num = 10000000000000000n; // Base scale for CKB
|
|
9
|
+
const depositUsedCapacity = ccc.fixedPointFrom(82); // 82n CKB
|
|
10
|
+
const depositCapacityDelta = (depositUsedCapacity * AR_0) / ccc.fixedPointFrom(100000);
|
|
11
|
+
const xudtOwnerMode = 0x80000000n;
|
|
12
|
+
|
|
13
|
+
// A receipt's fixed prefix: 4 bytes of deposit quantity and 8 of deposit amount.
|
|
14
|
+
export const receiptDataBytes = 12;
|
|
15
|
+
|
|
16
|
+
/** The receipt payload for one or more identical iCKB deposits. */
|
|
17
|
+
export interface ReceiptData {
|
|
18
|
+
/** Number of identical deposits represented by this receipt. */
|
|
19
|
+
depositQuantity: ccc.Num;
|
|
20
|
+
/** Free CKB capacity of each represented deposit before iCKB conversion. */
|
|
21
|
+
depositAmount: ccc.FixedPoint;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const ReceiptDataCodec = mol.struct({
|
|
25
|
+
depositQuantity: CheckedUint32LE,
|
|
26
|
+
depositAmount: CheckedUint64LE,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
/** Encodes a receipt payload. */
|
|
30
|
+
export function encodeReceiptData(data: ReceiptData): ccc.Bytes {
|
|
31
|
+
return ReceiptDataCodec.encode(data);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Decodes the fixed receipt-data prefix and ignores trailing cell payload bytes.
|
|
36
|
+
*
|
|
37
|
+
* @remarks The receipt data prefix is 12 bytes after the `0x` marker: 4 bytes for deposit
|
|
38
|
+
* quantity and 8 bytes for deposit amount. Later payload bytes belong to other protocol
|
|
39
|
+
* data and are intentionally tolerated here.
|
|
40
|
+
*/
|
|
41
|
+
export function decodeReceiptData(outputData: ccc.Hex): ReceiptData {
|
|
42
|
+
return ReceiptDataCodec.decode(outputData.slice(0, 2 + 2 * receiptDataBytes));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Soft per-deposit iCKB value cap used before applying the excess discount.
|
|
47
|
+
*/
|
|
48
|
+
export const ICKB_DEPOSIT_CAP = ccc.fixedPointFrom(100000); // 100,000 iCKB
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The iCKB xUDT token: its scripts, code cells, and the iCKB accounting of a
|
|
52
|
+
* transaction's inputs. Inputs carry iCKB as xUDT balances, as receipt value,
|
|
53
|
+
* and negatively as the first-phase deposits a transaction re-mints.
|
|
54
|
+
*/
|
|
55
|
+
export class IckbUdt {
|
|
56
|
+
/** The iCKB xUDT type script. */
|
|
57
|
+
public readonly script: ccc.Script;
|
|
58
|
+
|
|
59
|
+
/** The iCKB dep group, which also carries the xUDT and iCKB Logic code cells. */
|
|
60
|
+
public readonly cellDeps: ccc.CellDep[];
|
|
61
|
+
|
|
62
|
+
/** Logic script whose hash is embedded in this iCKB xUDT type script. */
|
|
63
|
+
public readonly logicScript: ccc.Script;
|
|
64
|
+
|
|
65
|
+
/** The Nervos DAO script, to recognize the first-phase deposits a transaction re-mints. */
|
|
66
|
+
public readonly daoScript: ccc.Script;
|
|
67
|
+
|
|
68
|
+
/** Creates an instance of IckbUdt from its code and script references. */
|
|
69
|
+
constructor({
|
|
70
|
+
script,
|
|
71
|
+
cellDeps,
|
|
72
|
+
logicScript,
|
|
73
|
+
daoScript,
|
|
74
|
+
}: {
|
|
75
|
+
script: ccc.ScriptLike;
|
|
76
|
+
cellDeps: ccc.CellDepLike[];
|
|
77
|
+
logicScript: ccc.ScriptLike;
|
|
78
|
+
daoScript: ccc.ScriptLike;
|
|
79
|
+
}) {
|
|
80
|
+
this.script = ccc.Script.from(script);
|
|
81
|
+
this.cellDeps = cellDeps.map((cellDep) => ccc.CellDep.from(cellDep));
|
|
82
|
+
this.logicScript = ccc.Script.from(logicScript);
|
|
83
|
+
this.daoScript = ccc.Script.from(daoScript);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Computes the iCKB UDT type script from raw UDT and Logic scripts.
|
|
88
|
+
*
|
|
89
|
+
* Concatenates the iCKB logic script hash with the fixed 4-byte little-endian
|
|
90
|
+
* xUDT owner-mode flags postfix ("00000080") to form the UDT type script args.
|
|
91
|
+
*
|
|
92
|
+
* @param udtScript - The raw xUDT script (codeHash and hashType reused).
|
|
93
|
+
* @param ickbLogic - The iCKB logic script (hash used for args).
|
|
94
|
+
* @returns A new Script with the computed args.
|
|
95
|
+
*/
|
|
96
|
+
public static typeScriptFrom(udtScript: ccc.Script, ickbLogic: ccc.Script): ccc.Script {
|
|
97
|
+
const { codeHash, hashType } = udtScript;
|
|
98
|
+
return new ccc.Script(
|
|
99
|
+
codeHash,
|
|
100
|
+
hashType,
|
|
101
|
+
ccc.hexFrom(
|
|
102
|
+
ccc.bytesConcat(ickbLogic.hash(), CheckedUint32LE.encode(xudtOwnerMode)),
|
|
103
|
+
),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Minimum capacity for an iCKB xUDT cell locked by the supplied lock script.
|
|
109
|
+
*/
|
|
110
|
+
public static minimumXudtCellCapacity(lock: ccc.Script): ccc.FixedPoint {
|
|
111
|
+
return (
|
|
112
|
+
BigInt(8 + lock.occupiedSize + ickbXudtTypeOccupiedSize + udtDataSize) * ccc.One
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Returns true when a cell carries this iCKB xUDT type and enough UDT data.
|
|
118
|
+
*/
|
|
119
|
+
public isUdt(cellLike: ccc.CellAnyLike): boolean {
|
|
120
|
+
const cell = ccc.CellAny.from(cellLike);
|
|
121
|
+
return (
|
|
122
|
+
cell.cellOutput.type?.eq(this.script) === true &&
|
|
123
|
+
ccc.bytesFrom(cell.outputData).length >= udtDataSize
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* iCKB carried by the transaction's inputs: xUDT balances plus receipt value, minus the
|
|
129
|
+
* first-phase deposits it re-mints. Receipts and deposits take the value the state read
|
|
130
|
+
* gave them at their deposit headers; a protocol input missing from `protocolCells` is
|
|
131
|
+
* a builder bug and throws, since iCKB Logic would refuse whatever it was valued at.
|
|
132
|
+
*/
|
|
133
|
+
public async inputBalance(
|
|
134
|
+
tx: ccc.Transaction,
|
|
135
|
+
client: ccc.Client,
|
|
136
|
+
protocolCells: readonly ProtocolCell[],
|
|
137
|
+
): Promise<ccc.Num> {
|
|
138
|
+
const values = new Map(
|
|
139
|
+
protocolCells.map(({ cell, udtValue }) => [cell.outPoint.toHex(), udtValue]),
|
|
140
|
+
);
|
|
141
|
+
let balance = ccc.Zero;
|
|
142
|
+
for (const input of tx.inputs) {
|
|
143
|
+
const cell = await input.getCell(client);
|
|
144
|
+
if (this.isUdt(cell)) {
|
|
145
|
+
balance += ccc.udtBalanceFrom(cell.outputData);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const sign = this.protocolSign(cell);
|
|
149
|
+
if (sign === 0n) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const value = values.get(cell.outPoint.toHex());
|
|
153
|
+
if (value === undefined) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`iCKB input ${cell.outPoint.toHex()} has no value from the state read`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
balance += sign * value;
|
|
159
|
+
}
|
|
160
|
+
return balance;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/** 1 for a receipt, -1 for a first-phase iCKB deposit, 0 for any other cell. */
|
|
164
|
+
private protocolSign(cell: ccc.Cell): bigint {
|
|
165
|
+
const { type, lock } = cell.cellOutput;
|
|
166
|
+
if (type !== undefined && this.logicScript.eq(type)) {
|
|
167
|
+
return 1n;
|
|
168
|
+
}
|
|
169
|
+
return this.logicScript.eq(lock) && isDaoDeposit(cell, this.daoScript) ? -1n : 0n;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** Adds the iCKB change output when the surplus is positive; zero needs no output. */
|
|
173
|
+
public addChange(tx: ccc.Transaction, lock: ccc.ScriptLike, balance: ccc.Num): void {
|
|
174
|
+
if (balance <= ccc.Zero) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
tx.addOutput({ lock, type: this.script }, CheckedUint128LE.encode(balance));
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Calculates iCKB value for unoccupied CKB capacity at a deposit header.
|
|
183
|
+
*
|
|
184
|
+
* @remarks
|
|
185
|
+
* Values above {@link ICKB_DEPOSIT_CAP} receive a 10% discount only on the
|
|
186
|
+
* excess amount.
|
|
187
|
+
*/
|
|
188
|
+
export function ickbValue(
|
|
189
|
+
ckbUnoccupiedCapacity: ccc.FixedPoint,
|
|
190
|
+
header: ccc.ClientBlockHeader,
|
|
191
|
+
): ccc.FixedPoint {
|
|
192
|
+
let ickbAmount = udtFromCkb(ckbUnoccupiedCapacity, ickbAccountingRatio(header));
|
|
193
|
+
if (ICKB_DEPOSIT_CAP < ickbAmount) {
|
|
194
|
+
// Apply a 10% discount for the amount exceeding the soft iCKB cap per deposit.
|
|
195
|
+
ickbAmount -= (ickbAmount - ICKB_DEPOSIT_CAP) / 10n;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return ickbAmount;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** The iCKB worth `amount` CKB at `ratio`, rounded down. */
|
|
202
|
+
export function udtFromCkb(amount: ccc.FixedPoint, ratio: ExchangeRatio): ccc.FixedPoint {
|
|
203
|
+
assertPositive(ratio);
|
|
204
|
+
return (amount * ratio.ckbScale) / ratio.udtScale;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** The CKB worth `amount` iCKB at `ratio`, rounded down. */
|
|
208
|
+
export function ckbFromUdt(amount: ccc.FixedPoint, ratio: ExchangeRatio): ccc.FixedPoint {
|
|
209
|
+
assertPositive(ratio);
|
|
210
|
+
return (amount * ratio.udtScale) / ratio.ckbScale;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function assertPositive({ ckbScale, udtScale }: ExchangeRatio): void {
|
|
214
|
+
if (ckbScale <= 0n || udtScale <= 0n) {
|
|
215
|
+
throw new Error("Exchange ratio scales must be positive");
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Calculates the free-capacity accounting ratio at a block header.
|
|
221
|
+
*
|
|
222
|
+
* @param header - The block header whose DAO accumulated rate is used.
|
|
223
|
+
* @returns An object containing the CKB and UDT scales.
|
|
224
|
+
*/
|
|
225
|
+
export function ickbAccountingRatio(header: ccc.ClientBlockHeader): ExchangeRatio {
|
|
226
|
+
return {
|
|
227
|
+
ckbScale: AR_0,
|
|
228
|
+
udtScale: header.dao.ar,
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Calculates the gross iCKB exchange ratio for a recoverable standard deposit.
|
|
234
|
+
*
|
|
235
|
+
* @remarks Includes the standard deposit's 82 CKB occupied capacity spread
|
|
236
|
+
* over the 100,000 iCKB reference amount.
|
|
237
|
+
*/
|
|
238
|
+
export function ickbExchangeRatio(header: ccc.ClientBlockHeader): ExchangeRatio {
|
|
239
|
+
return {
|
|
240
|
+
ckbScale: AR_0,
|
|
241
|
+
udtScale: header.dao.ar + depositCapacityDelta,
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/** A receipt or iCKB deposit with the iCKB value the state read gave it. */
|
|
246
|
+
export interface ProtocolCell {
|
|
247
|
+
cell: ccc.Cell;
|
|
248
|
+
udtValue: ccc.Num;
|
|
249
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { ccc } from "@ckb-ccc/core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Codec for little-endian 32-bit signed integers.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* The encoder rejects numeric values outside the signed 32-bit range before
|
|
8
|
+
* writing bytes. Decoding reads a signed little-endian integer from the bytes
|
|
9
|
+
* after CCC byte normalization.
|
|
10
|
+
*/
|
|
11
|
+
export const CheckedInt32LE = ccc.Codec.from<ccc.NumLike, number>({
|
|
12
|
+
byteLength: 4,
|
|
13
|
+
encode: (numLike) => {
|
|
14
|
+
const normalized = normalizeInteger(numLike);
|
|
15
|
+
if (normalized < -2147483648n || normalized > 2147483647n) {
|
|
16
|
+
throw new Error("NumLike out of int32 bounds");
|
|
17
|
+
}
|
|
18
|
+
const num = Number(normalized);
|
|
19
|
+
const encoded = new Uint8Array(4);
|
|
20
|
+
new DataView(encoded.buffer).setInt32(0, num, true);
|
|
21
|
+
return encoded;
|
|
22
|
+
},
|
|
23
|
+
decode: (bytesLike) => {
|
|
24
|
+
const bytes = ccc.bytesFrom(bytesLike);
|
|
25
|
+
return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getInt32(
|
|
26
|
+
0,
|
|
27
|
+
true,
|
|
28
|
+
);
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// CCC's unsigned codecs throw on overflow but encode a negative as its two's complement
|
|
33
|
+
// without a word, and `Ratio.toBytes()` is public and skips `validate`, so these checked
|
|
34
|
+
// codecs keep "never encode a negative" in one place; CCC has no signed Int32 codec at all.
|
|
35
|
+
|
|
36
|
+
/** Checked codec for 8-bit unsigned integers. */
|
|
37
|
+
export const CheckedUint8 = checkedUintLE(1);
|
|
38
|
+
|
|
39
|
+
/** Checked codec for little-endian 32-bit unsigned integers. */
|
|
40
|
+
export const CheckedUint32LE = checkedUintLE(4);
|
|
41
|
+
|
|
42
|
+
/** Checked codec for little-endian 64-bit unsigned integers. */
|
|
43
|
+
export const CheckedUint64LE = checkedUintLE(8);
|
|
44
|
+
|
|
45
|
+
/** Checked codec for little-endian 128-bit unsigned integers. */
|
|
46
|
+
export const CheckedUint128LE = checkedUintLE(16);
|
|
47
|
+
|
|
48
|
+
function checkedUintLE(byteLength: number): ccc.Codec<ccc.NumLike, ccc.Num> {
|
|
49
|
+
const bitLength = BigInt(byteLength * 8);
|
|
50
|
+
const max = (1n << bitLength) - 1n;
|
|
51
|
+
return ccc.Codec.from({
|
|
52
|
+
byteLength,
|
|
53
|
+
encode: (numLike: ccc.NumLike) => {
|
|
54
|
+
const num = normalizeInteger(numLike);
|
|
55
|
+
if (num < 0n || num > max) {
|
|
56
|
+
throw new Error(`NumLike out of uint${String(bitLength)} bounds`);
|
|
57
|
+
}
|
|
58
|
+
return ccc.numLeToBytes(num, byteLength);
|
|
59
|
+
},
|
|
60
|
+
decode: ccc.numFromBytes,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function normalizeInteger(numLike: ccc.NumLike): ccc.Num {
|
|
65
|
+
if (typeof numLike === "number") {
|
|
66
|
+
if (!Number.isFinite(numLike) || !Number.isInteger(numLike)) {
|
|
67
|
+
throw new TypeError("NumLike must be a finite integer");
|
|
68
|
+
}
|
|
69
|
+
if (!Number.isSafeInteger(numLike)) {
|
|
70
|
+
throw new TypeError("NumLike number must be a safe integer");
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
return ccc.numFrom(numLike);
|
|
75
|
+
} catch {
|
|
76
|
+
throw new TypeError("NumLike must be a finite integer");
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { ccc } from "@ckb-ccc/core";
|
|
2
|
+
import { cccA } from "@ckb-ccc/core/advanced";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The default page size used when querying cells from the chain.
|
|
6
|
+
*
|
|
7
|
+
* This page size is aligned with Nervos CKB's pull request #4576
|
|
8
|
+
* (https://github.com/nervosnetwork/ckb/pull/4576) to avoid excessive paging.
|
|
9
|
+
*/
|
|
10
|
+
export const defaultCellPageSize = 400;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A CCC client cache sized for a whole pool: 4096 headers and 4096 transaction bodies,
|
|
14
|
+
* where CCC's defaults keep 128 and 256 (SDK README, Account state). An LRU fills only as
|
|
15
|
+
* far as the book and pool need.
|
|
16
|
+
*/
|
|
17
|
+
export function clientCache(): ccc.ClientCacheMemory {
|
|
18
|
+
return new ccc.ClientCacheMemory(512, 4096, 4096);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** A committed cell and the block the node names for its creating transaction. */
|
|
22
|
+
export interface ScannedCell {
|
|
23
|
+
cell: ccc.Cell;
|
|
24
|
+
blockNumber: ccc.Num;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Reads every committed cell matching the key with the block the node names for it, page
|
|
29
|
+
* by page through the raw requestor (CCC's typed scan drops the block), bypassing CCC's
|
|
30
|
+
* cell cache. A full page whose cursor does not advance throws. Why: SDK README, Account
|
|
31
|
+
* state.
|
|
32
|
+
*/
|
|
33
|
+
export async function findCells(
|
|
34
|
+
client: ccc.Client,
|
|
35
|
+
keyLike: ccc.ClientIndexerSearchKeyLike,
|
|
36
|
+
): Promise<ScannedCell[]> {
|
|
37
|
+
const requestor = jsonRpcRequestor(client);
|
|
38
|
+
const key = cccA.JsonRpcTransformers.indexerSearchKeyFrom(keyLike);
|
|
39
|
+
const cells: ScannedCell[] = [];
|
|
40
|
+
let after: string | undefined;
|
|
41
|
+
for (;;) {
|
|
42
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion, no-restricted-syntax -- the node's indexer answer, parsed field by field below as CCC's own transformer does.
|
|
43
|
+
const page = (await requestor.request("get_cells", [
|
|
44
|
+
key,
|
|
45
|
+
"asc",
|
|
46
|
+
ccc.numToHex(defaultCellPageSize),
|
|
47
|
+
after,
|
|
48
|
+
])) as RawCellsPage;
|
|
49
|
+
for (const object of page.objects) {
|
|
50
|
+
cells.push({
|
|
51
|
+
cell: ccc.Cell.from({
|
|
52
|
+
outPoint: cccA.JsonRpcTransformers.outPointTo(object.out_point),
|
|
53
|
+
cellOutput: cccA.JsonRpcTransformers.cellOutputTo(object.output),
|
|
54
|
+
outputData: object.output_data ?? "0x",
|
|
55
|
+
}),
|
|
56
|
+
blockNumber: ccc.numFrom(object.block_number),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (page.objects.length < defaultCellPageSize) {
|
|
60
|
+
return cells;
|
|
61
|
+
}
|
|
62
|
+
if (page.last_cursor === after) {
|
|
63
|
+
throw new Error("Cell scan cursor did not advance");
|
|
64
|
+
}
|
|
65
|
+
after = page.last_cursor;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The headers of a batch of block numbers, each read once; every block must exist. */
|
|
70
|
+
export async function headersByNumber(
|
|
71
|
+
client: ccc.Client,
|
|
72
|
+
blockNumbers: Iterable<ccc.Num>,
|
|
73
|
+
): Promise<Map<ccc.Num, ccc.ClientBlockHeader>> {
|
|
74
|
+
const distinct = [...new Set(blockNumbers)];
|
|
75
|
+
const headers = await Promise.all(
|
|
76
|
+
distinct.map(async (blockNumber) => client.getHeaderByNumber(blockNumber)),
|
|
77
|
+
);
|
|
78
|
+
return new Map(
|
|
79
|
+
distinct.map((blockNumber, index) => {
|
|
80
|
+
const header = headers[index];
|
|
81
|
+
if (header === undefined) {
|
|
82
|
+
throw new Error(`Header not found for block number ${String(blockNumber)}`);
|
|
83
|
+
}
|
|
84
|
+
return [blockNumber, header];
|
|
85
|
+
}),
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** The fields of the indexer's `get_cells` answer the scan reads. */
|
|
90
|
+
interface RawCellsPage {
|
|
91
|
+
last_cursor: string;
|
|
92
|
+
objects: Array<{
|
|
93
|
+
out_point: cccA.JsonRpcOutPoint;
|
|
94
|
+
output: cccA.JsonRpcCellOutput;
|
|
95
|
+
output_data?: ccc.Hex;
|
|
96
|
+
block_number: ccc.Hex;
|
|
97
|
+
}>;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* CKB and UDT amounts carried by a cell, order, or planned value.
|
|
102
|
+
*/
|
|
103
|
+
export interface ValueComponents {
|
|
104
|
+
/** CKB-side amount as a `ccc.FixedPoint`. */
|
|
105
|
+
ckbValue: ccc.FixedPoint;
|
|
106
|
+
|
|
107
|
+
/** UDT-side amount as a `ccc.FixedPoint`. */
|
|
108
|
+
udtValue: ccc.FixedPoint;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Integer scale pair for comparing or converting CKB-side and UDT-side values.
|
|
113
|
+
*
|
|
114
|
+
* @remarks
|
|
115
|
+
* CKB-to-UDT conversions multiply by `ckbScale` and divide by `udtScale`.
|
|
116
|
+
* UDT-to-CKB conversions swap the scales. Callers choose the rounding policy.
|
|
117
|
+
*/
|
|
118
|
+
export interface ExchangeRatio {
|
|
119
|
+
/** Numerator scale for CKB-side values. */
|
|
120
|
+
ckbScale: ccc.Num;
|
|
121
|
+
|
|
122
|
+
/** Numerator scale for UDT-side values. */
|
|
123
|
+
udtScale: ccc.Num;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Script plus cell dependencies needed to build transactions that use it.
|
|
128
|
+
*/
|
|
129
|
+
export interface ScriptDeps {
|
|
130
|
+
/**
|
|
131
|
+
* The lock or type script.
|
|
132
|
+
*/
|
|
133
|
+
script: ccc.Script;
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Cell dependencies required to resolve the script code.
|
|
137
|
+
*/
|
|
138
|
+
cellDeps: ccc.CellDep[];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* True when a cell has no type script and no data payload.
|
|
143
|
+
*
|
|
144
|
+
* @remarks
|
|
145
|
+
* This is a structural filter for plain capacity cells. Spendability still
|
|
146
|
+
* depends on the lock script, live cell state, and transaction context.
|
|
147
|
+
*/
|
|
148
|
+
export function isPlainCapacityCell(cell: ccc.Cell): boolean {
|
|
149
|
+
return cell.cellOutput.type === undefined && cell.outputData === "0x";
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* The client's JSON-RPC requestor, which the SDK's raw reads need: scans with block
|
|
154
|
+
* numbers, status polls and transaction proofs. The connector hands the interface a
|
|
155
|
+
* composition proxy over the public client (`ClientWithFeeRate`, a `ccc.Client` but not a
|
|
156
|
+
* `ccc.ClientJsonRpc`), so `instanceof` misses it; the proxy forwards the property.
|
|
157
|
+
*/
|
|
158
|
+
export function jsonRpcRequestor(client: ccc.Client): ccc.RequestorJsonRpc {
|
|
159
|
+
const requestor = "requestor" in client ? client.requestor : undefined;
|
|
160
|
+
if (!(requestor instanceof ccc.RequestorJsonRpc)) {
|
|
161
|
+
throw new TypeError("The SDK needs a JSON-RPC client");
|
|
162
|
+
}
|
|
163
|
+
return requestor;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** `numerator / denominator` rounded up; the denominator is positive. */
|
|
167
|
+
export function ceilDiv(numerator: bigint, denominator: bigint): bigint {
|
|
168
|
+
return (numerator + denominator - 1n) / denominator;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Compares two bigint values using sort-compatible ordering.
|
|
173
|
+
*
|
|
174
|
+
* @returns `-1` when `left` is smaller, `1` when `left` is larger, and `0` when equal.
|
|
175
|
+
*/
|
|
176
|
+
export function compareBigInt(left: bigint, right: bigint): number {
|
|
177
|
+
if (left < right) {
|
|
178
|
+
return -1;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (left > right) {
|
|
182
|
+
return 1;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return 0;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Yields unique items from the given iterable based on their hex representation.
|
|
190
|
+
*
|
|
191
|
+
* The function uses a Set to track the hex-string keys of items that have already been yielded.
|
|
192
|
+
* Only the first occurrence of each unique key is yielded.
|
|
193
|
+
*
|
|
194
|
+
* @typeParam T - A type that extends ccc.Entity.
|
|
195
|
+
* @param items - An iterable collection of items of type T.
|
|
196
|
+
* @returns A generator that yields items from the iterable, ensuring that each item's
|
|
197
|
+
* hex representation (via toHex()) is unique.
|
|
198
|
+
*/
|
|
199
|
+
export function* unique<T extends ccc.Entity>(items: Iterable<T>): Generator<T> {
|
|
200
|
+
const set = new Set<string>();
|
|
201
|
+
for (const i of items) {
|
|
202
|
+
const key = i.toHex();
|
|
203
|
+
if (!set.has(key)) {
|
|
204
|
+
set.add(key);
|
|
205
|
+
yield i;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { ccc } from "@ckb-ccc/core";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wraps `inner` into a client that checks the reads the SDK makes: every header it hands
|
|
5
|
+
* out against the header's own hash, a header read by number against the number asked
|
|
6
|
+
* for, a transaction read by hash against the hash of its body, and a cell against the
|
|
7
|
+
* body of its creating transaction. Other reads pass through unchecked; the cell
|
|
8
|
+
* beside `getCellWithHeader`'s header is one, since the SDK reads only its header.
|
|
9
|
+
*
|
|
10
|
+
* @remarks It wraps the client, not the SDK's readers, because CCC's own DAO valuation
|
|
11
|
+
* (`Cell.getNervosDaoInfo`, in the send) reads headers through the client it is given.
|
|
12
|
+
* Why each check exists: SDK README, Account state.
|
|
13
|
+
*/
|
|
14
|
+
export function verifiedClient(inner: ccc.Client): ccc.Client {
|
|
15
|
+
return new VerifiedClient(inner);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// @ts-expect-error TS2655: the abstract members are forwarded by the proxy at runtime, as in CCC's own connector.
|
|
19
|
+
class VerifiedClient extends ccc.Proxy.Base(ccc.Client) {
|
|
20
|
+
public static readonly [ccc.Proxy.localKeys] = ["bodies"];
|
|
21
|
+
|
|
22
|
+
/** Creating transactions `getCell` read, so concurrent reads of one share a request. */
|
|
23
|
+
private readonly bodies = new Map<
|
|
24
|
+
ccc.Hex,
|
|
25
|
+
Promise<ccc.ClientTransactionResponse | undefined>
|
|
26
|
+
>();
|
|
27
|
+
|
|
28
|
+
public override async getTipHeader(): Promise<ccc.ClientBlockHeader> {
|
|
29
|
+
return verified(await this[ccc.Proxy.inner].getTipHeader());
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
public override async getHeaderByNumber(
|
|
33
|
+
...args: Parameters<ccc.Client["getHeaderByNumber"]>
|
|
34
|
+
): Promise<ccc.ClientBlockHeader | undefined> {
|
|
35
|
+
const header = ccc.apply(
|
|
36
|
+
verified,
|
|
37
|
+
await this[ccc.Proxy.inner].getHeaderByNumber(...args),
|
|
38
|
+
);
|
|
39
|
+
const number = ccc.numFrom(args[0]);
|
|
40
|
+
if (header !== undefined && header.number !== number) {
|
|
41
|
+
throw new Error(`Header ${header.hash} answers for block ${number.toString()}`);
|
|
42
|
+
}
|
|
43
|
+
return header;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
public override async getTransaction(
|
|
47
|
+
...args: Parameters<ccc.Client["getTransaction"]>
|
|
48
|
+
): ReturnType<ccc.Client["getTransaction"]> {
|
|
49
|
+
return verifiedBody(await this[ccc.Proxy.inner].getTransaction(...args), args[0]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The output of the verified creating transaction, cache first: CCC keys cached
|
|
54
|
+
* transactions by their body's hash, and its own `getCell` would build the cell from a
|
|
55
|
+
* read this wrapper never sees, then cache it.
|
|
56
|
+
*/
|
|
57
|
+
public override async getCell(
|
|
58
|
+
...args: Parameters<ccc.Client["getCell"]>
|
|
59
|
+
): ReturnType<ccc.Client["getCell"]> {
|
|
60
|
+
const outPoint = ccc.OutPoint.from(args[0]);
|
|
61
|
+
const { txHash } = outPoint;
|
|
62
|
+
let body = this.bodies.get(txHash);
|
|
63
|
+
if (body === undefined) {
|
|
64
|
+
body = this.readBody(txHash);
|
|
65
|
+
this.bodies.set(txHash, body);
|
|
66
|
+
}
|
|
67
|
+
const output = (await body)?.transaction.getOutput(outPoint.index);
|
|
68
|
+
return output === undefined
|
|
69
|
+
? undefined
|
|
70
|
+
: ccc.Cell.from({
|
|
71
|
+
outPoint,
|
|
72
|
+
cellOutput: output.cellOutput,
|
|
73
|
+
outputData: output.outputData,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private async readBody(
|
|
78
|
+
txHash: ccc.Hex,
|
|
79
|
+
): Promise<ccc.ClientTransactionResponse | undefined> {
|
|
80
|
+
// CCC's own cache keys bodies by their hash, but a consumer's cache need not.
|
|
81
|
+
return (
|
|
82
|
+
verifiedBody(await this.cache.getTransactionResponse(txHash), txHash) ??
|
|
83
|
+
(await this.getTransaction(txHash))
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public override async getCellWithHeader(
|
|
88
|
+
...args: Parameters<ccc.Client["getCellWithHeader"]>
|
|
89
|
+
): ReturnType<ccc.Client["getCellWithHeader"]> {
|
|
90
|
+
const result = await this[ccc.Proxy.inner].getCellWithHeader(...args);
|
|
91
|
+
ccc.apply(verified, result?.header);
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** The response when its body hashes to the hash asked for, else throws. */
|
|
97
|
+
function verifiedBody(
|
|
98
|
+
response: ccc.ClientTransactionResponse | undefined,
|
|
99
|
+
txHash: ccc.HexLike,
|
|
100
|
+
): ccc.ClientTransactionResponse | undefined {
|
|
101
|
+
const asked = ccc.hexFrom(txHash);
|
|
102
|
+
if (response !== undefined && response.transaction.hash() !== asked) {
|
|
103
|
+
throw new Error(`Transaction ${asked} was answered with a body of another hash`);
|
|
104
|
+
}
|
|
105
|
+
return response;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** The header when its fields hash to the hash it carries, else throws. */
|
|
109
|
+
function verified(header: ccc.ClientBlockHeader): ccc.ClientBlockHeader {
|
|
110
|
+
const computed = headerHash(header);
|
|
111
|
+
if (computed !== header.hash) {
|
|
112
|
+
throw new Error(`Header ${header.hash} carries the fields of ${computed}`);
|
|
113
|
+
}
|
|
114
|
+
return header;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* The hash of a header as the chain computes it: blake2b over the header struct, each
|
|
119
|
+
* number little-endian, in declaration order, the nonce last.
|
|
120
|
+
*/
|
|
121
|
+
export function headerHash(header: ccc.ClientBlockHeader): ccc.Hex {
|
|
122
|
+
const { dao } = header;
|
|
123
|
+
return ccc.hashCkb(
|
|
124
|
+
ccc.numLeToBytes(header.version, 4),
|
|
125
|
+
ccc.numLeToBytes(header.compactTarget, 4),
|
|
126
|
+
ccc.numLeToBytes(header.timestamp, 8),
|
|
127
|
+
ccc.numLeToBytes(header.number, 8),
|
|
128
|
+
ccc.numLeToBytes(header.epoch.toNum(), 8),
|
|
129
|
+
header.parentHash,
|
|
130
|
+
header.transactionsRoot,
|
|
131
|
+
header.proposalsHash,
|
|
132
|
+
header.extraHash,
|
|
133
|
+
...[dao.c, dao.ar, dao.s, dao.u].map((field) => ccc.numLeToBytes(field, 8)),
|
|
134
|
+
ccc.numLeToBytes(header.nonce, 16),
|
|
135
|
+
);
|
|
136
|
+
}
|
package/dist/codec.d.ts
DELETED
package/dist/codec.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"codec.d.ts","sourceRoot":"../src/","sources":["codec.ts"],"names":[],"mappings":"AAAA,OAAO,EAAO,GAAG,EAAE,MAAM,eAAe,CAAC;AAgBzC,eAAO,MAAM,YAAY,+BAkDvB,CAAC"}
|