UNPKG

@layerzerolabs/lz-sui-sdk-v2

Version:

361 lines (309 loc) 13.7 kB
import { createHash } from 'crypto' import { SuiClient } from '@mysten/sui/client' import { BlockedMessageLib, BlockedMessageLibPtbBuilder, Call, Counter, DVN, DVNFeeLib, DvnLayerZero, DvnPtbBuilder, Endpoint, EndpointPtbBuilder, Executor, ExecutorFeeLib, ExecutorLayerZero, ExecutorPtbBuilder, LayerZeroViews, OApp, PackageWhitelistValidator, PriceFeed, PtbBuilder, SimpleMessageLib, SimpleMessageLibPtbBuilder, Treasury, Uln302, Uln302PtbBuilder, Utils, WorkerRegistry, Zro, } from './modules' import { ModuleOptions, ObjectOptions, PackageOptions } from './types' import { Modules } from './types/modules' /** * Module Manager - Centralized management for all SDK modules * Uses unified storage for both core modules and cached modules with options */ export class ModuleManager { public packages: PackageOptions public objects: ObjectOptions private storage = new Map<string, unknown>() constructor(packages: PackageOptions, objects: ObjectOptions) { this.packages = packages this.objects = objects } /** * Generate a cache key using hash for modules with options */ private generateCacheKey(moduleName: string, options?: ModuleOptions): string { if (!options) { return `${moduleName}:default` } // Create hash from JSON string const jsonStr = JSON.stringify(options, Object.keys(options).sort()) const hash = createHash('sha256').update(jsonStr).digest('hex').substring(0, 16) return `${moduleName}:${hash}` } private setCoreModules(modules: { [key: string]: unknown }): void { Object.entries(modules).forEach(([name, instance]) => { this.setModule(name, undefined, instance) }) } /** * Get module instance (with options), throws error if not found */ getModule<T>(moduleName: string, options?: ModuleOptions): T { const key = this.generateCacheKey(moduleName, options) const module = this.storage.get(key) as T | undefined if (module === undefined) { const optionsStr = options ? ` with options ${JSON.stringify(options)}` : '' throw new Error(`Module '${moduleName}'${optionsStr} not found`) } return module } /** * Store module instance (with options) */ setModule<T>(moduleName: string, options: ModuleOptions | undefined, instance: T): void { const key = this.generateCacheKey(moduleName, options) this.storage.set(key, instance) } /** * Check if module (with options) exists */ hasModule(moduleName: string, options?: ModuleOptions): boolean { const key = this.generateCacheKey(moduleName, options) return this.storage.has(key) } /** * Get module instance or create new one using factory function */ getOrCreateModule<T>(moduleName: string, options: ModuleOptions | undefined, factory: () => T): T { // Check if module exists first if (this.hasModule(moduleName, options)) { return this.getModule<T>(moduleName, options) } // Create new instance and store it const instance = factory() this.setModule(moduleName, options, instance) return instance } /** * Get all core modules as an object */ getAllCoreModules(): { [key: string]: unknown } { const result: { [key: string]: unknown } = {} const coreModuleKeys = Array.from(this.storage.keys()).filter((key) => key.endsWith(':default')) for (const key of coreModuleKeys) { const moduleName = key.replace(':default', '') result[moduleName] = this.storage.get(key) } return result } /** * Clear all modules and cache */ clear(): void { this.storage.clear() } /** * Remove a module with specific options */ removeModule(moduleName: string, options?: ModuleOptions): boolean { const key = this.generateCacheKey(moduleName, options) return this.storage.delete(key) } /** * Initialize all standard LayerZero modules * @param packages - Package addresses configuration * @param objects - Object addresses configuration * @param client - Sui client instance * @param context - Context object to be populated with module references */ initializeCoreModules(packages: PackageOptions, objects: ObjectOptions, client: SuiClient): void { this.setCoreModules({ [Modules.Endpoint]: new Endpoint(packages.endpointV2, client, objects, this), [Modules.SimpleMessageLib]: new SimpleMessageLib(packages.simpleMessageLib, client, objects, this), [Modules.BlockedMessageLib]: new BlockedMessageLib(packages.blockedMessageLib, client, objects, this), [Modules.Uln302]: new Uln302(packages.uln302, client, objects, this), [Modules.Utils]: new Utils(packages.utils, client), [Modules.Zro]: new Zro(packages.zro, client, this), [Modules.Call]: new Call(packages.call, client), [Modules.Treasury]: new Treasury(packages.treasury, client, objects, this), [Modules.LayerZeroViews]: new LayerZeroViews(packages.layerzeroViews, client, objects, this), [Modules.PtbBuilder]: new PtbBuilder(packages.ptbMoveCall, client), }) } // === Core Module Getters === getEndpoint(): Endpoint { return this.getModule(Modules.Endpoint) } getSimpleMessageLib(): SimpleMessageLib { return this.getModule(Modules.SimpleMessageLib) } getBlockedMessageLib(): BlockedMessageLib { return this.getModule(Modules.BlockedMessageLib) } getUln302(): Uln302 { return this.getModule(Modules.Uln302) } getUtils(): Utils { return this.getModule(Modules.Utils) } getZro(): Zro { return this.getModule(Modules.Zro) } getCall(): Call { return this.getModule(Modules.Call) } getTreasury(): Treasury { return this.getModule(Modules.Treasury) } getLayerZeroViews(): LayerZeroViews { return this.getModule(Modules.LayerZeroViews) } getPtbBuilder(): PtbBuilder { return this.getModule(Modules.PtbBuilder) } getOApp(client: SuiClient, callCapId: string, options?: ModuleOptions): OApp { return this.getOrCreateModule(Modules.Oapp + callCapId, options, () => { const packageId = options?.packageId ?? this.packages.oapp const objects = this.mergeObjectsOptions(options) return new OApp(packageId, callCapId, client, objects, this) }) } // === Non-core Module Getters (created on-demand with caching) === getCounter(client: SuiClient, options?: ModuleOptions): Counter { return this.getOrCreateModule(Modules.Counter, options, () => { const packageId = options?.packageId ?? this.packages.counterV2 const objects = this.mergeObjectsOptions(options) return new Counter(packageId, client, objects, this) }) } getExecutor(client: SuiClient, options?: ModuleOptions): Executor { return this.getOrCreateModule(Modules.Executor, options, () => { const packageId = options?.packageId ?? this.packages.executor const objects = this.mergeObjectsOptions(options) return new Executor(packageId, client, objects, this) }) } getDvn(client: SuiClient, options?: ModuleOptions): DVN { return this.getOrCreateModule(Modules.Dvn, options, () => { const packageId = options?.packageId ?? this.packages.dvn const objects = this.mergeObjectsOptions(options) return new DVN(packageId, client, objects, this) }) } getDvnFeeLib(client: SuiClient, options?: ModuleOptions): DVNFeeLib { return this.getOrCreateModule(Modules.DvnFeeLib, options, () => { const packageId = options?.packageId ?? this.packages.dvnFeeLib const objects = this.mergeObjectsOptions(options) return new DVNFeeLib(packageId, client, objects, this) }) } getExecutorFeeLib(client: SuiClient, options?: ModuleOptions): ExecutorFeeLib { return this.getOrCreateModule(Modules.ExecutorFeeLib, options, () => { const packageId = options?.packageId ?? this.packages.executorFeeLib const objects = this.mergeObjectsOptions(options) return new ExecutorFeeLib(packageId, client, objects, this) }) } getPriceFeed(client: SuiClient, options?: ModuleOptions): PriceFeed { return this.getOrCreateModule(Modules.PriceFeed, options, () => { const packageId = options?.packageId ?? this.packages.priceFeed const objects = this.mergeObjectsOptions(options) return new PriceFeed(packageId, client, objects, this) }) } getDvnLayerZero(client: SuiClient, options?: ModuleOptions): DvnLayerZero { return this.getOrCreateModule(Modules.DvnLayerZero, options, () => { const packageId = options?.packageId ?? this.packages.dvnLayerzero return new DvnLayerZero(packageId, client, this) }) } getExecutorLayerZero(client: SuiClient, options?: ModuleOptions): ExecutorLayerZero { return this.getOrCreateModule(Modules.ExecutorLayerZero, options, () => { const packageId = options?.packageId ?? this.packages.executorLayerzero return new ExecutorLayerZero(packageId, client, this) }) } getDvnPtbBuilder(client: SuiClient, options?: ModuleOptions): DvnPtbBuilder { return this.getOrCreateModule(Modules.DvnPtbBuilder, options, () => { const packageId = options?.packageId ?? this.packages.dvnPtbBuilder const objects = this.mergeObjectsOptions(options) return new DvnPtbBuilder(packageId, client, objects, this) }) } getExecutorPtbBuilder(client: SuiClient, options?: ModuleOptions): ExecutorPtbBuilder { return this.getOrCreateModule(Modules.ExecutorPtbBuilder, options, () => { const packageId = options?.packageId ?? this.packages.executorPtbBuilder const objects = this.mergeObjectsOptions(options) return new ExecutorPtbBuilder(packageId, client, objects, this) }) } getPackageWhitelistValidator(client: SuiClient, options?: ModuleOptions): PackageWhitelistValidator { return this.getOrCreateModule(Modules.PackageWhitelistValidator, options, () => { const packageId = options?.packageId ?? this.packages.packageWhitelistValidator const objects = this.mergeObjectsOptions(options) return new PackageWhitelistValidator(packageId, client, objects, this) }) } getUln302PtbBuilder(client: SuiClient, options?: ModuleOptions): Uln302PtbBuilder { return this.getOrCreateModule(Modules.Uln302PtbBuilder, options, () => { const packageId = options?.packageId ?? this.packages.uln302PtbBuilder const objects = this.mergeObjectsOptions(options) return new Uln302PtbBuilder(packageId, client, objects, this) }) } getEndpointPtbBuilder(client: SuiClient, options?: ModuleOptions): EndpointPtbBuilder { return this.getOrCreateModule(Modules.EndpointPtbBuilder, options, () => { const packageId = options?.packageId ?? this.packages.endpointPtbBuilder const objects = this.mergeObjectsOptions(options) return new EndpointPtbBuilder(packageId, client, objects, this) }) } getSimpleMessageLibPtbBuilder(client: SuiClient, options?: ModuleOptions): SimpleMessageLibPtbBuilder { return this.getOrCreateModule(Modules.SimpleMessageLibPtbBuilder, options, () => { const packageId = options?.packageId ?? this.packages.simpleMessageLibPtbBuilder const objects = this.mergeObjectsOptions(options) return new SimpleMessageLibPtbBuilder(packageId, client, objects, this) }) } getBlockedMessageLibPtbBuilder(client: SuiClient, options?: ModuleOptions): BlockedMessageLibPtbBuilder { return this.getOrCreateModule(Modules.BlockedMessageLibPtbBuilder, options, () => { const packageId = options?.packageId ?? this.packages.blockedMessageLibPtbBuilder const objects = this.mergeObjectsOptions(options) return new BlockedMessageLibPtbBuilder(packageId, client, objects, this) }) } getWorkerRegistry(client: SuiClient, options?: ModuleOptions): WorkerRegistry { return this.getOrCreateModule(Modules.WorkerRegistry, options, () => { const packageId = options?.packageId ?? this.packages.workerRegistry const objects = this.mergeObjectsOptions(options) return new WorkerRegistry(packageId, client, objects, this) }) } // === Private Utility Methods === /** * Merge objects configuration with options, avoiding unnecessary object spreading */ private mergeObjectsOptions(options?: ModuleOptions): ObjectOptions { // If no options or options.objects is empty, return original objects if (!options?.objects || Object.keys(options.objects).length === 0) { return this.objects } // Only perform object spreading when there are actual properties to merge return { ...this.objects, ...options.objects } } }