diff --git a/.changeset/legal-laws-attend.md b/.changeset/legal-laws-attend.md new file mode 100644 index 00000000..2a51a816 --- /dev/null +++ b/.changeset/legal-laws-attend.md @@ -0,0 +1,6 @@ +--- +"@ckb-ccc/core": minor +--- + +feat(core): `SignerCkbAlwaysSuccess` + \ No newline at end of file diff --git a/packages/core/src/signer/ckb/index.ts b/packages/core/src/signer/ckb/index.ts index 5a9dc928..e22307bd 100644 --- a/packages/core/src/signer/ckb/index.ts +++ b/packages/core/src/signer/ckb/index.ts @@ -1,4 +1,5 @@ export * from "./secp256k1Signing.js"; +export * from "./signerCkbAlwaysSuccess.js"; export * from "./signerCkbPrivateKey.js"; export * from "./signerCkbPublicKey.js"; export * from "./signerCkbScriptReadonly.js"; diff --git a/packages/core/src/signer/ckb/signerCkbAlwaysSuccess.test.ts b/packages/core/src/signer/ckb/signerCkbAlwaysSuccess.test.ts new file mode 100644 index 00000000..8d40487f --- /dev/null +++ b/packages/core/src/signer/ckb/signerCkbAlwaysSuccess.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, it } from "vitest"; +import { ccc } from "../../index.js"; + +describe("SignerCkbAlwaysSuccess", () => { + const scriptInfo = ccc.ScriptInfo.from({ + codeHash: + "0x0000000000000000000000000000000000000000000000000000000000000000", + hashType: "data1", + cellDeps: [ + { + cellDep: { + outPoint: { + txHash: + "0x0000000000000000000000000000000000000000000000000000000000000000", + index: 0, + }, + depType: "code", + }, + }, + ], + }); + const client = { + addressPrefix: "ckt", + getKnownScript: async () => scriptInfo, + getCellDeps: async () => scriptInfo.cellDeps.map(({ cellDep }) => cellDep), + } as unknown as ccc.Client; + const signer = new ccc.SignerCkbAlwaysSuccess(client); + + it("uses the always-success script with empty args", async () => { + const addresses = await signer.getAddressObjs(); + + expect(addresses).toHaveLength(1); + expect(signer.type).toBe(ccc.SignerType.CKB); + expect(signer.signType).toBe(ccc.SignerSignType.Unknown); + expect(await signer.isConnected()).toBe(true); + expect(addresses[0].script).toEqual( + ccc.Script.from({ + codeHash: scriptInfo.codeHash, + hashType: scriptInfo.hashType, + args: "0x", + }), + ); + }); + + it("adds the always-success cell dep while signing", async () => { + const tx = ccc.Transaction.default(); + + const signed = await signer.signTransaction(tx); + + expect(signed.cellDeps).toHaveLength(1); + expect(signed.cellDeps[0].eq(scriptInfo.cellDeps[0].cellDep)).toBe(true); + + const signedAgain = await signer.signTransaction(signed); + expect(signedAgain.cellDeps).toHaveLength(1); + }); +}); diff --git a/packages/core/src/signer/ckb/signerCkbAlwaysSuccess.ts b/packages/core/src/signer/ckb/signerCkbAlwaysSuccess.ts new file mode 100644 index 00000000..aacae4a7 --- /dev/null +++ b/packages/core/src/signer/ckb/signerCkbAlwaysSuccess.ts @@ -0,0 +1,71 @@ +import { Address } from "../../address/index.js"; +import { Transaction, TransactionLike } from "../../ckb/index.js"; +import { Client, KnownScript } from "../../client/index.js"; +import { Signer, SignerSignType, SignerType } from "../signer/index.js"; + +/** + * A signer for the well-known always-success lock script. + * + * The always-success script does not require a signature, so signing a + * transaction only needs to add its cell dependency. + * + * @public + */ +export class SignerCkbAlwaysSuccess extends Signer { + get type(): SignerType { + return SignerType.CKB; + } + + get signType(): SignerSignType { + return SignerSignType.Unknown; + } + + /** + * Creates an instance of SignerCkbAlwaysSuccess. + * + * @param client - The client instance used for communication and resolving + * the always-success deployment. + */ + constructor(client: Client) { + super(client); + } + + async connect(): Promise {} + + async isConnected(): Promise { + return true; + } + + async getInternalAddress(): Promise { + return this.getRecommendedAddress(); + } + + /** + * Gets the address of the always-success lock script. + */ + async getAddressObjs(): Promise { + return [ + await Address.fromKnownScript( + this.client, + KnownScript.AlwaysSuccess, + "0x", + ), + ]; + } + + /** + * Adds the always-success cell dependency to a transaction. + */ + async prepareTransaction(txLike: TransactionLike): Promise { + const tx = Transaction.from(txLike); + await tx.addCellDepsOfKnownScripts(this.client, KnownScript.AlwaysSuccess); + return tx; + } + + /** + * The always-success script requires no signature. + */ + async signOnlyTransaction(txLike: TransactionLike): Promise { + return Transaction.from(txLike); + } +}