diff --git a/packages/examples/src/registerUdtMetadata.ts b/packages/examples/src/registerUdtMetadata.ts new file mode 100644 index 000000000..4332464fe --- /dev/null +++ b/packages/examples/src/registerUdtMetadata.ts @@ -0,0 +1,47 @@ +import { ccc } from "@ckb-ccc/ccc"; +import { render, signer } from "@ckb-ccc/playground"; + +// Prepare the UDT trait +const type = ccc.Script.from({ + codeHash: + "0x8b887e59f396f99302996ee8911b31f73fb2e2be4d9cade3104f017a871b8ed3", + hashType: "type", + // Equal to the TypeId args of metadata cell, UDT would use it to search metadata cell + // for extracting the metadata. It can be empty if no metadata cell deployed on the chain. + args: "0x1e370b8965e12faf572a0f7ca7bf585027404ee23c32a82ef049965d5ebb8ff6", +}); + +const code = ccc.OutPoint.from({ + // SSRI-UDT script deployment tx hash on Testnet + txHash: "0x1fecfac56696b38d76304f9e2dc1db39406679f3a6e517d5ed16bddbd8fdd7ab", + index: 0, +}); + +const executor = new ccc.ssri.ExecutorJsonRpc("http://localhost:9090"); // Linking to your native SSRI-Server +const udt = new ccc.udt.UdtRegister(code, type, { + executor, +}); + +// Register the UDT with metadata +const { tx: registerTx, tokenHash: metadataTypeIdArgs } = await udt.register( + signer, + { + name: "SSRI UDT", + symbol: "WSS", + decimals: 8, + icon: "", + }, +); + +// Get the metadata TypeId args, then you can use it to mint UDT tokens +console.log("metadataTypeIdArgs =", metadataTypeIdArgs); + +const tx = registerTx.res; +await render(tx); + +// Complete missing parts: Pay fee +await tx.completeFeeBy(signer); +await render(tx); + +const txHash = await signer.sendTransaction(tx); +console.log("tx hash =", txHash); diff --git a/packages/udt/src/barrel.ts b/packages/udt/src/barrel.ts index 90fb85aed..a65afa649 100644 --- a/packages/udt/src/barrel.ts +++ b/packages/udt/src/barrel.ts @@ -1,2 +1,3 @@ export * from "./udt/index.js"; export * from "./udtPausable/index.js"; +export * from "./udtRegister/index.js"; diff --git a/packages/udt/src/udtRegister/index.ts b/packages/udt/src/udtRegister/index.ts new file mode 100644 index 000000000..d72ca1a8c --- /dev/null +++ b/packages/udt/src/udtRegister/index.ts @@ -0,0 +1,130 @@ +import { ccc } from "@ckb-ccc/core"; +import { ssri } from "@ckb-ccc/ssri"; +import { Udt, UdtConfigLike } from "../udt/index.js"; + +/** + * The basic metadata of a UDT token. + * + * @example + * ```typescript + * const metadataMolecule = UdtMetadata.encode({ + * name: "My UDT", + * symbol: "MYUDT", + * decimals: 8, + * icon: "https://example.com/icon.png", + * }); + * ``` + * + * @public + * @category UDT + */ +export const UdtMetadata = ccc.mol.table({ + name: ccc.mol.String, + symbol: ccc.mol.String, + decimals: ccc.mol.Uint8, + icon: ccc.mol.String, +}); + +/** + * Represents a UDT (User Defined Token) with separated SSRI metadata functionality. + * @extends {Udt} This must be a SSRI UDT that does not fallback to xUDT. + * @public + */ +export class UdtRegister extends Udt { + constructor( + code: ccc.OutPointLike, + script: ccc.ScriptLike, + config: UdtConfigLike & { executor: ssri.Executor }, + ) { + super(code, script, config); + } + + /** + * Registers (creates) a new UDT with on-chain metadata. + * This method creates a new unique UDT instance (usually with a TypeId pattern), + * assigns on-chain metadata (name, symbol, decimals, icon), and returns a transaction + * to instantiate the new token. Often used by the deployer/owner. + * + * @param signer - The signer (owner) who will initialize and own the new UDT + * @param metadata - Object containing UDT metadata (name, symbol, decimals, icon) + * @param tx - Optional existing transaction to build upon + * @returns Promise resolving to `{ tx, tokenHash }`, where `tx` is the deployment transaction, + * and `tokenHash` is the computed TypeId/Token hash of the new UDT + * + * @example + * ```typescript + * const udt = new Udt(codeOutPoint, scriptConfig); + * const { tx, tokenHash } = await udt.register( + * signer, + * { name: "My UDT", symbol: "MYT", decimals: 8, icon: "https://..." } + * ); + * // Send tx.res or complete tx.res and send the transaction + * ``` + * + * @remarks + * - Uses SSRI executor if available for advanced/SSRI-compliant registration. + * - Falls back to legacy registration (TypeId pattern) if no executor is present. + * - The token hash can be used as the args for the UDT type script. + */ + async register( + signer: ccc.Signer, + metadata: { + name: string; + symbol: string; + decimals: number; + icon: string; + }, + tx?: ccc.TransactionLike | null, + ): Promise<{ + tx: ssri.ExecutorResponse; + tokenHash: ccc.Hex; + }> { + const owner = await signer.getRecommendedAddressObj(); + const register = ccc.Transaction.from(tx ?? {}); + if (register.inputs.length === 0) { + await register.completeInputsAtLeastOne(signer); // For `TypeId` calcuclation + } + const tokenHash = ccc.hashTypeId( + register.inputs[0], + register.outputs.length, + ); + + let resTx; + if (this.executor) { + const res = await this.executor.runScriptTry( + this.code, + "SSRIUDT.create", + [ + register.toBytes(), + ccc.Script.from(owner.script).toBytes(), + UdtMetadata.encode(metadata), + ], + ); + if (res) { + resTx = res.map((res) => ccc.Transaction.fromBytes(res)); + } + } + + // Fallback logic + if (!resTx) { + register.addOutput( + { + lock: owner.script, + type: { + codeHash: + "00000000000000000000000000000000000000000000000000545950455f4944", + hashType: "type", + args: tokenHash, + }, + }, + UdtMetadata.encode(metadata), + ); + resTx = ssri.ExecutorResponse.new(register); + } + + return { + tx: resTx.map((tx) => this.addCellDeps(tx)), + tokenHash, + }; + } +}