@nuwa-ai/identity-kit
Version:
SDK for NIP-1 Agent Single DID Multi-Key Model and NIP-3 CADOP (Custodian-Assisted DID Onboarding Protocol)
1 lines • 350 kB
Source Map (JSON)
{"version":3,"sources":["../../src/testHelpers/index.ts","../../src/testHelpers/env.ts","../../src/utils/DebugLogger.ts","../../src/cache/InMemoryLRUDIDDocumentCache.ts","../../src/errors/IdentityKitError.ts","../../src/vdr/VDRRegistry.ts","../../src/vdr/roochVDR.ts","../../src/multibase/base.ts","../../src/utils/bytes.ts","../../src/types/crypto.ts","../../src/multibase/key.ts","../../src/multibase/did.ts","../../src/crypto/providers/ed25519.ts","../../src/crypto/providers/secp256k1.ts","../../src/crypto/providers/ecdsa_r1.ts","../../src/crypto/factory.ts","../../src/crypto/utils.ts","../../src/crypto/EcdsaR1PublicKey.ts","../../src/signers/keyStoreUtils.ts","../../src/signers/types.ts","../../src/signers/didAccountSigner.ts","../../src/utils/did.ts","../../src/vdr/abstractVDR.ts","../../src/vdr/roochVDRTypes.ts","../../src/utils/sessionScopes.ts","../../src/testHelpers/roochLocalNode.ts","../../src/testHelpers/rooch.ts","../../src/testHelpers/didFactory.ts","../../src/keys/KeyStore.ts","../../src/keys/StoredKeyCodec.ts","../../src/keys/KeyManager.ts","../../src/vdr/keyVDR.ts","../../src/vdr/index.ts","../../src/IdentityEnv.ts","../../src/IdentityKit.ts","../../src/CadopIdentityKit.ts"],"sourcesContent":["/**\n * Test helpers for Rooch DID integration testing\n *\n * This module provides utilities to simplify DID-related integration tests\n * by abstracting away the complexity of on-chain DID creation and management.\n *\n * @module testHelpers\n * @example\n * ```ts\n * import { TestEnv, createSelfDid, createCadopDid } from '@nuwa-ai/identity-kit/testHelpers';\n *\n * if (TestEnv.skipIfNoNode()) return;\n *\n * const env = await TestEnv.bootstrap();\n * const { did, keyManager } = await createSelfDid(env);\n * ```\n */\n\nexport * from './env';\nexport * from './rooch';\nexport * from './didFactory';\nexport * from './types';\n\n// Re-export key classes and functions for convenience\nexport { TestEnv } from './env';\nexport { createSelfDid, createCadopCustodian, createDidViaCadop } from './didFactory';\nexport { RoochLocalNode, startLocalRoochNode, ensureRoochReady } from './roochLocalNode';\n","import { RoochClient } from '@roochnetwork/rooch-sdk';\nimport { DebugLogger } from '../utils/DebugLogger';\nimport { VDRRegistry } from '../vdr/VDRRegistry';\nimport { RoochVDR } from '../vdr/roochVDR';\nimport { KeyManager } from '../keys/KeyManager';\nimport { MemoryKeyStore } from '../keys/KeyStore';\nimport { existsSync } from 'fs';\nimport {\n TestEnvOptions,\n EnvironmentCheck,\n CreateSelfDidResult,\n CreateSelfDidOptions,\n CreateCadopDidOptions,\n RoochNodeOptions,\n RoochNodeHandle,\n} from './types';\nimport { RoochLocalNode } from './roochLocalNode';\n\n/**\n * Test environment for Rooch DID integration testing\n *\n * Provides a pre-configured environment with:\n * - Rooch client and VDR registry\n * - Helper methods for creating test identities\n *\n * Note: Each createSelfDid() call returns its own dedicated IdentityEnv,\n * which is preferred for multi-party testing scenarios to avoid conflicts.\n */\nexport class TestEnv {\n private static instance?: TestEnv;\n private logger: DebugLogger;\n\n public readonly rpcUrl: string;\n public readonly network: string;\n public readonly client: RoochClient;\n public readonly vdrRegistry: VDRRegistry;\n public readonly roochVDR: RoochVDR;\n\n private constructor(options: Required<TestEnvOptions>) {\n this.logger = DebugLogger.get('TestEnv');\n this.rpcUrl = options.rpcUrl;\n this.network = options.network;\n\n // Initialize Rooch client\n this.client = new RoochClient({ url: this.rpcUrl });\n\n // Initialize VDR\n this.vdrRegistry = VDRRegistry.getInstance();\n this.roochVDR = new RoochVDR({\n rpcUrl: this.rpcUrl,\n network: options.network as any,\n debug: options.debug,\n });\n\n // Register VDR if not already registered\n if (!this.vdrRegistry.getVDR('rooch')) {\n this.vdrRegistry.registerVDR(this.roochVDR);\n }\n\n if (options.debug) {\n this.logger.debug('TestEnv initialized', {\n rpcUrl: this.rpcUrl,\n network: this.network,\n });\n }\n }\n\n /**\n * Bootstrap test environment\n */\n static async bootstrap(options: TestEnvOptions = {}): Promise<TestEnv> {\n const resolvedOptions = await TestEnv.resolveOptions(options);\n let localNode: RoochNodeHandle | undefined;\n\n // If autoStartLocalNode is enabled, try to start a local node\n if (resolvedOptions.autoStartLocalNode) {\n try {\n // First, check if existing node is available\n const check = await TestEnv.checkEnvironment(resolvedOptions);\n if (!check.shouldSkip) {\n // Existing node is available, use it\n return new TestEnv(resolvedOptions);\n }\n } catch (error) {\n // Continue to start local node\n }\n\n try {\n console.log('🚀 Starting local Rooch node for testing...');\n localNode = await RoochLocalNode.start({\n binaryPath: process.env.ROOCH_E2E_BIN,\n network: resolvedOptions.network,\n debug: resolvedOptions.debug\n });\n\n // Update RPC URL to use the local node\n resolvedOptions.rpcUrl = localNode.rpcUrl;\n console.log(`✅ Local Rooch node started at ${localNode.rpcUrl}`);\n\n // Register cleanup handlers\n const cleanup = async () => {\n try {\n if (localNode) {\n console.log('🛑 Stopping local Rooch node...');\n await localNode.stop();\n console.log('✅ Local Rooch node stopped');\n }\n } catch (error) {\n console.error('❌ Error stopping local node:', error);\n }\n };\n\n // Register cleanup for various exit scenarios\n process.once('exit', cleanup);\n process.once('SIGINT', cleanup);\n process.once('SIGTERM', cleanup);\n process.once('SIGUSR2', cleanup); // nodemon restart\n\n } catch (error) {\n throw new Error(`Failed to start local Rooch node: ${error instanceof Error ? error.message : 'Unknown error'}`);\n }\n }\n\n try {\n // Check environment (will test connectivity to local node if started)\n const check = await TestEnv.checkEnvironment(resolvedOptions);\n if (check.shouldSkip) {\n throw new Error(`Test environment not available: ${check.reason}`);\n }\n\n return new TestEnv(resolvedOptions);\n } catch (error) {\n // Cleanup local node if TestEnv creation failed\n if (localNode) {\n try {\n await localNode.stop();\n } catch {\n // Ignore cleanup errors during failure cleanup\n }\n }\n throw error;\n }\n }\n\n /**\n * Check if integration tests should be skipped\n */\n static skipIfNoNode(): boolean {\n const check = TestEnv.checkEnvironmentSync();\n return check.shouldSkip;\n }\n\n /**\n * Synchronous environment check\n */\n static checkEnvironmentSync(): EnvironmentCheck {\n const isCI = process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true';\n const hasRpcUrl = !!process.env.ROOCH_NODE_URL;\n\n if (isCI && !hasRpcUrl) {\n return {\n shouldSkip: true,\n reason: 'ROOCH_NODE_URL not set in CI environment',\n };\n }\n\n return {\n shouldSkip: false,\n rpcUrl: process.env.ROOCH_NODE_URL || 'http://localhost:6767',\n };\n }\n\n /**\n * Async environment check with RPC connectivity test\n */\n static async checkEnvironment(options: Required<TestEnvOptions>): Promise<EnvironmentCheck> {\n try {\n const client = new RoochClient({ url: options.rpcUrl });\n // Try to get chain ID to verify connectivity\n await client.getChainId();\n\n return {\n shouldSkip: false,\n rpcUrl: options.rpcUrl,\n };\n } catch (error) {\n const isCI = process.env.CI === 'true' || process.env.GITHUB_ACTIONS === 'true';\n\n if (isCI) {\n return {\n shouldSkip: true,\n reason: `Cannot connect to Rooch node at ${options.rpcUrl}: ${error instanceof Error ? error.message : 'Unknown error'}`,\n };\n }\n\n // For local development, we might want to continue with a warning\n console.warn(\n `Warning: Cannot connect to Rooch node at ${options.rpcUrl}. Some tests may fail.`\n );\n return {\n shouldSkip: false,\n rpcUrl: options.rpcUrl,\n };\n }\n }\n\n /**\n * Resolve options with defaults\n */\n private static async resolveOptions(options: TestEnvOptions): Promise<Required<TestEnvOptions>> {\n const defaults = {\n rpcUrl: process.env.ROOCH_NODE_URL || 'http://localhost:6767',\n network: 'test' as const,\n autoStartLocalNode: false,\n faucetAmount: BigInt(1000000), // 1M base units\n debug: false,\n };\n\n const resolved = { ...defaults, ...options };\n\n // If autoStartLocalNode is enabled, validate ROOCH_E2E_BIN\n if (resolved.autoStartLocalNode) {\n const binaryPath = process.env.ROOCH_E2E_BIN;\n if (!binaryPath) {\n throw new Error('ROOCH_E2E_BIN environment variable is required when autoStartLocalNode is enabled');\n }\n\n if (!existsSync(binaryPath)) {\n throw new Error(`Rooch binary not found at: ${binaryPath}. Set ROOCH_E2E_BIN to a valid Rooch binary path.`);\n }\n }\n\n return resolved;\n }\n\n /**\n * Fund an account via faucet (placeholder for future implementation)\n */\n async fundAccount(address: string, amount?: bigint): Promise<void> {\n // This is a placeholder - actual faucet implementation would depend on the network\n this.logger.debug('Funding account', { address, amount });\n // For now, we assume accounts have sufficient funds or skip funding\n }\n}\n","/* eslint-disable no-console */\n/*\n * Lightweight environment-agnostic debug logger.\n *\n * ‑ Works in both Node.js and browser.\n * ‑ Supports level filtering (debug | info | warn | error | silent).\n * ‑ Namespaced: each module/class can request its own logger via DebugLogger.get(\"MyModule\").\n * ‑ Global level can be controlled at runtime via DebugLogger.setGlobalLevel() *or*\n * environment variable NUWA_LOG_LEVEL (node) / window.__NUWA_LOG_LEVEL__ (browser).\n */\n\nexport type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';\n\nconst LEVEL_ORDER: Record<LogLevel, number> = {\n debug: 10,\n info: 20,\n warn: 30,\n error: 40,\n silent: 50,\n};\n\nfunction detectInitialGlobalLevel(): LogLevel {\n // Node.js: use process.env if available\n if (typeof process !== 'undefined' && (process as any).env) {\n const envLevel = (process as any).env.NUWA_LOG_LEVEL as string | undefined;\n if (envLevel && envLevel in LEVEL_ORDER) return envLevel as LogLevel;\n }\n // Browser: allow runtime override via global variable\n if (typeof window !== 'undefined' && (window as any).__NUWA_LOG_LEVEL__) {\n const envLevel = (window as any).__NUWA_LOG_LEVEL__ as string;\n if (envLevel && envLevel in LEVEL_ORDER) return envLevel as LogLevel;\n }\n return 'info';\n}\n\nexport class DebugLogger {\n // ---------------------------------------------------------------------------\n // Static section\n // ---------------------------------------------------------------------------\n private static globalLevel: LogLevel = detectInitialGlobalLevel();\n private static loggers = new Map<string, DebugLogger>();\n private static defaultNamespace = 'global';\n\n /** Acquire (or create) a logger for the given namespace. */\n static get(namespace: string): DebugLogger {\n if (!DebugLogger.loggers.has(namespace)) {\n DebugLogger.loggers.set(namespace, new DebugLogger(namespace));\n }\n return DebugLogger.loggers.get(namespace)!;\n }\n\n /** Override global log level at runtime. */\n static setGlobalLevel(level: LogLevel): void {\n DebugLogger.globalLevel = level;\n // Propagate to existing instances unless they explicitly override.\n for (const logger of DebugLogger.loggers.values()) {\n if (!logger.levelOverridden) {\n logger.level = level;\n }\n }\n }\n\n /** Read current global level. */\n static getGlobalLevel(): LogLevel {\n return DebugLogger.globalLevel;\n }\n\n /** Set default namespace used by static convenience methods. */\n static setDefaultNamespace(namespace: string): void {\n DebugLogger.defaultNamespace = namespace;\n }\n\n // ---------------------------------------------------------------------------\n // Static convenience logging methods\n // ---------------------------------------------------------------------------\n /**\n * Log using the default namespace. Useful when callers don't need per-module loggers.\n * Example: DebugLogger.debug('hello')\n */\n static debug(...args: unknown[]): void {\n DebugLogger.get(DebugLogger.defaultNamespace).debug(...args);\n }\n\n static info(...args: unknown[]): void {\n DebugLogger.get(DebugLogger.defaultNamespace).info(...args);\n }\n\n static warn(...args: unknown[]): void {\n DebugLogger.get(DebugLogger.defaultNamespace).warn(...args);\n }\n\n static error(...args: unknown[]): void {\n DebugLogger.get(DebugLogger.defaultNamespace).error(...args);\n }\n\n // ---------------------------------------------------------------------------\n // Instance section\n // ---------------------------------------------------------------------------\n private level: LogLevel;\n private levelOverridden = false;\n\n private constructor(private namespace: string) {\n this.level = DebugLogger.globalLevel;\n }\n\n /** Override level for this logger only. */\n setLevel(level: LogLevel): void {\n this.level = level;\n this.levelOverridden = true;\n }\n\n // -------------------------------------------------------\n // Logging helpers\n // -------------------------------------------------------\n debug(...args: unknown[]): void {\n this._log('debug', args);\n }\n\n info(...args: unknown[]): void {\n this._log('info', args);\n }\n\n warn(...args: unknown[]): void {\n this._log('warn', args);\n }\n\n error(...args: unknown[]): void {\n this._log('error', args);\n }\n\n // prettier-ignore\n private _log(level: LogLevel, args: unknown[]): void {\n if (LEVEL_ORDER[level] < LEVEL_ORDER[this.level]) {\n return; // filtered out\n }\n\n const prefix = `[${this.namespace}]`;\n\n // Colorize in browser / modern terminal if desired; keep simple for now.\n switch (level) {\n case 'debug':\n console.debug(prefix, ...args);\n break;\n case 'info':\n console.info(prefix, ...args);\n break;\n case 'warn':\n console.warn(prefix, ...args);\n break;\n case 'error':\n console.error(prefix, ...args);\n break;\n }\n }\n}\n","import { DIDDocument } from '../types';\nimport { DIDDocumentCache } from './index';\n\n/**\n * A lightweight in-memory LRU cache implementation for DID Documents.\n * It is intentionally dependency-free so that the SDK does not pull in\n * additional packages by default. You can replace it with your own\n * implementation (Redis, IndexedDB, etc.) by implementing the\n * `DIDDocumentCache` interface and providing it to `VDRRegistry.setCache()`.\n */\nexport class InMemoryLRUDIDDocumentCache implements DIDDocumentCache {\n private readonly capacity: number;\n private readonly map: Map<string, DIDDocument | null>;\n\n constructor(maxEntries = 1000) {\n this.capacity = maxEntries;\n this.map = new Map<string, DIDDocument | null>();\n }\n\n get(did: string): DIDDocument | null | undefined {\n if (!this.map.has(did)) return undefined;\n const value = this.map.get(did) ?? null;\n // Refresh the recently used key to the end.\n this.map.delete(did);\n this.map.set(did, value);\n return value;\n }\n\n set(did: string, doc: DIDDocument | null): void {\n if (this.map.has(did)) {\n this.map.delete(did);\n } else if (this.map.size >= this.capacity) {\n // Evict the least-recently-used entry (Map iteration order is insertion order).\n const lruKey = this.map.keys().next().value;\n if (lruKey !== undefined) {\n this.map.delete(lruKey);\n }\n }\n this.map.set(did, doc);\n }\n\n has(did: string): boolean {\n return this.map.has(did);\n }\n\n delete(did: string): void {\n this.map.delete(did);\n }\n\n clear(): void {\n this.map.clear();\n }\n}\n","/**\n * Unified error handling for IdentityKit\n */\n\n/**\n * Error codes for IdentityKit operations\n */\nexport enum IdentityKitErrorCode {\n // Configuration and initialization\n INVALID_CONFIGURATION = 'INVALID_CONFIGURATION',\n ENVIRONMENT_NOT_SUPPORTED = 'ENVIRONMENT_NOT_SUPPORTED',\n INITIALIZATION_FAILED = 'INITIALIZATION_FAILED',\n\n // DID operations\n DID_NOT_FOUND = 'DID_NOT_FOUND',\n DID_INVALID_FORMAT = 'DID_INVALID_FORMAT',\n DID_METHOD_NOT_SUPPORTED = 'DID_METHOD_NOT_SUPPORTED',\n DID_CREATION_FAILED = 'DID_CREATION_FAILED',\n DID_RESOLUTION_FAILED = 'DID_RESOLUTION_FAILED',\n DID_NOT_SET = 'DID_NOT_SET',\n DID_SERVICE_NOT_FOUND = 'DID_SERVICE_NOT_FOUND',\n DID_VERIFICATION_METHOD_NOT_FOUND = 'DID_VERIFICATION_METHOD_NOT_FOUND',\n\n // VDR (Verifiable Data Registry) operations\n VDR_NOT_AVAILABLE = 'VDR_NOT_AVAILABLE',\n VDR_OPERATION_FAILED = 'VDR_OPERATION_FAILED',\n VDR_NETWORK_ERROR = 'VDR_NETWORK_ERROR',\n VDR_INVALID_RESPONSE = 'VDR_INVALID_RESPONSE',\n\n // Key management\n KEY_NOT_FOUND = 'KEY_NOT_FOUND',\n KEY_INVALID_FORMAT = 'KEY_INVALID_FORMAT',\n KEY_GENERATION_FAILED = 'KEY_GENERATION_FAILED',\n KEY_IMPORT_FAILED = 'KEY_IMPORT_FAILED',\n KEY_STORAGE_ERROR = 'KEY_STORAGE_ERROR',\n KEY_PERMISSION_DENIED = 'KEY_PERMISSION_DENIED',\n KEY_VALIDATION_FAILED = 'KEY_VALIDATION_FAILED',\n KEY_PRIVATE_KEY_NOT_AVAILABLE = 'KEY_PRIVATE_KEY_NOT_AVAILABLE',\n KEY_DID_MISMATCH = 'KEY_DID_MISMATCH',\n KEY_ID_MISMATCH = 'KEY_ID_MISMATCH',\n KEY_TYPE_NOT_SUPPORTED = 'KEY_TYPE_NOT_SUPPORTED',\n KEY_ALREADY_EXISTS = 'KEY_ALREADY_EXISTS',\n\n // Signing operations\n SIGNING_FAILED = 'SIGNING_FAILED',\n SIGNATURE_VERIFICATION_FAILED = 'SIGNATURE_VERIFICATION_FAILED',\n SIGNER_NOT_AVAILABLE = 'SIGNER_NOT_AVAILABLE',\n SIGNER_INVALID_DID = 'SIGNER_INVALID_DID',\n SIGNER_NO_KEYS = 'SIGNER_NO_KEYS',\n\n // Authentication (from existing AuthErrorCode)\n AUTH_INVALID_HEADER = 'AUTH_INVALID_HEADER',\n AUTH_INVALID_BASE64 = 'AUTH_INVALID_BASE64',\n AUTH_INVALID_JSON = 'AUTH_INVALID_JSON',\n AUTH_MISSING_SIGNATURE = 'AUTH_MISSING_SIGNATURE',\n AUTH_TIMESTAMP_OUT_OF_WINDOW = 'AUTH_TIMESTAMP_OUT_OF_WINDOW',\n AUTH_NONCE_REPLAYED = 'AUTH_NONCE_REPLAYED',\n AUTH_DID_DOCUMENT_NOT_FOUND = 'AUTH_DID_DOCUMENT_NOT_FOUND',\n AUTH_VERIFICATION_METHOD_NOT_FOUND = 'AUTH_VERIFICATION_METHOD_NOT_FOUND',\n AUTH_INVALID_PUBLIC_KEY = 'AUTH_INVALID_PUBLIC_KEY',\n AUTH_DID_MISMATCH = 'AUTH_DID_MISMATCH',\n\n // Web-specific operations\n WEB_BROWSER_NOT_SUPPORTED = 'WEB_BROWSER_NOT_SUPPORTED',\n WEB_STORAGE_NOT_AVAILABLE = 'WEB_STORAGE_NOT_AVAILABLE',\n WEB_DEEPLINK_FAILED = 'WEB_DEEPLINK_FAILED',\n WEB_CADOP_CONNECTION_FAILED = 'WEB_CADOP_CONNECTION_FAILED',\n WEB_OAUTH_CALLBACK_FAILED = 'WEB_OAUTH_CALLBACK_FAILED',\n WEB_NOT_CONNECTED = 'WEB_NOT_CONNECTED',\n WEB_CALLBACK_FAILED = 'WEB_CALLBACK_FAILED',\n\n // React-specific operations\n REACT_NOT_AVAILABLE = 'REACT_NOT_AVAILABLE',\n REACT_HOOK_MISUSE = 'REACT_HOOK_MISUSE',\n\n // Crypto operations\n CRYPTO_PROVIDER_NOT_FOUND = 'CRYPTO_PROVIDER_NOT_FOUND',\n CRYPTO_OPERATION_FAILED = 'CRYPTO_OPERATION_FAILED',\n CRYPTO_KEY_DERIVATION_FAILED = 'CRYPTO_KEY_DERIVATION_FAILED',\n\n // Multibase operations\n MULTIBASE_DECODE_FAILED = 'MULTIBASE_DECODE_FAILED',\n MULTIBASE_ENCODE_FAILED = 'MULTIBASE_ENCODE_FAILED',\n MULTIBASE_INVALID_FORMAT = 'MULTIBASE_INVALID_FORMAT',\n\n // Validation operations\n SCOPE_VALIDATION_FAILED = 'SCOPE_VALIDATION_FAILED',\n SCOPE_INVALID_FORMAT = 'SCOPE_INVALID_FORMAT',\n VALIDATION_FAILED = 'VALIDATION_FAILED',\n INVALID_INPUT_FORMAT = 'INVALID_INPUT_FORMAT',\n\n // DeepLink operations\n DEEPLINK_INVALID_STATE = 'DEEPLINK_INVALID_STATE',\n DEEPLINK_CALLBACK_FAILED = 'DEEPLINK_CALLBACK_FAILED',\n\n // Storage operations\n STORAGE_QUOTA_EXCEEDED = 'STORAGE_QUOTA_EXCEEDED',\n STORAGE_OPERATION_FAILED = 'STORAGE_OPERATION_FAILED',\n\n // Signer operations\n SIGNER_CONVERSION_FAILED = 'SIGNER_CONVERSION_FAILED',\n SIGNER_OPERATION_FAILED = 'SIGNER_OPERATION_FAILED',\n\n // Permission operations\n PERMISSION_DENIED = 'PERMISSION_DENIED',\n OPERATION_NOT_PERMITTED = 'OPERATION_NOT_PERMITTED',\n\n // Generic errors\n OPERATION_NOT_SUPPORTED = 'OPERATION_NOT_SUPPORTED',\n INVALID_PARAMETER = 'INVALID_PARAMETER',\n INTERNAL_ERROR = 'INTERNAL_ERROR',\n NETWORK_ERROR = 'NETWORK_ERROR',\n TIMEOUT_ERROR = 'TIMEOUT_ERROR',\n}\n\n/**\n * Base error class for all IdentityKit errors\n */\nexport class IdentityKitError extends Error {\n public readonly code: IdentityKitErrorCode;\n public readonly category: string;\n public readonly details?: unknown;\n public readonly cause?: Error;\n\n constructor(\n code: IdentityKitErrorCode,\n message: string,\n options?: {\n category?: string;\n details?: unknown;\n cause?: Error;\n }\n ) {\n super(message);\n this.name = 'IdentityKitError';\n this.code = code;\n this.category = options?.category || this.inferCategory(code);\n this.details = options?.details;\n this.cause = options?.cause;\n\n // Maintain proper stack trace\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, this.constructor);\n }\n\n // Chain the original error stack if available\n if (options?.cause) {\n this.stack = `${this.stack}\\nCaused by: ${options.cause.stack}`;\n }\n }\n\n /**\n * Infer error category from error code\n */\n private inferCategory(code: IdentityKitErrorCode): string {\n if (code.startsWith('AUTH_')) return 'authentication';\n if (code.startsWith('DID_')) return 'did';\n if (code.startsWith('VDR_')) return 'vdr';\n if (code.startsWith('KEY_')) return 'key-management';\n if (code.startsWith('WEB_')) return 'web';\n if (code.startsWith('REACT_')) return 'react';\n if (code.startsWith('CRYPTO_')) return 'crypto';\n if (code.startsWith('MULTIBASE_')) return 'multibase';\n if (code.startsWith('SCOPE_') || code.startsWith('VALIDATION_')) return 'validation';\n if (code.startsWith('DEEPLINK_')) return 'deeplink';\n if (code.startsWith('STORAGE_')) return 'storage';\n if (code.startsWith('SIGNER_')) return 'signer';\n if (code.includes('SIGNING') || code.includes('SIGNATURE')) return 'signing';\n if (code.includes('NETWORK')) return 'network';\n if (code.includes('PERMISSION')) return 'permission';\n return 'general';\n }\n\n /**\n * Convert to a plain object for serialization\n */\n toJSON(): {\n name: string;\n code: string;\n category: string;\n message: string;\n details?: unknown;\n stack?: string;\n } {\n return {\n name: this.name,\n code: this.code,\n category: this.category,\n message: this.message,\n details: this.details,\n stack: this.stack,\n };\n }\n\n /**\n * Get a user-friendly error message with suggestions\n */\n getUserMessage(): string {\n const suggestions = this.getSuggestions();\n let message = this.message;\n\n if (suggestions.length > 0) {\n message += '\\n\\nSuggestions:\\n' + suggestions.map(s => `• ${s}`).join('\\n');\n }\n\n return message;\n }\n\n /**\n * Get contextual suggestions based on error code\n */\n private getSuggestions(): string[] {\n switch (this.code) {\n case IdentityKitErrorCode.WEB_BROWSER_NOT_SUPPORTED:\n return [\n 'Use a modern browser that supports required Web APIs',\n \"Check if you're running in a browser environment\",\n ];\n\n case IdentityKitErrorCode.WEB_STORAGE_NOT_AVAILABLE:\n return [\n 'Enable localStorage or IndexedDB in your browser',\n \"Check if you're in private/incognito mode\",\n 'Consider using memory storage as fallback',\n ];\n\n case IdentityKitErrorCode.DID_METHOD_NOT_SUPPORTED:\n return [\n 'Check if the DID method is registered with VDRRegistry',\n 'Verify the DID format is correct',\n ];\n\n case IdentityKitErrorCode.VDR_NETWORK_ERROR:\n return [\n 'Check your network connection',\n 'Verify the RPC URL is correct and accessible',\n 'Check if the VDR service is running',\n ];\n\n case IdentityKitErrorCode.KEY_STORAGE_ERROR:\n return [\n 'Check browser storage permissions',\n 'Verify storage quota is not exceeded',\n 'Try clearing browser storage and retry',\n ];\n\n case IdentityKitErrorCode.REACT_NOT_AVAILABLE:\n return [\n 'Ensure React is properly installed and imported',\n \"Check if you're using the hook in a React component\",\n ];\n\n case IdentityKitErrorCode.CRYPTO_PROVIDER_NOT_FOUND:\n return [\n 'Check if the key type is supported',\n 'Verify the crypto provider factory configuration',\n ];\n\n case IdentityKitErrorCode.MULTIBASE_DECODE_FAILED:\n return [\n 'Verify the encoded string format is correct',\n 'Check if the multibase prefix is valid',\n 'Ensure the input is not corrupted',\n ];\n\n case IdentityKitErrorCode.SCOPE_VALIDATION_FAILED:\n return [\n 'Check the scope format: address::module::function',\n 'Verify the address format is valid',\n 'Ensure module and function names are correct',\n ];\n\n case IdentityKitErrorCode.DEEPLINK_INVALID_STATE:\n return [\n 'Check if the state parameter matches the stored value',\n 'Verify the callback URL parameters are correct',\n 'Ensure the session storage is available',\n ];\n\n case IdentityKitErrorCode.STORAGE_QUOTA_EXCEEDED:\n return [\n 'Clear unused data from browser storage',\n 'Check available storage quota',\n 'Consider using a different storage strategy',\n ];\n\n case IdentityKitErrorCode.SIGNER_CONVERSION_FAILED:\n return [\n 'Verify the signer implements the required interface',\n 'Check if the key ID is available in the signer',\n 'Ensure the DID account signer is properly configured',\n ];\n\n default:\n return [];\n }\n }\n}\n\n/**\n * Factory functions for creating specific error types\n */\n\nexport function createConfigurationError(\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(IdentityKitErrorCode.INVALID_CONFIGURATION, message, {\n category: 'configuration',\n details,\n cause,\n });\n}\n\nexport function createDIDError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'did',\n details,\n cause,\n });\n}\n\nexport function createVDRError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'vdr',\n details,\n cause,\n });\n}\n\nexport function createKeyManagementError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'key-management',\n details,\n cause,\n });\n}\n\nexport function createAuthenticationError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'authentication',\n details,\n cause,\n });\n}\n\nexport function createWebError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'web',\n details,\n cause,\n });\n}\n\nexport function createReactError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'react',\n details,\n cause,\n });\n}\n\nexport function createCryptoError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'crypto',\n details,\n cause,\n });\n}\n\nexport function createMultibaseError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'multibase',\n details,\n cause,\n });\n}\n\nexport function createStorageError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'storage',\n details,\n cause,\n });\n}\n\nexport function createValidationError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'validation',\n details,\n cause,\n });\n}\n\nexport function createSignerError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'signer',\n details,\n cause,\n });\n}\n\nexport function createDeepLinkError(\n code: IdentityKitErrorCode,\n message: string,\n details?: unknown,\n cause?: Error\n): IdentityKitError {\n return new IdentityKitError(code, message, {\n category: 'deeplink',\n details,\n cause,\n });\n}\n\n/**\n * Wrap unknown errors into IdentityKitError\n */\nexport function wrapUnknownError(\n error: unknown,\n context: string,\n code: IdentityKitErrorCode = IdentityKitErrorCode.INTERNAL_ERROR\n): IdentityKitError {\n const originalError = error instanceof Error ? error : new Error(String(error));\n\n return new IdentityKitError(code, `${context}: ${originalError.message}`, {\n cause: originalError,\n details: { context },\n });\n}\n\n/**\n * Type guard to check if an error is an IdentityKitError\n */\nexport function isIdentityKitError(error: unknown): error is IdentityKitError {\n return error instanceof IdentityKitError;\n}\n\n/**\n * Legacy compatibility: map old AuthErrorCode to new IdentityKitErrorCode\n */\nexport const AuthErrorCodeMapping = {\n INVALID_HEADER: IdentityKitErrorCode.AUTH_INVALID_HEADER,\n INVALID_BASE64: IdentityKitErrorCode.AUTH_INVALID_BASE64,\n INVALID_JSON: IdentityKitErrorCode.AUTH_INVALID_JSON,\n MISSING_SIGNATURE: IdentityKitErrorCode.AUTH_MISSING_SIGNATURE,\n TIMESTAMP_OUT_OF_WINDOW: IdentityKitErrorCode.AUTH_TIMESTAMP_OUT_OF_WINDOW,\n NONCE_REPLAYED: IdentityKitErrorCode.AUTH_NONCE_REPLAYED,\n SIGNATURE_VERIFICATION_FAILED: IdentityKitErrorCode.SIGNATURE_VERIFICATION_FAILED,\n DID_DOCUMENT_NOT_FOUND: IdentityKitErrorCode.AUTH_DID_DOCUMENT_NOT_FOUND,\n VERIFICATION_METHOD_NOT_FOUND: IdentityKitErrorCode.AUTH_VERIFICATION_METHOD_NOT_FOUND,\n INVALID_PUBLIC_KEY: IdentityKitErrorCode.AUTH_INVALID_PUBLIC_KEY,\n DID_MISMATCH: IdentityKitErrorCode.AUTH_DID_MISMATCH,\n} as const;\n","import { DIDDocument, DIDResolver } from '../types';\nimport { DIDDocumentCache } from '../cache';\nimport {\n VDRInterface,\n DIDCreationRequest,\n DIDCreationResult,\n CADOPCreationRequest,\n CADOPControllerCreationRequest,\n} from './types';\n\nimport { InMemoryLRUDIDDocumentCache } from '../cache/InMemoryLRUDIDDocumentCache';\nimport { IdentityKitErrorCode, createVDRError } from '../errors';\n\n/**\n * Global registry for VDR (Verifiable Data Registry) implementations.\n * This singleton manages all registered VDRs and maintains a DID Document cache.\n */\nexport class VDRRegistry implements DIDResolver {\n private static instance: VDRRegistry;\n private vdrs: Map<string, VDRInterface> = new Map();\n\n private cache: DIDDocumentCache;\n\n private constructor() {\n // Use the default in-memory cache unless overridden by the developer.\n this.cache = new InMemoryLRUDIDDocumentCache();\n }\n\n static getInstance(): VDRRegistry {\n if (!this.instance) {\n this.instance = new VDRRegistry();\n }\n return this.instance;\n }\n\n /** Register a VDR implementation for its DID method (e.g., 'key', 'rooch'). */\n registerVDR(vdr: VDRInterface) {\n this.vdrs.set(vdr.getMethod(), vdr);\n }\n\n /** Retrieve a previously registered VDR implementation by its method. */\n getVDR(method: string): VDRInterface | undefined {\n return this.vdrs.get(method);\n }\n\n /**\n * Override the default cache implementation.\n * This allows developers to provide their own cache (e.g., Redis, browser storage).\n */\n setCache(cache: DIDDocumentCache) {\n this.cache = cache;\n }\n\n /** Returns the currently configured cache instance. */\n getCache(): DIDDocumentCache {\n return this.cache;\n }\n\n async resolveDID(did: string, options?: { forceRefresh?: boolean }): Promise<DIDDocument | null> {\n const method = did.split(':')[1];\n const vdr = this.vdrs.get(method);\n if (!vdr) {\n throw createVDRError(\n IdentityKitErrorCode.VDR_NOT_AVAILABLE,\n `No VDR available for method: ${method}`,\n { method, availableMethods: Array.from(this.vdrs.keys()) }\n );\n }\n\n // Attempt to serve from cache if allowed.\n if (!options?.forceRefresh) {\n const cached = this.cache.get(did);\n if (cached !== undefined) {\n return cached;\n }\n }\n\n const resolved = await vdr.resolve(did);\n // Cache the resolution result (including null for negative caching).\n this.cache.set(did, resolved);\n return resolved;\n }\n\n async createDID(\n method: string,\n creationRequest: DIDCreationRequest,\n options?: Record<string, any>\n ): Promise<DIDCreationResult> {\n const vdr = this.vdrs.get(method);\n if (!vdr) {\n throw createVDRError(\n IdentityKitErrorCode.VDR_NOT_AVAILABLE,\n `No VDR available for method: ${method}`,\n { method, availableMethods: Array.from(this.vdrs.keys()) }\n );\n }\n const result = await vdr.create(creationRequest, options);\n if (result.success && result.didDocument) {\n this.cache.set(result.didDocument.id, result.didDocument);\n }\n return result;\n }\n\n async createDIDViaCADOP(\n method: string,\n creationRequest: CADOPCreationRequest,\n options?: Record<string, any>\n ): Promise<DIDCreationResult> {\n const vdr = this.vdrs.get(method);\n if (!vdr) {\n throw createVDRError(\n IdentityKitErrorCode.VDR_NOT_AVAILABLE,\n `No VDR available for method: ${method}`,\n { method, availableMethods: Array.from(this.vdrs.keys()) }\n );\n }\n const result = await vdr.createViaCADOP(creationRequest, options);\n if (result.success && result.didDocument) {\n this.cache.set(result.didDocument.id, result.didDocument);\n }\n return result;\n }\n\n async createDIDViaCADOPWithController(\n method: string,\n creationRequest: CADOPControllerCreationRequest,\n options?: Record<string, any>\n ): Promise<DIDCreationResult> {\n const vdr = this.vdrs.get(method);\n if (!vdr) {\n throw createVDRError(\n IdentityKitErrorCode.VDR_NOT_AVAILABLE,\n `No VDR available for method: ${method}`,\n { method, availableMethods: Array.from(this.vdrs.keys()) }\n );\n }\n const result = await vdr.createViaCADOPWithController(creationRequest, options);\n if (result.success && result.didDocument) {\n this.cache.set(result.didDocument.id, result.didDocument);\n }\n return result;\n }\n\n async exists(did: string): Promise<boolean> {\n const method = did.split(':')[1];\n const vdr = this.vdrs.get(method);\n if (!vdr) {\n throw createVDRError(\n IdentityKitErrorCode.VDR_NOT_AVAILABLE,\n `No VDR available for method: ${method}`,\n { method, availableMethods: Array.from(this.vdrs.keys()) }\n );\n }\n\n // If we have a positive cache entry, short-circuit the call.\n if (this.cache.has(did)) {\n const doc = this.cache.get(did);\n return doc !== null;\n }\n\n const exists = await vdr.exists(did);\n // We don't cache the existence check result here to avoid stale data.\n return exists;\n }\n}\n","import {\n RoochClient,\n Transaction,\n Args,\n getRoochNodeUrl as sdkGetRoochNodeUrl,\n Signer,\n RoochAddress,\n SignatureScheme,\n Keypair,\n PublicKey,\n Address,\n Bytes,\n Authenticator,\n BitcoinAddress,\n ObjectStateView,\n Ed25519PublicKey,\n Secp256k1PublicKey,\n EventView,\n} from '@roochnetwork/rooch-sdk';\nimport {\n DIDDocument,\n VerificationMethod,\n VerificationRelationship,\n ServiceEndpoint,\n} from '../types/did';\nimport { SignerInterface, DidAccountSigner, isSignerInterface } from '../signers';\nimport { KeyType, keyTypeToRoochSignatureScheme } from '../types/crypto';\nimport {\n DIDCreationRequest,\n DIDCreationResult,\n CADOPCreationRequest,\n CADOPControllerCreationRequest,\n} from './types';\nimport { AbstractVDR } from './abstractVDR';\nimport {\n convertMoveDIDDocumentToInterface,\n formatDIDString,\n parseDIDCreatedEvent,\n resolveDidObjectID,\n} from './roochVDRTypes';\nimport { DebugLogger } from '../utils/DebugLogger';\nimport { parseDid, extractFragmentFromId } from '../utils/did';\nimport { validateScopes, combineScopes } from '../utils/sessionScopes';\nimport {\n IdentityKitErrorCode,\n createVDRError,\n createValidationError,\n createSignerError,\n wrapUnknownError,\n} from '../errors';\n\nexport interface RoochClientConfig {\n url: string;\n transport?: any;\n}\n\nexport interface RoochTransactionResult {\n execution_info: {\n status: {\n type: string; // 'executed' | 'failed'\n };\n gas_used: string;\n };\n output?: {\n events?: Array<{\n event_type: string;\n event_data: string;\n event_index: string;\n decoded_event_data?: any;\n }>;\n };\n transaction: any;\n}\n\n/**\n * Options for RoochVDR configuration\n */\nexport interface RoochVDROptions {\n /**\n * Rooch RPC endpoint URL\n */\n rpcUrl?: string;\n\n /**\n * Network type (local, dev, test, main)\n */\n network?: 'local' | 'dev' | 'test' | 'main';\n\n /**\n * Enable debug mode for detailed logging\n */\n debug?: boolean;\n}\n\n/**\n * Result of store operation with actual DID address\n */\nexport interface StoreResult {\n success: boolean;\n actualDIDAddress?: string;\n}\n\n/**\n * Options for Rooch VDR operations\n */\nexport interface RoochVDROperationOptions {\n /**\n * Signer to use for this operation\n */\n signer?: SignerInterface | Signer;\n\n /**\n * Key ID to use for this operation\n */\n keyId?: string;\n\n /**\n * Custom session key scopes (for authentication VM)\n * Only used when adding a verification method with authentication relationship\n */\n scopes?: string[];\n\n /**\n * Advanced blockchain transaction options\n * For high-level users who need fine-grained control over transaction parameters\n */\n advanced?: RoochTxnOptions;\n}\n\n/**\n * Advanced Rooch blockchain transaction options\n * These options are typically only needed for advanced use cases\n */\nexport interface RoochTxnOptions {\n /**\n * Maximum gas limit for the transaction\n */\n maxGas?: number;\n}\n\n/**\n * VDR implementation for did:rooch method\n *\n * This implementation integrates with Rooch network's DID contract system\n * to provide on-chain DID document storage and management.\n */\nexport class RoochVDR extends AbstractVDR {\n private readonly options: RoochVDROptions;\n private client: RoochClient;\n private readonly didContractAddress: string;\n private readonly debug: boolean;\n private readonly logger: DebugLogger;\n private lastCreatedDIDAddress?: string;\n\n constructor(options: RoochVDROptions) {\n super('rooch');\n this.options = options;\n this.didContractAddress = '0x3::did';\n this.debug = options.debug || false;\n this.logger = DebugLogger.get('RoochVDR');\n if (this.debug) {\n this.logger.setLevel('debug');\n }\n\n let rpcUrl = options.rpcUrl;\n if (!rpcUrl) {\n rpcUrl = RoochVDR.getRoochNodeUrl(options.network || 'test');\n }\n this.logger.debug(`RoochVDR initialized with rpcUrl: ${rpcUrl}`);\n // Initialize Rooch client\n this.client = new RoochClient({ url: rpcUrl });\n }\n\n /**\n * Log message if debug mode is enabled\n */\n private debugLog(message: string, data?: any) {\n if (data !== undefined) {\n this.logger.debug(message, data);\n } else {\n this.logger.debug(message);\n }\n }\n\n /**\n * Log error message (always logged regardless of debug mode)\n */\n private errorLog(message: string, error?: any) {\n if (error !== undefined) {\n this.logger.error(message, error);\n } else {\n this.logger.error(message);\n }\n }\n\n private async convertSigner(signer: SignerInterface | Signer, keyId?: string): Promise<Signer> {\n // If it implements SignerInterface, convert it to DidAccountSigner\n if (isSignerInterface(signer)) {\n return DidAccountSigner.create(signer, keyId);\n }\n // Fallback: assume it's Signer\n return signer;\n }\n\n /**\n * Override create method to support Rooch dynamic DID generation\n */\n async create(\n request: DIDCreationRequest,\n options?: RoochVDROperationOptions\n ): Promise<DIDCreationResult> {\n try {\n const signer = options?.signer;\n if (!signer) {\n throw createSignerError(\n IdentityKitErrorCode.SIGNER_NOT_AVAILABLE,\n 'No signer provided for create operation',\n { operation: 'create', request }\n );\n }\n\n this.debugLog('Creating DID with request:', request);\n\n const didAccountSigner = await this.convertSigner(signer, options?.keyId);\n\n // Always combine base scopes with custom scopes\n const finalScopes = combineScopes(request.customScopes || []);\n\n // Validate all scopes\n const scopeValidation = validateScopes(finalScopes);\n if (!scopeValidation.valid) {\n throw createValidationError(\n IdentityKitErrorCode.SCOPE_VALIDATION_FAILED,\n `Invalid scope format: ${scopeValidation.invalidScopes.join(', ')}`,\n {\n invalidScopes: scopeValidation.invalidScopes,\n allScopes: finalScopes,\n operation: 'create',\n }\n );\n }\n\n // Always use the scopes version since we always have scopes (at minimum base scopes)\n const transaction = this.createTransaction();\n transaction.callFunction({\n target: `${this.didContractAddress}::create_did_object_for_self_with_custom_scopes_entry`,\n args: [Args.string(request.publicKeyMultibase), Args.vec('string', finalScopes)],\n maxGas: options?.advanced?.maxGas || 100000000,\n });\n\n this.debugLog('Creating DID with scopes:', finalScopes);\n\n this.debugLog('Creating DID Transaction:', transaction);\n\n // Execute transaction\n const result = await this.client.signAndExecuteTransaction({\n transaction,\n signer: didAccountSigner,\n option: { withOutput: true },\n });\n\n const success = result.execution_info.status.type === 'executed';\n\n if (!success) {\n // Return preferredDID or generate a failure placeholder on failure\n return {\n success: false,\n error:\n 'Transaction execution failed, execution_info: ' +\n JSON.stringify(result.execution_info),\n debug: {\n requestedDID: request.preferredDID,\n transactionResult: result.execution_info,\n },\n };\n }\n\n // Parse the actual created DID\n const didCreatedEvent = result.output?.events?.find(\n (event: EventView) => event.event_type === '0x3::did::DIDCreatedEvent'\n );\n if (!didCreatedEvent) {\n throw createVDRError(\n IdentityKitErrorCode.VDR_INVALID_RESPONSE,\n 'DIDCreatedEvent not found in transaction result',\n { transactionResult: result, events: result.output?.events, operation: 'create' }\n );\n }\n let actualDID = this.parseDIDCreatedEventAndGetDID(didCreatedEvent);\n\n this.lastCreatedDIDAddress = actualDID;\n\n let didDocument = await this.resolve(actualDID);\n if (!didDocument) {\n throw createVDRError(\n IdentityKitErrorCode.DID_RESOLUTION_FAILED,\n `DID document not found with DID: ${actualDID}`,\n { did: actualDID, operation: 'create' }\n );\n }\n\n return {\n success: true,\n didDocument: didDocument,\n transactionHash: (result as any).transaction_hash,\n debug: {\n requestedDID: request.preferredDID,\n actualDID: actualDID || undefined,\n events: result.output?.events,\n },\n };\n } catch (error) {\n this.errorLog('Error creating DID:', error);\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n }\n\n /**\n * Override CADOP creation method\n */\n async createViaCADOP(\n request: CADOPCreationRequest,\n options?: RoochVDROperationOptions\n ): Promise<DIDCreationResult> {\n try {\n const signer = options?.signer;\n if (!signer) {\n throw createSignerError(\n IdentityKitErrorCode.SIGNER_NOT_AVAILABLE,\n 'No custodian signer provided for CADOP operation',\n { operation: 'createViaCADOP', request }\n );\n }\n\n this.debugLog('Creating DID via CADOP with request:', request);\n const didAccountSigner = await this.convertSigner(signer, options?.keyId);\n\n // Always combine base scopes with custom scopes\n const finalScopes = combineScopes(request.customScopes || []);\n\n // Validate all scopes\n const scopeValidation = validateScopes(finalScopes);\n if (!scopeValidation.valid) {\n throw createValidationError(\n IdentityKitErrorCode.SCOPE_VALIDATION_FAILED,\n `Invalid scope format: ${scopeValidation.invalidScopes.join(', ')}`,\n {\n invalidScopes: scopeValidation.invalidScopes,\n allScopes: finalScopes,\n operation: 'createViaCADOP',\n }\n );\n }\n\n // Always use the scopes version since the contract will add base scopes\n const transaction = this.createTransaction();\n transaction.callFunction({\n target: `${this.didContractAddress}::create_did_object_via_cadop_with_did_key_and_scopes_entry`,\n args: [\n Args.string(request.userDidKey),\n Args.string(request.custodianServicePublicKey),\n Args.string(request.custodianServiceVMType),\n Args.vec('string', finalScopes),\n ],\n maxGas: options?.advanced?.maxGas || 100000000,\n });\n\n this.debugLog('Creating DID via CADOP with scopes:', finalScopes);\n\n this.debugLog('Creating DID via CADOP Transaction:', transaction);\n\n // Execute transaction\n const result = await this.client.signAndExecuteTransaction({\n transaction,\n signer: didAccountSigner,\n option: { withOutput: true },\n });\n\n this.debugLog('Creating DID via CADOP Transaction Result:', result);\n\n const success = result.execution_info.status.type === 'executed';\n\n if (!success) {\n return {\n success: false,\n error:\n 'CADOP transaction execution failed, execution_info: ' +\n JSON.stringify(result.execution_info),\n };\n }\n\n // Parse the created DID\n const didCreatedEvent = result.output?.events?.find(\n (event: any) => event.event_type === '0x3::did::DIDCreatedEvent'\n );\n if (!didCreatedEvent) {\n throw createVDRError(\n IdentityKitErrorCode.VDR_INVALID_RESPONSE,\n 'DIDCreatedEvent not found in transaction result',\n { transactionResult: result, events: result.output?.events }\n );\n }\n let actualDID = this.parseDIDCreatedEventAndGetDID(didCreatedEvent);\n let didDocument = await this.resolve(actualDID);\n if (!didDocument) {\n throw createVDRError(\n IdentityKitErrorCode.DID_RESOLUTION_FAILED,\n `DID document not found with DID: ${actualDID}`,\n { did: actualDID }\n );\n }\n return {\n success: true,\n didDocument: didDocument,\n transactionHash: (result as any).transaction_hash,\n };\n } catch (error) {\n this.errorLog('Error creating DID via CADOP:', error);\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Unknown error',\n };\n }\n }\n\n /**\n * Create DID via CADOP with controller (supports did:key, did:bitcoin