@layerzerolabs/lz-sui-sdk-v2
Version:
361 lines (309 loc) • 13.7 kB
text/typescript
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 }
}
}