UNPKG

@ickb/utils

Version:

General utilities built on top of CCC

201 lines 7.7 kB
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