From ec643ac6bbfeacd3e444ccf5970ea070a30fce8f Mon Sep 17 00:00:00 2001 From: Hanssen0 <0@hanssen0.com> Date: Fri, 3 Jul 2026 07:28:29 +0800 Subject: [PATCH 1/2] feat(core)!: @codec implements a default from --- .changeset/sixty-ants-hang.md | 6 + packages/core/src/codec/entity.test.ts | 164 ++++++++++++++++++++++++- packages/core/src/codec/entity.ts | 40 ++++-- 3 files changed, 195 insertions(+), 15 deletions(-) create mode 100644 .changeset/sixty-ants-hang.md diff --git a/.changeset/sixty-ants-hang.md b/.changeset/sixty-ants-hang.md new file mode 100644 index 000000000..fcf400b1c --- /dev/null +++ b/.changeset/sixty-ants-hang.md @@ -0,0 +1,6 @@ +--- +"@ckb-ccc/core": major +--- + +feat(core)!: @codec implements a default from + \ No newline at end of file diff --git a/packages/core/src/codec/entity.test.ts b/packages/core/src/codec/entity.test.ts index e40dbef1a..257ecda8c 100644 --- a/packages/core/src/codec/entity.test.ts +++ b/packages/core/src/codec/entity.test.ts @@ -1,7 +1,9 @@ -import { describe, expect, test } from "vitest"; +import { describe, expect, it, test } from "vitest"; import { bytesFrom } from "../bytes/index.js"; +import { hashCkb } from "../hasher/index.js"; +import { hexFrom } from "../hex/index.js"; import { mol } from "../molecule/index.js"; -import { Codec } from "./codec.js"; +import { Codec, DecodedType, EncodableType } from "./codec.js"; import { codec, Entity } from "./entity.js"; interface FooLike { @@ -18,7 +20,7 @@ class Foo extends Entity.Base() { public readonly v: number; public readonly via: string; - constructor(v: number, via: string) { + constructor({ v }: { v: number }, via: string) { super(); this.v = v; this.via = via; @@ -28,11 +30,11 @@ class Foo extends Entity.Base() { if (fooLike instanceof Foo) { return fooLike; } - return new Foo(fooLike.v, "Foo"); + return new Foo({ v: fooLike.v }, "Foo"); } override clone(): Foo { - return new Foo(this.v, this.via); + return new Foo({ v: this.v }, this.via); } } @@ -42,7 +44,66 @@ class Bar extends Foo { if (fooLike instanceof Foo) { return fooLike; } - return new Foo(fooLike.v, "Bar"); + return new Foo({ v: fooLike.v }, "Bar"); + } +} + +/** + * A simple entity with no custom `from`. Its constructor accepts the decoded + * shape directly, so `codec()` should be able to auto-generate `from`, + * `encode`, `decode` and `fromBytes` without any extra code. + */ +const PointCodec = mol.struct({ + x: mol.Uint32, + y: mol.Uint32, +}); +type PointLike = EncodableType; + +@codec(PointCodec) +class Point extends Entity.Base() { + public x: number; + public y: number; + + constructor({ x, y }: DecodedType) { + super(); + + this.x = x; + this.y = y; + } +} + +/** + * An entity with a custom `from` that accepts multiple input shapes + * (a bare bigint/number, or the canonical decoded object) and delegates + * the canonical-shape case to `super.from(...)`, which is the + * auto-generated implementation installed on the private `Impl` base + * class by `codec()`. + */ +const MoneyCodec = mol.struct({ + amount: mol.Uint64, +}); +type MoneyLike = EncodableType | bigint | number; + +@codec(MoneyCodec) +class Money extends Entity.Base() { + public amount: bigint; + + constructor({ amount }: DecodedType) { + super(); + + this.amount = amount; + } + + static from(input: MoneyLike): Money { + if (input instanceof Money) { + return input; + } + + if (typeof input === "bigint" || typeof input === "number") { + return super.from({ amount: BigInt(input) }); + } + + return super.from(input); } } @@ -103,3 +164,94 @@ describe("codec decorator static method dispatch", () => { expect(rebound.from({ v: 1 }).via).toBe("Bar"); }); }); + +describe("Entity + codec", () => { + describe("auto-generated from/encode/decode (Point)", () => { + it("exposes the fixed byteLength from the underlying codec", () => { + expect(Point.byteLength).toBe(8); // 4 bytes for x + 4 bytes for y + }); + + it("auto-generates from() to construct instances from the decoded shape", () => { + const p = Point.from({ x: 1, y: 2 }); + expect(p).toBeInstanceOf(Point); + expect(p.x).toBe(1); + expect(p.y).toBe(2); + }); + + it("returns the same reference when from() is called with an existing instance", () => { + const p = Point.from({ x: 1, y: 2 }); + expect(Point.from(p)).toBe(p); + }); + + it("round-trips through encode/decode and fromBytes", () => { + const p = Point.from({ x: 0x11223344, y: 0x55667788 }); + const bytes = Point.encode(p); + expect(bytes.byteLength).toBe(8); + + const decoded = Point.decode(bytes); + expect(decoded.x).toBe(p.x); + expect(decoded.y).toBe(p.y); + + const fromBytes = Point.fromBytes(bytes); + expect(fromBytes.x).toBe(p.x); + expect(fromBytes.y).toBe(p.y); + }); + + it("toBytes/toHex/hash are consistent with each other", () => { + const p = Point.from({ x: 7, y: 9 }); + const bytes = p.toBytes(); + expect(p.toHex()).toBe(hexFrom(bytes)); + expect(p.hash()).toBe(hashCkb(bytes)); + }); + + it("eq() compares by value, not by reference", () => { + const a = Point.from({ x: 3, y: 4 }); + const b = Point.from({ x: 3, y: 4 }); + const c = Point.from({ x: 3, y: 5 }); + + expect(a).not.toBe(b); + expect(a.eq(b)).toBe(true); + expect(a.eq(c)).toBe(false); + // eq() also accepts a Like value, not just an instance + expect(a.eq({ x: 3, y: 4 })).toBe(true); + }); + + it("clone() returns an equal but distinct instance", () => { + const a = Point.from({ x: 3, y: 4 }); + const b = a.clone(); + + expect(b).not.toBe(a); + expect(b).toBeInstanceOf(Point); + expect(b.eq(a)).toBe(true); + }); + }); + + describe("custom from() delegating to super.from() (Money)", () => { + it("normalizes a bigint/number input via super.from()", () => { + const a = Money.from(5n); + const b = Money.from(5); + + expect(a.amount).toBe(5n); + expect(b.amount).toBe(5n); + }); + + it("accepts the canonical decoded shape directly via super.from()", () => { + const m = Money.from({ amount: 42n }); + expect(m.amount).toBe(42n); + }); + + it("returns the same reference for an existing instance", () => { + const m = Money.from(1n); + expect(Money.from(m)).toBe(m); + }); + + it("round-trips through encode/decode using the overridden from()", () => { + const m = Money.from(123456789n); + const bytes = Money.encode(m); + const decoded = Money.decode(bytes); + + expect(decoded).toBeInstanceOf(Money); + expect(decoded.amount).toBe(m.amount); + }); + }); +}); diff --git a/packages/core/src/codec/entity.ts b/packages/core/src/codec/entity.ts index 02ed85738..1757001c3 100644 --- a/packages/core/src/codec/entity.ts +++ b/packages/core/src/codec/entity.ts @@ -148,15 +148,25 @@ export abstract class Entity { * A class decorator to add methods implementation on the {@link Entity.Base} class * @example * ```typescript - * @codec( - * mol.table({ - * codeHash: mol.Byte32, - * hashType: HashTypeCodec, - * args: mol.Bytes, - * }), - * ) + * const ScriptCodec = mol.table({ + * codeHash: mol.Byte32, + * hashType: HashTypeCodec, + * args: mol.Bytes, + * }); + * export type ScriptLike = EncodableType; + * @codec(ScriptCodec) * export class Script extends Entity.Base() { - * from(scriptLike: ScriptLike): Script {} + * public codeHash: Hex; + * public hashType: HashType; + * public args: Hex; + * + * constructor({ codeHash, hashType, args }: DecodedType) { + * super(); + * + * this.codeHash = codeHash; + * this.hashType = hashType; + * this.args = args; + * } * } * ``` */ @@ -168,7 +178,8 @@ export function codec( return function < TypeLike extends Encodable, Type extends TypeLike, - ConstructorType extends Constructor, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + ConstructorType extends Constructor, >( Constructor: ConstructorType & Omit, "from"> & { @@ -183,6 +194,8 @@ export function codec( CodecLike, "from" > & { + // See above, we already know that Decoded extends TypeLike, so we can safely cast the type here. + from: (encodable: TypeLike) => Type; fromBytes: (bytes: BytesLike) => Type; }; @@ -213,5 +226,14 @@ export function codec( return this.from(codec.decode(bytesFrom(bytes), config)); }; } + if (Base.from === undefined) { + Base.from = function (encodable: TypeLike) { + if (encodable instanceof Constructor) { + return encodable; + } + + return new Constructor(codec.from(encodable)); + }; + } }; } From 26dc4bf9bb39bb5bc0415999fe67fd4bb2f70fcf Mon Sep 17 00:00:00 2001 From: Hanssen0 <0@hanssen0.com> Date: Fri, 3 Jul 2026 07:16:10 +0800 Subject: [PATCH 2/2] fix: fix constructor args for default from --- packages/core/src/ckb/epoch.ts | 70 ++-- packages/core/src/ckb/script.ts | 69 ++-- packages/core/src/ckb/transaction.ts | 375 +++++++++--------- .../signer/ckb/signerMultisigCkbReadonly.ts | 60 +-- packages/did-ckb/src/codec.ts | 84 ++-- 5 files changed, 339 insertions(+), 319 deletions(-) diff --git a/packages/core/src/ckb/epoch.ts b/packages/core/src/ckb/epoch.ts index 99b8c0fb4..c790217e7 100644 --- a/packages/core/src/ckb/epoch.ts +++ b/packages/core/src/ckb/epoch.ts @@ -1,5 +1,11 @@ import type { ClientBlockHeader } from "../client/clientTypes.js"; -import { codec, codecPadding, codecUint, Entity } from "../codec/index.js"; +import { + codec, + codecPadding, + codecUint, + DecodedType, + Entity, +} from "../codec/index.js"; import { Zero } from "../fixedPoint/index.js"; import { type Hex, type HexLike } from "../hex/index.js"; import { mol } from "../molecule/index.js"; @@ -36,6 +42,13 @@ export type EpochLike = | Num | Hex; +const EpochCodec = mol.struct({ + padding: codecPadding(1), + denominator: codecUint(2), + numerator: codecUint(2), + integer: codecUint(3), +}); + /** * Epoch * @@ -55,15 +68,12 @@ export type EpochLike = * @remarks * This class is primarily a thin value-object; operations return new Epoch instances. */ -@codec( - mol.struct({ - padding: codecPadding(1), - denominator: codecUint(2), - numerator: codecUint(2), - integer: codecUint(3), - }), -) +@codec(EpochCodec) export class Epoch extends Entity.Base() { + public readonly integer: Num; + public readonly numerator: Num; + public readonly denominator: Num; + /** * Construct a new Epoch instance. * @@ -71,12 +81,16 @@ export class Epoch extends Entity.Base() { * @param numerator - Fractional numerator (Num). * @param denominator - Fractional denominator (Num). */ - public constructor( - public readonly integer: Num, - public readonly numerator: Num, - public readonly denominator: Num, - ) { + public constructor({ + integer, + numerator, + denominator, + }: Omit, "padding">) { super(); + + this.integer = integer; + this.numerator = numerator; + this.denominator = denominator; } /** @@ -229,11 +243,11 @@ export class Epoch extends Entity.Base() { static fromNum(v: NumLike): Epoch { const num = numFrom(v); - return new Epoch( - num & numFrom("0xffffff"), - (num >> numFrom(24)) & numFrom("0xffff"), - (num >> numFrom(40)) & numFrom("0xffff"), - ); + return Epoch.from({ + integer: num & numFrom("0xffffff"), + numerator: (num >> numFrom(24)) & numFrom("0xffff"), + denominator: (num >> numFrom(40)) & numFrom("0xffff"), + }); } /** @@ -250,21 +264,25 @@ export class Epoch extends Entity.Base() { * @param e - Value convertible to Epoch * @returns Epoch instance */ - static override from(e: EpochLike): Epoch { + static from(e: EpochLike): Epoch { if (e instanceof Epoch) { return e; } if (Array.isArray(e)) { - return new Epoch(numFrom(e[0]), numFrom(e[1]), numFrom(e[2])); + return new Epoch({ + integer: numFrom(e[0]), + numerator: numFrom(e[1]), + denominator: numFrom(e[2]), + }); } if (typeof e === "object") { - return new Epoch( - numFrom(e.integer), - numFrom(e.numerator), - numFrom(e.denominator), - ); + return new Epoch({ + integer: numFrom(e.integer), + numerator: numFrom(e.numerator), + denominator: numFrom(e.denominator), + }); } return Epoch.fromNum(e); diff --git a/packages/core/src/ckb/script.ts b/packages/core/src/ckb/script.ts index f18231ed0..1f98e9580 100644 --- a/packages/core/src/ckb/script.ts +++ b/packages/core/src/ckb/script.ts @@ -1,7 +1,13 @@ import { Bytes, BytesLike, bytesFrom } from "../bytes/index.js"; import type { Client } from "../client/index.js"; import { KnownScript } from "../client/knownScript.js"; -import { Codec, Entity, codec } from "../codec/index.js"; +import { + Codec, + DecodedType, + EncodableType, + Entity, + codec, +} from "../codec/index.js"; import { Hex, HexLike, hexFrom } from "../hex/index.js"; import { mol } from "../molecule/index.js"; import { @@ -98,25 +104,24 @@ export function hashTypeFromBytes(bytes: BytesLike): HashType { return NUM_TO_HASH_TYPE[bytesFrom(bytes)[0]]; } +const ScriptCodec = mol.table({ + codeHash: mol.Byte32, + hashType: HashTypeCodec, + args: mol.Bytes, +}); /** * @public */ -export type ScriptLike = { - codeHash: BytesLike; - hashType: HashTypeLike; - args: BytesLike; -}; +export type ScriptLike = EncodableType; /** * @public */ -@codec( - mol.table({ - codeHash: mol.Byte32, - hashType: HashTypeCodec, - args: mol.Bytes, - }), -) +@codec(ScriptCodec) export class Script extends Entity.Base() { + public codeHash: Hex; + public hashType: HashType; + public args: Hex; + /** * Creates an instance of Script. * @@ -124,12 +129,12 @@ export class Script extends Entity.Base() { * @param hashType - The hash type of the script. * @param args - The arguments for the script. */ - constructor( - public codeHash: Hex, - public hashType: HashType, - public args: Hex, - ) { + constructor({ codeHash, hashType, args }: DecodedType) { super(); + + this.codeHash = codeHash; + this.hashType = hashType; + this.args = args; } get occupiedSize(): number { @@ -174,34 +179,6 @@ export class Script extends Entity.Base() { ); } - /** - * Creates a Script instance from a ScriptLike object. - * - * @param script - A ScriptLike object or an instance of Script. - * @returns A Script instance. - * - * @example - * ```typescript - * const script = Script.from({ - * codeHash: "0x1234...", - * hashType: "type", - * args: "0xabcd..." - * }); - * ``` - */ - - static from(script: ScriptLike): Script { - if (script instanceof Script) { - return script; - } - - return new Script( - hexFrom(script.codeHash), - hashTypeFrom(script.hashType), - hexFrom(script.args), - ); - } - /** * Creates a Script instance from client and known script. * diff --git a/packages/core/src/ckb/transaction.ts b/packages/core/src/ckb/transaction.ts index c5b85875f..7f07f0460 100644 --- a/packages/core/src/ckb/transaction.ts +++ b/packages/core/src/ckb/transaction.ts @@ -7,7 +7,13 @@ import { type ClientBlockHeaderLike, } from "../client/index.js"; import { KnownScript } from "../client/knownScript.js"; -import { Codec, Entity, codec } from "../codec/index.js"; +import { + Codec, + DecodedType, + EncodableType, + Entity, + codec, +} from "../codec/index.js"; import { Zero, fixedPointFrom } from "../fixedPoint/index.js"; import { Hasher, HasherCkb, hashCkb } from "../hasher/index.js"; import { Hex, HexLike, hexFrom } from "../hex/index.js"; @@ -120,50 +126,33 @@ export function depTypeFromBytes(bytes: BytesLike): DepType { /** * @public */ -export type OutPointLike = { - txHash: HexLike; - index: NumLike; -}; +const OutPointCodec = mol.struct({ + txHash: mol.Byte32, + index: mol.Uint32, +}); /** * @public */ -@codec( - mol.struct({ - txHash: mol.Byte32, - index: mol.Uint32, - }), -) +export type OutPointLike = EncodableType; +/** + * @public + */ +@codec(OutPointCodec) export class OutPoint extends Entity.Base() { + public txHash: Hex; + public index: Num; + /** * Creates an instance of OutPoint. * * @param txHash - The transaction hash. * @param index - The index of the output in the transaction. */ - - constructor( - public txHash: Hex, - public index: Num, - ) { + constructor({ txHash, index }: DecodedType) { super(); - } - /** - * Creates an OutPoint instance from an OutPointLike object. - * - * @param outPoint - An OutPointLike object or an instance of OutPoint. - * @returns An OutPoint instance. - * - * @example - * ```typescript - * const outPoint = OutPoint.from({ txHash: "0x...", index: 0 }); - * ``` - */ - static from(outPoint: OutPointLike): OutPoint { - if (outPoint instanceof OutPoint) { - return outPoint; - } - return new OutPoint(hexFrom(outPoint.txHash), numFrom(outPoint.index)); + this.txHash = txHash; + this.index = numFrom(index); } /** @@ -200,6 +189,14 @@ export class OutPoint extends Entity.Base() { } } +/** + * @public + */ +const CellOutputCodec = mol.table({ + capacity: mol.Uint64, + lock: Script, + type: ScriptOpt, +}); /** * @public */ @@ -211,14 +208,12 @@ export type CellOutputLike = { /** * @public */ -@codec( - mol.table({ - capacity: mol.Uint64, - lock: Script, - type: ScriptOpt, - }), -) +@codec(CellOutputCodec) export class CellOutput extends Entity.Base() { + public capacity: Num; + public lock: Script; + public type?: Script; + /** * Creates an instance of CellOutput. * @@ -226,13 +221,12 @@ export class CellOutput extends Entity.Base() { * @param lock - The lock script of the cell. * @param type - The optional type script of the cell. */ - - constructor( - public capacity: Num, - public lock: Script, - public type?: Script, - ) { + constructor({ capacity, lock, type }: DecodedType) { super(); + + this.capacity = capacity; + this.lock = lock; + this.type = type; } get occupiedSize(): number { @@ -268,16 +262,13 @@ export class CellOutput extends Entity.Base() { cellOutput: CellOutputLike, outputData?: HexLike | null, ): CellOutput { - const output = (() => { - if (cellOutput instanceof CellOutput) { - return cellOutput; - } - return new CellOutput( - numFrom(cellOutput.capacity ?? 0), - Script.from(cellOutput.lock), - apply(Script.from, cellOutput.type), - ); - })(); + const output = + cellOutput instanceof CellOutput + ? cellOutput + : super.from({ + ...cellOutput, + capacity: cellOutput.capacity ?? 0, + }); if (outputData != null) { output.capacity = numMax( @@ -671,27 +662,33 @@ export type SinceLike = value: NumLike; } | NumLike; + +const SinceCodec = mol.Uint64.map({ + inMap: (encodable: SinceLike) => Since.from(encodable).toNum(), + outMap: (num: Num): Pick => + Since.fromNum(num), +}); + /** * @public */ -@codec( - mol.Uint64.mapIn((encodable: SinceLike) => Since.from(encodable).toNum()), -) +@codec(SinceCodec) export class Since extends Entity.Base() { + public relative: "absolute" | "relative"; + public metric: "blockNumber" | "epoch" | "timestamp"; + public value: Num; + /** * Creates an instance of Since. * - * @param relative - Absolute or relative - * @param metric - The metric of since - * @param value - The value of since + * @param num - The since representation value. */ - - constructor( - public relative: "absolute" | "relative", - public metric: "blockNumber" | "epoch" | "timestamp", - public value: Num, - ) { + constructor({ relative, metric, value }: DecodedType) { super(); + + this.relative = relative; + this.metric = metric; + this.value = value; } /** @@ -729,7 +726,11 @@ export class Since extends Entity.Base() { } if (typeof since === "object" && "relative" in since) { - return new Since(since.relative, since.metric, numFrom(since.value)); + return new Since({ + relative: since.relative, + metric: since.metric, + value: numFrom(since.value), + }); } return Since.fromNum(since); @@ -745,7 +746,6 @@ export class Since extends Entity.Base() { * const num = since.toNum(); * ``` */ - toNum(): Num { return ( this.value | @@ -769,7 +769,6 @@ export class Since extends Entity.Base() { * const since = Since.fromNum("0x0"); * ``` */ - static fromNum(numLike: NumLike): Since { const num = numFrom(numLike); @@ -779,10 +778,17 @@ export class Since extends Entity.Base() { ]; const value = num & numFrom("0x00ffffffffffffff"); - return new Since(relative, metric, value); + return new Since({ relative, metric, value }); } } +/** + * @public + */ +const CellInputCodec = mol.struct({ + since: Since, + previousOutput: OutPoint, +}); /** * @public */ @@ -800,14 +806,14 @@ export type CellInputLike = ( * @public */ @codec( - mol - .struct({ - since: Since, - previousOutput: OutPoint, - }) - .mapIn((encodable: CellInputLike) => CellInput.from(encodable)), + CellInputCodec.mapIn((encodable: CellInputLike) => CellInput.from(encodable)), ) export class CellInput extends Entity.Base() { + public previousOutput: OutPoint; + public since: Num; + public cellOutput?: CellOutput; + public outputData?: Hex; + /** * Creates an instance of CellInput. * @@ -816,14 +822,21 @@ export class CellInput extends Entity.Base() { * @param cellOutput - The optional cell output associated with the cell input. * @param outputData - The optional output data associated with the cell input. */ - - constructor( - public previousOutput: OutPoint, - public since: Num, - public cellOutput?: CellOutput, - public outputData?: Hex, - ) { + constructor({ + previousOutput, + since, + cellOutput, + outputData, + }: DecodedType & { + cellOutput?: CellOutput; + outputData?: Hex; + }) { super(); + + this.previousOutput = previousOutput; + this.since = Since.from(since).toNum(); + this.cellOutput = cellOutput; + this.outputData = outputData; } /** @@ -845,16 +858,16 @@ export class CellInput extends Entity.Base() { return cellInput; } - return new CellInput( - OutPoint.from( + return new CellInput({ + previousOutput: OutPoint.from( "previousOutput" in cellInput ? cellInput.previousOutput : cellInput.outPoint, ), - Since.from(cellInput.since ?? 0).toNum(), - apply(CellOutput.from, cellInput.cellOutput), - apply(hexFrom, cellInput.outputData), - ); + since: Since.from(cellInput.since ?? 0), + cellOutput: apply(CellOutput.from, cellInput.cellOutput), + outputData: apply(hexFrom, cellInput.outputData), + }); } async getCell(client: Client): Promise { @@ -929,58 +942,33 @@ export const CellInputVec = mol.vector(CellInput); /** * @public */ -export type CellDepLike = { - outPoint: OutPointLike; - depType: DepTypeLike; -}; +const CellDepCodec = mol.struct({ + outPoint: OutPoint, + depType: DepTypeCodec, +}); /** * @public */ -@codec( - mol.struct({ - outPoint: OutPoint, - depType: DepTypeCodec, - }), -) +export type CellDepLike = EncodableType; +/** + * @public + */ +@codec(CellDepCodec) export class CellDep extends Entity.Base() { + public outPoint: OutPoint; + public depType: DepType; + /** * Creates an instance of CellDep. * * @param outPoint - The outpoint of the cell dependency. * @param depType - The dependency type. */ - - constructor( - public outPoint: OutPoint, - public depType: DepType, - ) { + constructor({ outPoint, depType }: DecodedType) { super(); - } - - /** - * Creates a CellDep instance from a CellDepLike object. - * - * @param cellDep - A CellDepLike object or an instance of CellDep. - * @returns A CellDep instance. - * - * @example - * ```typescript - * const cellDep = CellDep.from({ - * outPoint: { txHash: "0x...", index: 0 }, - * depType: "depGroup" - * }); - * ``` - */ - static from(cellDep: CellDepLike): CellDep { - if (cellDep instanceof CellDep) { - return cellDep; - } - - return new CellDep( - OutPoint.from(cellDep.outPoint), - depTypeFrom(cellDep.depType), - ); + this.outPoint = outPoint; + this.depType = depType; } /** @@ -1005,22 +993,24 @@ export const CellDepVec = mol.vector(CellDep); /** * @public */ -export type WitnessArgsLike = { - lock?: HexLike | null; - inputType?: HexLike | null; - outputType?: HexLike | null; -}; +const WitnessArgsCodec = mol.table({ + lock: mol.BytesOpt, + inputType: mol.BytesOpt, + outputType: mol.BytesOpt, +}); /** * @public */ -@codec( - mol.table({ - lock: mol.BytesOpt, - inputType: mol.BytesOpt, - outputType: mol.BytesOpt, - }), -) +export type WitnessArgsLike = EncodableType; +/** + * @public + */ +@codec(WitnessArgsCodec) export class WitnessArgs extends Entity.Base() { + public lock?: Hex; + public inputType?: Hex; + public outputType?: Hex; + /** * Creates an instance of WitnessArgs. * @@ -1028,41 +1018,16 @@ export class WitnessArgs extends Entity.Base() { * @param inputType - The optional input type field of the witness. * @param outputType - The optional output type field of the witness. */ - - constructor( - public lock?: Hex, - public inputType?: Hex, - public outputType?: Hex, - ) { + constructor({ + lock, + inputType, + outputType, + }: DecodedType) { super(); - } - /** - * Creates a WitnessArgs instance from a WitnessArgsLike object. - * - * @param witnessArgs - A WitnessArgsLike object or an instance of WitnessArgs. - * @returns A WitnessArgs instance. - * - * @example - * ```typescript - * const witnessArgs = WitnessArgs.from({ - * lock: "0x...", - * inputType: "0x...", - * outputType: "0x..." - * }); - * ``` - */ - - static from(witnessArgs: WitnessArgsLike): WitnessArgs { - if (witnessArgs instanceof WitnessArgs) { - return witnessArgs; - } - - return new WitnessArgs( - apply(hexFrom, witnessArgs.lock), - apply(hexFrom, witnessArgs.inputType), - apply(hexFrom, witnessArgs.outputType), - ); + this.lock = lock; + this.inputType = inputType; + this.outputType = outputType; } } @@ -1119,6 +1084,14 @@ export type TransactionLike = { .mapOut((tx) => Transaction.from({ ...tx.raw, witnesses: tx.witnesses })), ) export class Transaction extends Entity.Base() { + public version: Num; + public cellDeps: CellDep[]; + public headerDeps: Hex[]; + public inputs: CellInput[]; + public outputs: CellOutput[]; + public outputsData: Hex[]; + public witnesses: Hex[]; + /** * Creates an instance of Transaction. * @@ -1130,17 +1103,32 @@ export class Transaction extends Entity.Base() { * @param outputsData - The data associated with the outputs. * @param witnesses - The witnesses of the transaction. */ - - constructor( - public version: Num, - public cellDeps: CellDep[], - public headerDeps: Hex[], - public inputs: CellInput[], - public outputs: CellOutput[], - public outputsData: Hex[], - public witnesses: Hex[], - ) { + constructor({ + version, + cellDeps, + headerDeps, + inputs, + outputs, + outputsData, + witnesses, + }: { + version: Num; + cellDeps: CellDep[]; + headerDeps: Hex[]; + inputs: CellInput[]; + outputs: CellOutput[]; + outputsData: Hex[]; + witnesses: Hex[]; + }) { super(); + + this.version = version; + this.cellDeps = cellDeps; + this.headerDeps = headerDeps; + this.inputs = inputs; + this.outputs = outputs; + this.outputsData = outputsData; + this.witnesses = witnesses; } /** @@ -1247,7 +1235,6 @@ export class Transaction extends Entity.Base() { * }); * ``` */ - static from(tx: TransactionLike): Transaction { if (tx instanceof Transaction) { return tx; @@ -1265,15 +1252,15 @@ export class Transaction extends Entity.Base() { ); } - return new Transaction( - numFrom(tx.version ?? 0), - tx.cellDeps?.map((cellDep) => CellDep.from(cellDep)) ?? [], - tx.headerDeps?.map(hexFrom) ?? [], - tx.inputs?.map((input) => CellInput.from(input)) ?? [], + return new Transaction({ + version: numFrom(tx.version ?? 0), + cellDeps: tx.cellDeps?.map((cellDep) => CellDep.from(cellDep)) ?? [], + headerDeps: tx.headerDeps?.map(hexFrom) ?? [], + inputs: tx.inputs?.map((input) => CellInput.from(input)) ?? [], outputs, outputsData, - tx.witnesses?.map(hexFrom) ?? [], - ); + witnesses: tx.witnesses?.map(hexFrom) ?? [], + }); } /** diff --git a/packages/core/src/signer/ckb/signerMultisigCkbReadonly.ts b/packages/core/src/signer/ckb/signerMultisigCkbReadonly.ts index d64009960..11a0011f4 100644 --- a/packages/core/src/signer/ckb/signerMultisigCkbReadonly.ts +++ b/packages/core/src/signer/ckb/signerMultisigCkbReadonly.ts @@ -18,7 +18,7 @@ import { ScriptInfo, ScriptInfoLike, } from "../../client/index.js"; -import { codec, Entity } from "../../codec/index.js"; +import { Codec, codec, DecodedType, Entity } from "../../codec/index.js"; import { HASH_CKB_SHORT_LENGTH, hashCkbShort } from "../../hasher/index.js"; import { Hex, hexFrom, HexLike } from "../../hex/index.js"; import { numFrom, NumLike, numToBytes } from "../../num/index.js"; @@ -44,12 +44,8 @@ export type MultisigCkbWitnessLike = ( signatures?: HexLike[] | null; }; -/** - * A class representing multisig information, holding information ingredients and containing utilities. - * @public - */ -@codec({ - encode: (encodable: MultisigCkbWitness) => { +const MultisigCkbWitnessCodec = Codec.from({ + encode: (encodable: MultisigCkbWitnessLike) => { const { publicKeyHashes, threshold, mustMatch, signatures } = MultisigCkbWitness.from(encodable); @@ -97,7 +93,7 @@ export type MultisigCkbWitnessLike = ( publicKeyHashesLength * HASH_CKB_SHORT_LENGTH, ); - return MultisigCkbWitness.from({ + return { publicKeyHashes: Array.from(new Array(publicKeyHashesLength), (_, i) => hexFrom( rawKeyAndSignatures.slice( @@ -106,8 +102,8 @@ export type MultisigCkbWitnessLike = ( ), ), ), - threshold: numFrom(threshold), - mustMatch: numFrom(mustMatch), + threshold: threshold, + mustMatch: mustMatch, signatures: Array.from( new Array(Math.floor(signatures.length / SECP256K1_SIGNATURE_LENGTH)), (_, i) => @@ -118,25 +114,36 @@ export type MultisigCkbWitnessLike = ( ), ), ), - }); + }; }, -}) +}); + +/** + * A class representing multisig information, holding information ingredients and containing utilities. + * @public + */ +@codec(MultisigCkbWitnessCodec) export class MultisigCkbWitness extends Entity.Base< MultisigCkbWitnessLike, MultisigCkbWitness >() { + public publicKeyHashes: Hex[]; + public threshold: number; + public mustMatch: number; + public signatures: Hex[]; + /** * @param publicKeyHashes - The public key hashes. * @param threshold - The threshold. * @param mustMatch - The number of signatures that must match. * @param signatures - The signatures. */ - constructor( - public publicKeyHashes: Hex[], - public threshold: number, - public mustMatch: number, - public signatures: Hex[], - ) { + constructor({ + publicKeyHashes, + threshold, + mustMatch, + signatures, + }: DecodedType) { super(); const keysLength = publicKeyHashes.length; @@ -154,6 +161,11 @@ export class MultisigCkbWitness extends Entity.Base< if (keysLength > 255) { throw new Error("public keys length should be less than 256"); } + + this.publicKeyHashes = publicKeyHashes; + this.threshold = threshold; + this.mustMatch = mustMatch; + this.signatures = signatures; } /** @@ -170,12 +182,12 @@ export class MultisigCkbWitness extends Entity.Base< return witness.publicKeys.map((k) => hashCkbShort(k)); })(); - return new MultisigCkbWitness( - publicKeyHashes.map(hexFrom), - Number(numFrom(witness.threshold)), - Number(numFrom(witness.mustMatch ?? 0)), - witness.signatures?.map(hexFrom) ?? [], - ); + return new MultisigCkbWitness({ + publicKeyHashes: publicKeyHashes.map(hexFrom), + threshold: Number(numFrom(witness.threshold)), + mustMatch: Number(numFrom(witness.mustMatch ?? 0)), + signatures: witness.signatures?.map(hexFrom) ?? [], + }); } /** diff --git a/packages/did-ckb/src/codec.ts b/packages/did-ckb/src/codec.ts index c89ba8e57..48f2b6a33 100644 --- a/packages/did-ckb/src/codec.ts +++ b/packages/did-ckb/src/codec.ts @@ -26,11 +26,13 @@ export class DidCkbDataV1 extends ccc.Entity.Base< DidCkbDataV1Like, DidCkbDataV1 >() { - constructor( - public document: unknown, - public localId?: string, - ) { + public document: unknown; + public localId?: string; + + constructor({ document, localId }: { document: unknown; localId?: string }) { super(); + this.document = document; + this.localId = localId ?? undefined; } static from(data: DidCkbDataV1Like): DidCkbDataV1 { @@ -38,7 +40,10 @@ export class DidCkbDataV1 extends ccc.Entity.Base< return data; } - return new DidCkbDataV1(data.document, data.localId ?? undefined); + return new DidCkbDataV1({ + document: data.document, + localId: data.localId ?? undefined, + }); } } @@ -52,24 +57,32 @@ export type DidCkbDataLike = { }), ) export class DidCkbData extends ccc.Entity.Base() { - constructor( - public type: "v1", - public value: DidCkbDataV1, - ) { + public type: "v1"; + public value: DidCkbDataV1; + + constructor({ type, value }: { type: "v1"; value: DidCkbDataV1 }) { super(); + this.type = type; + this.value = value; } static from(data: DidCkbDataLike): DidCkbData { if (data instanceof DidCkbData) { return data; } - return new DidCkbData(data.type ?? "v1", DidCkbDataV1.from(data.value)); + return new DidCkbData({ + type: data.type ?? "v1", + value: DidCkbDataV1.from(data.value), + }); } static fromV1( data: DidCkbDataV1Like, ): DidCkbData & { type: "v1"; value: DidCkbDataV1 } { - return new DidCkbData("v1", DidCkbDataV1.from(data)); + return new DidCkbData({ + type: "v1", + value: DidCkbDataV1.from(data), + }); } } @@ -101,23 +114,36 @@ export class PlcAuthorization extends ccc.Entity.Base< PlcAuthorizationLike, PlcAuthorization >() { - constructor( - public history: object[], - public sig: ccc.Hex, - public rotationKeyIndices: ccc.Num[], - ) { + public history: object[]; + public sig: ccc.Hex; + public rotationKeyIndices: ccc.Num[]; + + constructor({ + history, + sig, + rotationKeyIndices, + }: { + history: object[]; + sig: ccc.Hex; + rotationKeyIndices: number[]; + }) { super(); + this.history = history; + this.sig = sig; + this.rotationKeyIndices = rotationKeyIndices.map(ccc.numFrom); } static from(data: PlcAuthorizationLike): PlcAuthorization { if (data instanceof PlcAuthorization) { return data; } - return new PlcAuthorization( - data.history, - ccc.hexFrom(data.sig), - data.rotationKeyIndices.map(ccc.numFrom), - ); + return new PlcAuthorization({ + history: data.history, + sig: ccc.hexFrom(data.sig), + rotationKeyIndices: data.rotationKeyIndices.map((v) => + Number(ccc.numFrom(v)), + ), + }); } } @@ -133,14 +159,14 @@ export class DidCkbWitness extends ccc.Entity.Base< DidCkbWitnessLike, DidCkbWitness >() { - constructor(public localIdAuthorization: PlcAuthorization) { - super(); - } + public localIdAuthorization: PlcAuthorization; - static from(data: DidCkbWitnessLike): DidCkbWitness { - if (data instanceof DidCkbWitness) { - return data; - } - return new DidCkbWitness(PlcAuthorization.from(data.localIdAuthorization)); + constructor({ + localIdAuthorization, + }: { + localIdAuthorization: PlcAuthorization; + }) { + super(); + this.localIdAuthorization = localIdAuthorization; } }