@ickb/utils
Version:
General utilities built on top of CCC
201 lines • 7.7 kB
JavaScript
import { ccc, mol } from "@ckb-ccc/core";
import { getHeader, hexFrom } from "./utils.js";
export class SmartTransaction extends ccc.Transaction {
constructor(version, cellDeps, headerDeps, inputs, outputs, outputsData, witnesses, udtHandlers, headers) {
super(version, cellDeps, headerDeps, inputs, outputs, outputsData, witnesses);
Object.defineProperty(this, "udtHandlers", {
enumerable: true,
configurable: true,
writable: true,
value: udtHandlers
});
Object.defineProperty(this, "headers", {
enumerable: true,
configurable: true,
writable: true,
value: headers
});
}
async completeFee(...args) {
const signer = args[0];
const options = args[4];
let inAdded = 0;
let addedChange = false;
for (const handler of this.udtHandlers.values()) {
const res = await handler.completeUdt(signer, this, options);
inAdded += res[0];
addedChange || (addedChange = res[1]);
}
const res = await super.completeFee(...args);
inAdded += res[0];
addedChange || (addedChange = res[1]);
const { hashType, codeHash } = await signer.client.getKnownScript(ccc.KnownScript.NervosDao);
const dao = ccc.Script.from({ codeHash, hashType, args: "0x" });
const isDaoTx = this.inputs.some((c) => c.cellOutput?.type?.eq(dao)) ||
this.outputs.some((c) => c.type?.eq(dao));
if (isDaoTx && this.outputs.length > 64) {
throw Error("More than 64 output cells in a NervosDAO transaction");
}
return [inAdded, addedChange];
}
getInputsUdtBalance(client, udtLike) {
const udt = ccc.Script.from(udtLike);
return (this.getUdtHandler(udt)
?.getInputsUdtBalance(client, this)
.then((b) => b[0]) ?? super.getInputsUdtBalance(client, udt));
}
getOutputsUdtBalance(udtLike) {
const udt = ccc.Script.from(udtLike);
return (this.getUdtHandler(udt)?.getOutputsUdtBalance(this)[0] ??
super.getOutputsUdtBalance(udt));
}
async getInputsCapacity(client) {
const { hashType, codeHash } = await client.getKnownScript(ccc.KnownScript.NervosDao);
const dao = ccc.Script.from({ codeHash, hashType, args: "0x" });
return ccc.reduceAsync(this.inputs, async (total, input) => {
await input.completeExtraInfos(client);
const { previousOutput: outPoint, cellOutput, outputData } = input;
if (!cellOutput || !outputData) {
throw Error("Unable to complete input");
}
const cell = ccc.Cell.from({
outPoint,
cellOutput,
outputData,
});
total += cellOutput.capacity;
if (outputData === "0x0000000000000000" || !cellOutput.type?.eq(dao)) {
return total;
}
const withdrawHeader = await this.getHeader(client, {
type: "txHash",
value: outPoint.txHash,
});
const depositHeader = await this.getHeader(client, {
type: "number",
value: mol.Uint64LE.decode(outputData),
});
return (total +
ccc.calcDaoProfit(cell.capacityFree, depositHeader, withdrawHeader));
}, 0n);
}
encodeUdtKey(udt) {
return hexFrom(ccc.Script.from(udt));
}
getUdtHandler(udt) {
return this.udtHandlers.get(this.encodeUdtKey(udt));
}
hasUdtHandler(udt) {
return this.udtHandlers.has(this.encodeUdtKey(udt));
}
addUdtHandlers(...udtHandlers) {
udtHandlers.flat().forEach((udtHandler) => {
this.udtHandlers.set(this.encodeUdtKey(udtHandler.script), udtHandler);
this.addCellDeps(udtHandler.cellDeps);
});
}
encodeHeaderKey(headerKey) {
const { type, value } = headerKey;
return hexFrom(value) + type;
}
addHeaders(...headers) {
headers.flat().forEach(({ header, txHash }) => {
const { hash, number } = header;
const keys = [
this.encodeHeaderKey({
type: "hash",
value: hash,
}),
this.encodeHeaderKey({
type: "number",
value: number,
}),
];
if (txHash) {
keys.push(this.encodeHeaderKey({
type: "txHash",
value: txHash,
}));
}
for (const key of keys) {
const h = this.headers.get(key);
if (!h) {
this.headers.set(key, header);
}
else if (hash == h.hash) {
header = h;
}
else {
throw Error("Found two hashes for the same header");
}
}
if (!this.headerDeps.some((h) => h === hash)) {
this.headerDeps.push(hash);
}
});
}
async getHeader(client, headerKey) {
const key = this.encodeHeaderKey(headerKey);
let header = this.headers.get(key);
if (!header) {
header = await getHeader(client, headerKey);
const headerDepsLength = this.headerDeps.length;
this.addHeaders({
header,
txHash: headerKey.type === "txHash" ? headerKey.value : undefined,
});
if (headerDepsLength !== this.headerDeps.length) {
throw Error("Header was not present in HeaderDeps");
}
}
else {
const { hash } = header;
if (!this.headerDeps.some((h) => h === hash)) {
throw Error("Header not found in HeaderDeps");
}
}
return header;
}
static default() {
return new SmartTransaction(0n, [], [], [], [], [], [], new Map(), new Map());
}
clone() {
const result = SmartTransaction.from(super.clone());
result.udtHandlers = this.udtHandlers;
result.headers = this.headers;
return result;
}
copy(txLike) {
const tx = SmartTransaction.from(txLike);
this.version = tx.version;
this.cellDeps = tx.cellDeps;
this.headerDeps = tx.headerDeps;
this.inputs = tx.inputs;
this.outputs = tx.outputs;
this.outputsData = tx.outputsData;
this.witnesses = tx.witnesses;
if (this.udtHandlers !== tx.udtHandlers) {
for (const [k, h] of tx.udtHandlers.entries()) {
this.udtHandlers.set(k, h);
}
}
if (this.headers !== tx.headers) {
for (const [k, h] of tx.headers.entries()) {
this.headers.set(k, h);
}
}
}
static fromLumosSkeleton(skeleton) {
return SmartTransaction.from(super.fromLumosSkeleton(skeleton));
}
static from(txLike) {
if (txLike instanceof SmartTransaction) {
return txLike;
}
const { version, cellDeps, headerDeps, inputs, outputs, outputsData, witnesses, } = ccc.Transaction.from(txLike);
const udtHandlers = txLike.udtHandlers ?? new Map();
const headers = txLike.headers ?? new Map();
return new SmartTransaction(version, cellDeps, headerDeps, inputs, outputs, outputsData, witnesses, udtHandlers, headers);
}
}
//# sourceMappingURL=transaction.js.map