]> git.codecow.com Git - libnemo.git/commitdiff
Rewrite vault worker to eliminate extraneous class wrapper. Refactor vault host and...
authorChris Duncan <chris@zoso.dev>
Tue, 31 Mar 2026 08:32:07 +0000 (01:32 -0700)
committerChris Duncan <chris@zoso.dev>
Tue, 31 Mar 2026 08:32:07 +0000 (01:32 -0700)
src/lib/vault/index.ts
src/lib/vault/vault-worker.ts

index 0273de5e716a000d1e19629aedbc39f9ca5bcc47..80d309758253af1532bd59d106bf979e30845c3e 100644 (file)
@@ -5,6 +5,7 @@ import { Worker as NodeWorker } from 'node:worker_threads'
 import { Data } from '../database'
 
 type Task = {
+       url: string
        id: number
        data: Record<string, Data | Record<string, Data>>
        reject: (value: any) => void
@@ -56,6 +57,7 @@ export class Vault {
                                return reject('Worker terminated')
                        }
                        const task: Task = {
+                               url: this.#url,
                                id: performance.now(),
                                data,
                                resolve,
@@ -76,7 +78,7 @@ export class Vault {
                this.#job = this.#queue.shift()
                this.#isIdle = this.#job == null
                if (this.#job != null) {
-                       const { data, reject } = this.#job
+                       const { url, id, data, reject } = this.#job
                        try {
                                const buffers: ArrayBuffer[] = []
                                for (const d of Object.values(data)) {
@@ -84,6 +86,8 @@ export class Vault {
                                                buffers.push(d)
                                        }
                                }
+                               data.url = url
+                               data.id = id
                                BROWSER: this.#worker.postMessage(data, buffers)
                                NODE: this.#worker.postMessage({ data }, buffers)
                        } catch (err) {
@@ -102,19 +106,27 @@ export class Vault {
                        return
                }
                if (this.#job == null) {
-                       throw new Error('Worker returned results but had nowhere to report it.')
+                       throw new Error('Vault worker returned results but had no job to report it.')
                }
-               const { resolve, reject } = this.#job
-               try {
-                       if (results?.error != null) {
-                               reject(results)
-                       } else {
-                               resolve(results)
+               const { url, id, resolve, reject } = this.#job
+               if (url == null) {
+                       throw new Error('Vault worker job missing URL')
+               }
+               if (id == null) {
+                       throw new Error('Vault worker job missing ID')
+               }
+               if (results?.url === url && results?.id === id) {
+                       try {
+                               if (results?.error != null) {
+                                       reject(results)
+                               } else {
+                                       resolve(results)
+                               }
+                       } catch (err) {
+                               reject(err)
+                       } finally {
+                               this.#process()
                        }
-               } catch (err) {
-                       reject(err)
-               } finally {
-                       this.#process()
                }
        }
 }
index e813cd61edf13d519dfb25fe8a9a288e84c7d161..57aa1f968ec53ebd834e93e177c2524432cb6275 100644 (file)
@@ -2,6 +2,7 @@
 //! SPDX-License-Identifier: GPL-3.0-or-later
 
 import * as nano25519 from 'nano25519'
+import { parentPort } from 'node:worker_threads'
 import { BIP44_COIN_NANO } from '../constants'
 import { Bip39, Bip44, Blake2b, WalletAesGcm } from '../crypto'
 import { WalletType } from '../wallet'
@@ -9,634 +10,617 @@ import { Passkey } from './passkey'
 import { VaultTimer } from './vault-timer'
 
 /**
-* Cross-platform worker for managing wallet secrets.
-*/
-export class VaultWorker {
-       #encoder: TextEncoder = new TextEncoder()
-       #locked: boolean
-       #timeout: number
-       #timer: VaultTimer
-       #type?: 'BIP-44' | 'BLAKE2b' | 'Exodus'
-       #seed?: ArrayBuffer
-       #mnemonic?: ArrayBuffer
-       #parentPort?: any
-
-       constructor () {
-               this.#locked = true
-               this.#timeout = 120_000
-               this.#timer = new VaultTimer(() => { }, 0)
-               this.#type = undefined
-               this.#seed = undefined
-               this.#mnemonic = undefined
-
-               const listener = (event: MessageEvent<any>): void => {
-                       NODE: if (this.#parentPort == null) setTimeout(() => listener(event), 0)
-                       const data = this.#parseData(event.data)
-                       const action = this.#parseAction(data)
-                       const keySalt = this.#parseKeySalt(action, data)
-                       Passkey.create(action, keySalt, data)
-                               .then((key: CryptoKey | undefined): Promise<Record<string, boolean | number | ArrayBuffer> | void> => {
-                                       const type = this.#parseType(action, data)
-                                       const iv = this.#parseIv(action, data)
-                                       const { seed, mnemonicPhrase, mnemonicSalt, index, encrypted, message, timeout } = this.#extractData(action, data)
-                                       switch (action) {
-                                               case 'STOP': {
-                                                       BROWSER: close()
-                                                       NODE: process.exit()
-                                               }
-                                               case 'config': {
-                                                       return this.config(timeout)
-                                               }
-                                               case 'create': {
-                                                       return this.create(type, key, keySalt, mnemonicSalt)
-                                               }
-                                               case 'derive': {
-                                                       return this.derive(index)
-                                               }
-                                               case 'load': {
-                                                       return this.load(type, key, keySalt, mnemonicPhrase ?? seed, mnemonicSalt)
-                                               }
-                                               case 'lock': {
-                                                       return this.lock()
-                                               }
-                                               case 'sign': {
-                                                       return this.sign(index, message)
-                                               }
-                                               case 'unlock': {
-                                                       return this.unlock(type, key, iv, encrypted)
-                                               }
-                                               case 'update': {
-                                                       return this.update(key, keySalt)
-                                               }
-                                               case 'verify': {
-                                                       return Promise.resolve(this.verify(seed, mnemonicPhrase))
-                                               }
-                                               default: {
-                                                       throw new Error(`Unknown wallet action '${action}'`)
-                                               }
-                                       }
-                               })
-                               .then((result: Record<string, boolean | number | ArrayBuffer> | void) => {
-                                       const transfer = []
-                                       if (result) {
-                                               for (const r of Object.values(result)) {
-                                                       if (r instanceof ArrayBuffer) {
-                                                               transfer.push(r)
-                                                       }
-                                               }
-                                       }
-                                       //@ts-expect-error
-                                       BROWSER: postMessage(result, transfer)
-                                       NODE: this.#parentPort?.postMessage(result, transfer)
-                               })
-                               .catch((err: any) => {
-                                       console.error(err)
-                                       for (let data of Object.values(event.data)) {
-                                               if (data instanceof ArrayBuffer && !data.detached) {
-                                                       new Uint8Array(data).fill(0).buffer.transfer?.()
-                                               }
-                                               data = undefined
-                                       }
-                                       BROWSER: postMessage({ error: 'Failed to process Vault request', cause: err })
-                                       NODE: this.#parentPort?.postMessage({ error: 'Failed to process Vault request', cause: err })
-                               })
-               }
-               BROWSER: addEventListener('message', listener)
-               NODE: {
-                       if (this.#parentPort == null) {
-                               import('node:worker_threads')
-                                       .then(({ parentPort }) => {
-                                               this.#parentPort = parentPort
-                                               this.#parentPort.on('message', listener)
-                                       })
-                       }
-               }
-       }
+ * Cross-platform worker for managing wallet secrets.
+ */
+const _encoder: TextEncoder = new TextEncoder()
+let _locked: boolean = true
+let _timeout: number = 120_000
+let _timer: VaultTimer = new VaultTimer(() => { }, 0)
+let _type: 'BIP-44' | 'BLAKE2b' | 'Exodus' | undefined = undefined
+let _seed: ArrayBuffer | undefined = undefined
+let _mnemonic: ArrayBuffer | undefined = undefined
 
-       /**
-       * Configures vault settings. The wallet must be unlocked prior to
-       * configuration.
-       */
-       config (timeout?: number): Promise<void> {
-               try {
-                       this.#timer?.pause()
-                       if (this.#locked) {
-                               throw new Error('Wallet is locked')
-                       }
-                       if (typeof timeout === 'number') {
-                               if (timeout < 10) {
-                                       throw new RangeError('Timeout must be at least 10 seconds')
+const listener = (event: MessageEvent<any>): void => {
+       const { url, id } = event.data
+       if (url !== location.href) return
+       NODE: if (parentPort == null) setTimeout(() => listener(event), 0)
+       const data = _parseData(event.data)
+       const action = _parseAction(data)
+       const keySalt = _parseKeySalt(action, data)
+       Passkey.create(action, keySalt, data)
+               .then((key: CryptoKey | undefined): Promise<Record<string, boolean | number | ArrayBuffer> | void> => {
+                       const type = _parseType(action, data)
+                       const iv = _parseIv(action, data)
+                       const { seed, mnemonicPhrase, mnemonicSalt, index, encrypted, message, timeout } = _extractData(action, data)
+                       switch (action) {
+                               case 'STOP': {
+                                       BROWSER: close()
+                                       NODE: process.exit()
+                               }
+                               case 'config': {
+                                       return config(timeout)
+                               }
+                               case 'create': {
+                                       return create(type, key, keySalt, mnemonicSalt)
+                               }
+                               case 'derive': {
+                                       return derive(index)
+                               }
+                               case 'load': {
+                                       return load(type, key, keySalt, mnemonicPhrase ?? seed, mnemonicSalt)
+                               }
+                               case 'lock': {
+                                       return lock()
                                }
-                               if (timeout > 600) {
-                                       throw new RangeError('Timeout must be at most 10 minutes')
+                               case 'sign': {
+                                       return sign(index, message)
+                               }
+                               case 'unlock': {
+                                       return unlock(type, key, iv, encrypted)
+                               }
+                               case 'update': {
+                                       return update(key, keySalt)
+                               }
+                               case 'verify': {
+                                       return Promise.resolve(verify(seed, mnemonicPhrase))
+                               }
+                               default: {
+                                       throw new Error(`Unknown wallet action '${action}'`)
                                }
-                               this.#timeout = timeout * 1000
-                               this.#timer = new VaultTimer(() => this.lock(), this.#timeout)
                        }
-                       return Promise.resolve()
-               } catch (err) {
-                       console.error(err)
-                       this.#timer?.resume()
-                       throw new Error('Failed to configure Vault', { cause: err })
-               }
-       }
-
-       /**
-       * Generates a new mnemonic and seed and then returns the initialization vector
-       * vector, salt, and encrypted data representing the wallet in a locked state.
-       */
-       create (type?: WalletType, key?: CryptoKey, keySalt?: ArrayBuffer, mnemonicSalt?: string): Promise<Record<string, ArrayBuffer>> {
-               if (type !== 'BIP-44' && type !== 'BLAKE2b') {
-                       throw new TypeError('Unsupported software wallet algorithm', { cause: type })
-               }
-               try {
-                       const entropy = crypto.getRandomValues(new Uint8Array(32))
-                       return Bip39.fromEntropy(entropy)
-                               .then(bip39 => this.#load(type, key, keySalt, bip39.phrase, mnemonicSalt))
-                               .then(({ iv, salt, encrypted }) => {
-                                       entropy.fill(0)
-                                       if (this.#seed == null || this.#mnemonic == null) {
-                                               throw new Error('Failed to generate seed and mnemonic')
+               })
+               .then((result: Record<string, boolean | number | ArrayBuffer> | void) => {
+                       result ??= {}
+                       const transfer: ArrayBuffer[] = []
+                       if (result) {
+                               for (const r of Object.values(result)) {
+                                       if (r instanceof ArrayBuffer) {
+                                               // transfer.push(r)
                                        }
-                                       return { iv, salt, encrypted, seed: this.#seed.slice(), mnemonic: this.#mnemonic.slice() }
-                               })
-               } catch (err) {
-                       console.error(err)
-                       throw new Error('Failed to create wallet', { cause: err })
-               } finally {
-                       this.lock()
-               }
-       }
-
-       /**
-       * Derives the private and public keys of a child account from the current
-       * wallet seed at a specified index and then returns the public key. The wallet
-       * must be unlocked prior to derivation.
-       */
-       derive (index?: number): Promise<Record<string, number | ArrayBuffer>> {
-               try {
-                       this.#timer.pause()
-                       if (this.#locked) {
-                               throw new Error('Wallet is locked')
+                               }
                        }
-                       if (this.#seed == null) {
-                               throw new Error('Wallet seed not found')
+                       result.url = url
+                       result.id = id
+                       //@ts-expect-error
+                       BROWSER: postMessage(result, transfer)
+                       NODE: parentPort?.postMessage(result, transfer)
+               })
+               .catch((err: any) => {
+                       for (let data of Object.values(event.data)) {
+                               if (data instanceof ArrayBuffer && !data.detached) {
+                                       new Uint8Array(data).fill(0).buffer.transfer?.()
+                               }
+                               data = undefined
                        }
-                       if (this.#type !== 'BIP-44' && this.#type !== 'BLAKE2b' && this.#type !== 'Exodus') {
-                               throw new Error('Invalid wallet type')
+                       BROWSER: postMessage({ error: 'Failed to process Vault request', cause: err })
+                       NODE: parentPort?.postMessage({ error: 'Failed to process Vault request', cause: err })
+               })
+}
+BROWSER: addEventListener('message', listener)
+NODE: parentPort?.on('message', listener)
+
+/**
+ * Configures vault settings. The wallet must be unlocked prior to
+ * configuration.
+ */
+async function config (timeout?: number): Promise<void> {
+       try {
+               _timer?.pause()
+               if (_locked) {
+                       throw new Error('Wallet is locked')
+               }
+               if (typeof timeout === 'number') {
+                       if (timeout < 10) {
+                               throw new RangeError('Timeout must be at least 10 seconds')
                        }
-                       if (typeof index !== 'number') {
-                               throw new Error('Invalid wallet account index')
+                       if (timeout > 600) {
+                               throw new RangeError('Timeout must be at most 10 minutes')
                        }
-                       const derive = this.#type === 'BIP-44'
-                               ? Bip44.ckd('ed25519 seed', this.#seed, BIP44_COIN_NANO, index)
-                               : this.#type === 'Exodus'
-                                       ? Bip44.ckd('Bitcoin seed', this.#seed, 0x100, index, 0, 0)
-                                       : Blake2b.ckd(this.#seed, index)
-                       return derive.then(result => {
-                               const prv = new Uint8Array(result)
-                               const pub = nano25519.derive(prv)
-                               this.#timer = new VaultTimer(() => this.lock(), this.#timeout)
-                               return { index, publicKey: pub.buffer }
-                       })
-               } catch (err) {
-                       console.error(err)
-                       this.#timer.resume()
-                       throw new Error('Failed to derive account', { cause: err })
+                       timeout = timeout * 1000
+                       _timer = new VaultTimer(() => lock(), timeout)
                }
+               return Promise.resolve()
+       } catch (err) {
+               console.error(err)
+               _timer?.resume()
+               throw new Error('Failed to configure Vault', { cause: err })
        }
+}
 
-       /**
-       * Encrypts an existing seed or mnemonic+salt and returns the initialization
-       * vector, salt, and encrypted data representing the wallet in a locked state.
-       */
-       load (type?: WalletType, key?: CryptoKey, keySalt?: ArrayBuffer, secret?: string | ArrayBuffer, mnemonicSalt?: string): Promise<Record<string, ArrayBuffer>> {
-               if (type !== 'BIP-44' && type !== 'BLAKE2b' && type !== 'Exodus') {
-                       throw new TypeError('Unsupported software wallet algorithm', { cause: type })
-               }
-               return this.#load(type, key, keySalt, secret, mnemonicSalt)
-                       .then(record => {
-                               if (this.#seed == null) {
-                                       throw new Error('Wallet seed not found')
+/**
+* Generates a new mnemonic and seed and then returns the initialization vector
+* vector, salt, and encrypted data representing the wallet in a locked state.
+*/
+async function create (type?: WalletType, key?: CryptoKey, keySalt?: ArrayBuffer, mnemonicSalt?: string): Promise<Record<string, ArrayBuffer>> {
+       if (type !== 'BIP-44' && type !== 'BLAKE2b') {
+               throw new TypeError('Unsupported software wallet algorithm', { cause: type })
+       }
+       try {
+               const entropy = crypto.getRandomValues(new Uint8Array(32))
+               return Bip39.fromEntropy(entropy)
+                       .then(bip39 => _load(type, key, keySalt, bip39.phrase, mnemonicSalt))
+                       .then(({ iv, salt, encrypted }) => {
+                               entropy.fill(0)
+                               if (_seed == null || _mnemonic == null) {
+                                       throw new Error('Failed to generate seed and mnemonic')
                                }
-                               return record
-                       })
-                       .catch(err => {
-                               console.error(err)
-                               throw new Error('Failed to load wallet', { cause: err })
+                               return { iv, salt, encrypted, seed: _seed.slice(), mnemonic: _mnemonic.slice() }
                        })
-                       .finally(() => this.lock())
+       } catch (err) {
+               console.error(err)
+               throw new Error('Failed to create wallet', { cause: err })
+       } finally {
+               lock()
        }
+}
 
-       lock (): Promise<void> {
-               this.#mnemonic = undefined
-               this.#seed = undefined
-               this.#locked = true
-               this.#timer?.pause()
-               BROWSER: postMessage('locked')
-               NODE: this.#parentPort?.postMessage('locked')
-               return Promise.resolve()
+/**
+* Derives the private and public keys of a child account from the current
+* wallet seed at a specified index and then returns the public key. The wallet
+* must be unlocked prior to derivation.
+*/
+async function derive (index?: number): Promise<Record<string, number | ArrayBuffer>> {
+       try {
+               _timer.pause()
+               if (_locked) {
+                       throw new Error('Wallet is locked')
+               }
+               if (_seed == null) {
+                       throw new Error('Wallet seed not found')
+               }
+               if (_type !== 'BIP-44' && _type !== 'BLAKE2b' && _type !== 'Exodus') {
+                       throw new Error('Invalid wallet type')
+               }
+               if (typeof index !== 'number') {
+                       throw new Error('Invalid wallet account index')
+               }
+               const derive = _type === 'BIP-44'
+                       ? Bip44.ckd('ed25519 seed', _seed, BIP44_COIN_NANO, index)
+                       : _type === 'Exodus'
+                               ? Bip44.ckd('Bitcoin seed', _seed, 0x100, index, 0, 0)
+                               : Blake2b.ckd(_seed, index)
+               return derive.then(result => {
+                       const prv = new Uint8Array(result)
+                       const pub = nano25519.derive(prv)
+                       _timer = new VaultTimer(() => lock(), _timeout)
+                       return { index, publicKey: pub.buffer }
+               })
+       } catch (err) {
+               console.error(err)
+               _timer.resume()
+               throw new Error('Failed to derive account', { cause: err })
        }
+}
 
-       /**
-       * Derives the account private key at a specified index, signs the input data,
-       * and returns a signature. The wallet must be unlocked prior to verification.
-       */
-       sign (index?: number, data?: ArrayBuffer): Promise<Record<string, ArrayBuffer>> {
-               try {
-                       this.#timer.pause()
-                       if (this.#locked) {
-                               throw new Error('Wallet is locked')
-                       }
-                       if (this.#seed == null) {
+/**
+* Encrypts an existing seed or mnemonic+salt and returns the initialization
+* vector, salt, and encrypted data representing the wallet in a locked state.
+*/
+async function load (type?: WalletType, key?: CryptoKey, keySalt?: ArrayBuffer, secret?: string | ArrayBuffer, mnemonicSalt?: string): Promise<Record<string, ArrayBuffer>> {
+       if (type !== 'BIP-44' && type !== 'BLAKE2b' && type !== 'Exodus') {
+               throw new TypeError('Unsupported software wallet algorithm', { cause: type })
+       }
+       return _load(type, key, keySalt, secret, mnemonicSalt)
+               .then(record => {
+                       if (_seed == null) {
                                throw new Error('Wallet seed not found')
                        }
-                       if (index == null) {
-                               throw new Error('Wallet account index is required to sign')
-                       }
-                       if (data == null) {
-                               throw new Error('Data to sign not found')
-                       }
-                       const derive = this.#type === 'BLAKE2b'
-                               ? Blake2b.ckd(this.#seed, index)
-                               : Bip44.ckd(this.#type === 'Exodus' ? 'Bitcoin seed' : 'ed25519 seed', this.#seed, BIP44_COIN_NANO, index)
-                       return derive.then(result => {
-                               const prv = new Uint8Array(result)
-                               const pub = nano25519.derive(prv)
-                               const sig = nano25519.sign(new Uint8Array(data), new Uint8Array([...prv, ...pub]))
-                               this.#timer = new VaultTimer(() => this.lock(), this.#timeout)
-                               return { signature: sig.buffer }
-                       })
-               } catch (err) {
+                       return record
+               })
+               .catch(err => {
                        console.error(err)
-                       this.#timer.resume()
-                       throw new Error('Failed to sign message', { cause: err })
+                       throw new Error('Failed to load wallet', { cause: err })
+               })
+               .finally(() => lock())
+}
+
+async function lock (): Promise<void> {
+       _mnemonic = undefined
+       _seed = undefined
+       _locked = true
+       _timer?.pause()
+       BROWSER: postMessage('locked')
+       NODE: parentPort?.postMessage('locked')
+       return Promise.resolve()
+}
+
+/**
+* Derives the account private key at a specified index, signs the input data,
+* and returns a signature. The wallet must be unlocked prior to verification.
+*/
+async function sign (index?: number, data?: ArrayBuffer): Promise<Record<string, ArrayBuffer>> {
+       try {
+               _timer.pause()
+               if (_locked) {
+                       throw new Error('Wallet is locked')
+               }
+               if (_seed == null) {
+                       throw new Error('Wallet seed not found')
+               }
+               if (index == null) {
+                       throw new Error('Wallet account index is required to sign')
+               }
+               if (data == null) {
+                       throw new Error('Data to sign not found')
                }
+               const derive = _type === 'BLAKE2b'
+                       ? Blake2b.ckd(_seed, index)
+                       : Bip44.ckd(_type === 'Exodus' ? 'Bitcoin seed' : 'ed25519 seed', _seed, BIP44_COIN_NANO, index)
+               return derive.then(result => {
+                       const prv = new Uint8Array(result)
+                       const pub = nano25519.derive(prv)
+                       const sig = nano25519.sign(new Uint8Array(data), new Uint8Array([...prv, ...pub]))
+                       _timer = new VaultTimer(() => lock(), _timeout)
+                       return { signature: sig.buffer }
+               })
+       } catch (err) {
+               console.error(err)
+               _timer.resume()
+               throw new Error('Failed to sign message', { cause: err })
        }
+}
 
-       /**
-       * Decrypts the input and sets the seed and, if it is included, the mnemonic.
-       */
-       unlock (type?: WalletType, key?: CryptoKey, iv?: ArrayBuffer, encrypted?: ArrayBuffer): Promise<void> {
-               if (type == null) {
-                       throw new TypeError('Wallet type is required')
-               }
-               if (type === 'Ledger') {
-                       this.#locked = false
+/**
+* Decrypts the input and sets the seed and, if it is included, the mnemonic.
+*/
+async function unlock (type?: WalletType, key?: CryptoKey, iv?: ArrayBuffer, encrypted?: ArrayBuffer): Promise<void> {
+       if (type == null) {
+               throw new TypeError('Wallet type is required')
+       }
+       if (type === 'Ledger') {
+               _locked = false
+               BROWSER: postMessage('unlocked')
+               NODE: parentPort?.postMessage('unlocked')
+               return Promise.resolve()
+       }
+       if (key == null) {
+               throw new TypeError('Wallet password is required')
+       }
+       if (iv == null) {
+               throw new TypeError('Wallet IV is required')
+       }
+       if (encrypted == null) {
+               throw new TypeError('Wallet encrypted data is required')
+       }
+       _timer?.pause()
+       return WalletAesGcm.decrypt(type, key, iv, encrypted)
+               .then(({ mnemonic, seed }) => {
+                       if (!(seed instanceof ArrayBuffer)) {
+                               throw new TypeError('Invalid seed')
+                       }
+                       if (mnemonic != null && !(mnemonic instanceof ArrayBuffer)) {
+                               throw new TypeError('Invalid mnemonic')
+                       }
+                       type = type
+                       seed = seed
+                       mnemonic = mnemonic
+                       _locked = false
+                       _timer = new VaultTimer(lock, _timeout)
                        BROWSER: postMessage('unlocked')
-                       NODE: this.#parentPort?.postMessage('unlocked')
-                       return Promise.resolve()
+                       NODE: parentPort?.postMessage('unlocked')
+               })
+               .catch(err => {
+                       console.error(err)
+                       _timer?.resume()
+                       throw new Error('Failed to unlock wallet', { cause: err })
+               })
+}
+
+/**
+* Re-encrypts the wallet with a new password.
+*/
+async function update (key?: CryptoKey, salt?: ArrayBuffer): Promise<Record<string, ArrayBuffer>> {
+       try {
+               _timer.pause()
+               if (_locked) {
+                       throw new Error('Wallet is locked')
                }
-               if (key == null) {
-                       throw new TypeError('Wallet password is required')
+               if (_seed == null) {
+                       throw new Error('Wallet seed not found')
                }
-               if (iv == null) {
-                       throw new TypeError('Wallet IV is required')
+               if (_type == null) {
+                       throw new Error('Wallet type not found')
                }
-               if (encrypted == null) {
-                       throw new TypeError('Wallet encrypted data is required')
+               if (key == null || salt == null) {
+                       throw new TypeError('Wallet password is required')
                }
-               this.#timer?.pause()
-               return WalletAesGcm.decrypt(type, key, iv, encrypted)
-                       .then(({ mnemonic, seed }) => {
-                               if (!(seed instanceof ArrayBuffer)) {
-                                       throw new TypeError('Invalid seed')
-                               }
-                               if (mnemonic != null && !(mnemonic instanceof ArrayBuffer)) {
-                                       throw new TypeError('Invalid mnemonic')
-                               }
-                               this.#type = type
-                               this.#seed = seed
-                               this.#mnemonic = mnemonic
-                               this.#locked = false
-                               this.#timer = new VaultTimer(this.lock.bind(this), this.#timeout)
-                               BROWSER: postMessage('unlocked')
-                               NODE: this.#parentPort?.postMessage('unlocked')
-                       })
-                       .catch(err => {
-                               console.error(err)
-                               this.#timer?.resume()
-                               throw new Error('Failed to unlock wallet', { cause: err })
+               return WalletAesGcm.encrypt(_type, key, _seed, _mnemonic)
+                       .then(({ iv, encrypted }) => {
+                               _timer = new VaultTimer(() => lock(), _timeout)
+                               return { iv, salt, encrypted }
                        })
+       } catch (err) {
+               console.error(err)
+               _timer.resume()
+               throw new Error('Failed to update wallet password', { cause: err })
        }
+}
 
-       /**
-       * Re-encrypts the wallet with a new password.
-       */
-       update (key?: CryptoKey, salt?: ArrayBuffer): Promise<Record<string, ArrayBuffer>> {
-               try {
-                       this.#timer.pause()
-                       if (this.#locked) {
-                               throw new Error('Wallet is locked')
-                       }
-                       if (this.#seed == null) {
-                               throw new Error('Wallet seed not found')
-                       }
-                       if (this.#type == null) {
-                               throw new Error('Wallet type not found')
-                       }
-                       if (key == null || salt == null) {
-                               throw new TypeError('Wallet password is required')
-                       }
-                       return WalletAesGcm.encrypt(this.#type, key, this.#seed, this.#mnemonic)
-                               .then(({ iv, encrypted }) => {
-                                       this.#timer = new VaultTimer(() => this.lock(), this.#timeout)
-                                       return { iv, salt, encrypted }
-                               })
-               } catch (err) {
-                       console.error(err)
-                       this.#timer.resume()
-                       throw new Error('Failed to update wallet password', { cause: err })
+/**
+* Checks the seed and, if it exists, the mnemonic against input. The wallet
+* must be unlocked prior to verification.
+*/
+function verify (seed?: ArrayBuffer, mnemonicPhrase?: string): Record<string, boolean> {
+       try {
+               if (_locked) {
+                       throw new Error('Wallet is locked')
                }
-       }
-
-       /**
-       * Checks the seed and, if it exists, the mnemonic against input. The wallet
-       * must be unlocked prior to verification.
-       */
-       verify (seed?: ArrayBuffer, mnemonicPhrase?: string): Record<string, boolean> {
-               try {
-                       if (this.#locked) {
-                               throw new Error('Wallet is locked')
-                       }
-                       if (this.#seed == null) {
-                               throw new Error('Wallet seed not found')
-                       }
-                       if (seed == null && mnemonicPhrase == null) {
-                               throw new Error('Seed or mnemonic phrase is required')
-                       }
-                       if (seed != null && mnemonicPhrase != null) {
-                               throw new Error('Seed or mnemonic phrase must be verified separately')
-                       }
-                       let isVerified = false
-                       if (seed != null) {
-                               let diff = 0
-                               const userSeed = new Uint8Array(seed)
-                               const thisSeed = new Uint8Array(this.#seed)
-                               for (let i = 0; i < userSeed.byteLength; i++) {
-                                       diff |= userSeed[i] ^ thisSeed[i]
-                               }
-                               isVerified = diff === 0
-                       }
-                       if (mnemonicPhrase != null) {
-                               console.log(new TextDecoder().decode((new Uint8Array(this.#mnemonic ?? []))))
-                               let diff = 0
-                               const userMnemonic = this.#encoder.encode(mnemonicPhrase)
-                               const thisMnemonic = new Uint8Array(this.#mnemonic ?? [])
-                               for (let i = 0; i < userMnemonic.byteLength; i++) {
-                                       diff |= userMnemonic[i] ^ thisMnemonic[i]
-                               }
-                               isVerified = diff === 0
-                       }
-                       return { isVerified }
-               } catch (err) {
-                       console.error(err)
-                       throw new Error('Failed to verify wallet', { cause: err })
+               if (seed == null) {
+                       throw new Error('Wallet seed not found')
                }
+               if (seed == null && mnemonicPhrase == null) {
+                       throw new Error('Seed or mnemonic phrase is required')
+               }
+               if (seed != null && mnemonicPhrase != null) {
+                       throw new Error('Seed or mnemonic phrase must be verified separately')
+               }
+               let isVerified = false
+               if (seed != null) {
+                       let diff = 0
+                       const userSeed = new Uint8Array(seed)
+                       const thisSeed = new Uint8Array(seed)
+                       for (let i = 0; i < userSeed.byteLength; i++) {
+                               diff |= userSeed[i] ^ thisSeed[i]
+                       }
+                       isVerified = diff === 0
+               }
+               if (mnemonicPhrase != null) {
+                       console.log(new TextDecoder().decode((new Uint8Array(_mnemonic ?? []))))
+                       let diff = 0
+                       const userMnemonic = _encoder.encode(mnemonicPhrase)
+                       const thisMnemonic = new Uint8Array(_mnemonic ?? [])
+                       for (let i = 0; i < userMnemonic.byteLength; i++) {
+                               diff |= userMnemonic[i] ^ thisMnemonic[i]
+                       }
+                       isVerified = diff === 0
+               }
+               return { isVerified }
+       } catch (err) {
+               console.error(err)
+               throw new Error('Failed to verify wallet', { cause: err })
        }
+}
 
-       /**
-       * Parse inbound message from main thread into typechecked variables.
-       */
-       #extractData (action: string, data: { [key: string]: unknown }) {
-               try {
-                       // Import requires seed or mnemonic phrase
-                       if (action === 'load' && data.seed == null && data.mnemonicPhrase == null) {
-                               throw new TypeError('Seed or mnemonic phrase required to load wallet')
-                       }
+/**
+* Parse inbound message from main thread into typechecked variables.
+*/
+function _extractData (action: string, data: { [key: string]: unknown }) {
+       try {
+               // Import requires seed or mnemonic phrase
+               if (action === 'load' && data.seed == null && data.mnemonicPhrase == null) {
+                       throw new TypeError('Seed or mnemonic phrase required to load wallet')
+               }
 
-                       // Seed to load
-                       if (action === 'load' && 'seed' in data && !(data.seed instanceof ArrayBuffer)) {
-                               throw new TypeError('Seed required to load wallet')
-                       }
-                       const seed = data.seed instanceof ArrayBuffer
-                               ? data.seed.slice()
-                               : undefined
-                       if (data.seed instanceof ArrayBuffer) {
-                               new Uint8Array(data.seed).fill(0)
-                               delete data.seed
-                       }
+               // Seed to load
+               if (action === 'load' && 'seed' in data && !(data.seed instanceof ArrayBuffer)) {
+                       throw new TypeError('Seed required to load wallet')
+               }
+               const seed = data.seed instanceof ArrayBuffer
+                       ? data.seed.slice()
+                       : undefined
+               if (data.seed instanceof ArrayBuffer) {
+                       new Uint8Array(data.seed).fill(0)
+                       delete data.seed
+               }
 
-                       // Mnemonic phrase to load
-                       if (action === 'load' && 'mnemonicPhrase' in data && typeof data.mnemonicPhrase !== 'string') {
-                               throw new TypeError('Invalid mnemonic phrase')
-                       }
-                       const mnemonicPhrase = typeof data.mnemonicPhrase === 'string'
-                               ? data.mnemonicPhrase
-                               : undefined
-                       delete data.mnemonicPhrase
+               // Mnemonic phrase to load
+               if (action === 'load' && 'mnemonicPhrase' in data && typeof data.mnemonicPhrase !== 'string') {
+                       throw new TypeError('Invalid mnemonic phrase')
+               }
+               const mnemonicPhrase = typeof data.mnemonicPhrase === 'string'
+                       ? data.mnemonicPhrase
+                       : undefined
+               delete data.mnemonicPhrase
 
-                       // Mnemonic salt for mnemonic phrase to load
-                       if (action === 'load' && data.mnemonicSalt != undefined && typeof data.mnemonicSalt !== 'string') {
-                               throw new TypeError('Invalid mnemonic salt for mnemonic phrase')
-                       }
-                       const mnemonicSalt = typeof data.mnemonicSalt === 'string'
-                               ? data.mnemonicSalt
-                               : undefined
-                       delete data.mnemonicSalt
+               // Mnemonic salt for mnemonic phrase to load
+               if (action === 'load' && data.mnemonicSalt != undefined && typeof data.mnemonicSalt !== 'string') {
+                       throw new TypeError('Invalid mnemonic salt for mnemonic phrase')
+               }
+               const mnemonicSalt = typeof data.mnemonicSalt === 'string'
+                       ? data.mnemonicSalt
+                       : undefined
+               delete data.mnemonicSalt
 
-                       // Encrypted seed and possibly mnemonic
-                       if (action === 'unlock') {
-                               if (data.encrypted == null) {
-                                       throw new TypeError('Wallet encrypted secrets not found')
-                               }
-                               if (!(data.encrypted instanceof ArrayBuffer)) {
-                                       throw new TypeError('Invalid wallet encrypted secrets')
-                               }
+               // Encrypted seed and possibly mnemonic
+               if (action === 'unlock') {
+                       if (data.encrypted == null) {
+                               throw new TypeError('Wallet encrypted secrets not found')
                        }
-                       const encrypted = data.encrypted instanceof ArrayBuffer
-                               ? data.encrypted.slice()
-                               : undefined
-                       if (data.encrypted instanceof ArrayBuffer) {
-                               new Uint8Array(data.encrypted).fill(0)
-                               delete data.encrypted
+                       if (!(data.encrypted instanceof ArrayBuffer)) {
+                               throw new TypeError('Invalid wallet encrypted secrets')
                        }
+               }
+               const encrypted = data.encrypted instanceof ArrayBuffer
+                       ? data.encrypted.slice()
+                       : undefined
+               if (data.encrypted instanceof ArrayBuffer) {
+                       new Uint8Array(data.encrypted).fill(0)
+                       delete data.encrypted
+               }
 
-                       // Index for child account to derive or sign
-                       if ((action === 'derive' || action === 'sign') && typeof data.index !== 'number') {
-                               throw new TypeError('Index is required to derive an account private key')
-                       }
-                       const index = typeof data.index === 'number'
-                               ? data.index
-                               : undefined
+               // Index for child account to derive or sign
+               if ((action === 'derive' || action === 'sign') && typeof data.index !== 'number') {
+                       throw new TypeError('Index is required to derive an account private key')
+               }
+               const index = typeof data.index === 'number'
+                       ? data.index
+                       : undefined
 
-                       // Data to sign
-                       if (action === 'sign') {
-                               if (data.message == null) {
-                                       throw new TypeError('Data to sign not found')
-                               }
-                               if (!(data.message instanceof ArrayBuffer)) {
-                                       throw new TypeError('Invalid data to sign')
-                               }
+               // Data to sign
+               if (action === 'sign') {
+                       if (data.message == null) {
+                               throw new TypeError('Data to sign not found')
                        }
-                       const message = data.message instanceof ArrayBuffer
-                               ? data.message
-                               : undefined
-                       delete data.message
-
-                       // Vault configuration
-                       if (action === 'config') {
-                               if (data.timeout == null) {
-                                       throw new TypeError('Configuration not found')
-                               }
-                               if (typeof data.timeout !== 'number') {
-                                       throw new TypeError('Invalid timeout configuration')
-                               }
+                       if (!(data.message instanceof ArrayBuffer)) {
+                               throw new TypeError('Invalid data to sign')
                        }
-                       const timeout = typeof data.timeout === 'number'
-                               ? data.timeout
-                               : undefined
-
-                       return { seed, mnemonicPhrase, mnemonicSalt, encrypted, index, message, timeout }
-               } catch (err) {
-                       console.error(err)
-                       throw new Error('Failed to extract data', { cause: err })
                }
-       }
+               const message = data.message instanceof ArrayBuffer
+                       ? data.message
+                       : undefined
+               delete data.message
 
-       /**
-       * Encrypts an existing seed or mnemonic+salt and returns the initialization
-       * vector, salt, and encrypted data representing the wallet in a locked state.
-       */
-       #load (type?: 'BIP-44' | 'BLAKE2b' | 'Exodus', key?: CryptoKey, keySalt?: ArrayBuffer, secret?: string | ArrayBuffer, mnemonicSalt?: string): Promise<Record<string, ArrayBuffer>> {
-               try {
-                       if (!this.#locked) {
-                               throw new Error('Wallet is in use')
-                       }
-                       if (key == null || keySalt == null) {
-                               throw new Error('Wallet password is required')
+               // Vault configuration
+               if (action === 'config') {
+                       if (data.timeout == null) {
+                               throw new TypeError('Configuration not found')
                        }
-                       if (type == null) {
-                               throw new TypeError('Wallet type is required')
+                       if (typeof data.timeout !== 'number') {
+                               throw new TypeError('Invalid timeout configuration')
                        }
-                       if (type !== 'BIP-44' && type !== 'BLAKE2b' && type !== 'Exodus') {
-                               throw new TypeError('Invalid wallet type')
+               }
+               const timeout = typeof data.timeout === 'number'
+                       ? data.timeout
+                       : undefined
+
+               return { seed, mnemonicPhrase, mnemonicSalt, encrypted, index, message, timeout }
+       } catch (err) {
+               console.error(err)
+               throw new Error('Failed to extract data', { cause: err })
+       }
+}
+
+/**
+* Encrypts an existing seed or mnemonic+salt and returns the initialization
+* vector, salt, and encrypted data representing the wallet in a locked state.
+*/
+function _load (type?: 'BIP-44' | 'BLAKE2b' | 'Exodus', key?: CryptoKey, keySalt?: ArrayBuffer, secret?: string | ArrayBuffer, mnemonicSalt?: string): Promise<Record<string, ArrayBuffer>> {
+       try {
+               if (!_locked) {
+                       throw new Error('Wallet is in use')
+               }
+               if (key == null || keySalt == null) {
+                       throw new Error('Wallet password is required')
+               }
+               if (type == null) {
+                       throw new TypeError('Wallet type is required')
+               }
+               if (type !== 'BIP-44' && type !== 'BLAKE2b' && type !== 'Exodus') {
+                       throw new TypeError('Invalid wallet type')
+               }
+               if (secret == null) {
+                       throw new TypeError('Seed or mnemonic is required')
+               }
+               if (typeof secret !== 'string' && mnemonicSalt !== undefined) {
+                       throw new TypeError('Mnemonic must be a string')
+               }
+               if (type === 'BIP-44') {
+                       if (secret instanceof ArrayBuffer && (secret.byteLength < 16 || secret.byteLength > 64)) {
+                               throw new RangeError('Seed for BIP-44 wallet must be 16-64 bytes')
                        }
-                       if (secret == null) {
-                               throw new TypeError('Seed or mnemonic is required')
+               }
+               if (type === 'BLAKE2b') {
+                       if (secret instanceof ArrayBuffer && secret.byteLength !== 32) {
+                               throw new RangeError('Seed for BLAKE2b wallet must be 32 bytes')
                        }
-                       if (typeof secret !== 'string' && mnemonicSalt !== undefined) {
-                               throw new TypeError('Mnemonic must be a string')
+               }
+               if (type === 'Exodus') {
+                       if (typeof secret === 'string' && secret.split(' ').length !== 12) {
+                               throw new RangeError('Mnemonic for Exodus wallet must be 12 words')
                        }
-                       if (type === 'BIP-44') {
-                               if (secret instanceof ArrayBuffer && (secret.byteLength < 16 || secret.byteLength > 64)) {
-                                       throw new RangeError('Seed for BIP-44 wallet must be 16-64 bytes')
-                               }
+                       if (secret instanceof ArrayBuffer && secret.byteLength !== 64) {
+                               throw new RangeError('Seed for Exodus wallet must be 64 bytes')
                        }
+               }
+               _type = type
+               let seed: Promise<ArrayBuffer>
+               if (secret instanceof ArrayBuffer) {
                        if (type === 'BLAKE2b') {
-                               if (secret instanceof ArrayBuffer && secret.byteLength !== 32) {
-                                       throw new RangeError('Seed for BLAKE2b wallet must be 32 bytes')
-                               }
-                       }
-                       if (type === 'Exodus') {
-                               if (typeof secret === 'string' && secret.split(' ').length !== 12) {
-                                       throw new RangeError('Mnemonic for Exodus wallet must be 12 words')
-                               }
-                               if (secret instanceof ArrayBuffer && secret.byteLength !== 64) {
-                                       throw new RangeError('Seed for Exodus wallet must be 64 bytes')
-                               }
-                       }
-                       this.#type = type
-                       let seed: Promise<ArrayBuffer>
-                       if (secret instanceof ArrayBuffer) {
-                               if (type === 'BLAKE2b') {
-                                       seed = Bip39.fromEntropy(new Uint8Array(secret))
-                                               .then(bip39 => {
-                                                       this.#mnemonic = new Uint8Array(this.#encoder.encode(bip39.phrase ?? '')).buffer
-                                                       return secret
-                                               })
-                               } else {
-                                       seed = Promise.resolve(secret)
-                               }
-                       } else {
-                               seed = Bip39.fromPhrase(secret)
+                               seed = Bip39.fromEntropy(new Uint8Array(secret))
                                        .then(bip39 => {
-                                               this.#mnemonic = new Uint8Array(this.#encoder.encode(bip39.phrase ?? '')).buffer
-                                               const derive = type === 'BLAKE2b'
-                                                       ? Promise.resolve(bip39.toBlake2bSeed())
-                                                       : bip39.toBip39Seed(mnemonicSalt ?? '')
-                                               return derive.then(s => s.buffer)
+                                               _mnemonic = new Uint8Array(_encoder.encode(bip39.phrase ?? '')).buffer
+                                               return secret
                                        })
+                       } else {
+                               seed = Promise.resolve(secret)
                        }
-                       return seed.then(seed => {
-                               this.#seed = seed
-                               return WalletAesGcm.encrypt(type, key, this.#seed, this.#mnemonic)
-                                       .then(({ iv, encrypted }) => ({ iv, salt: keySalt, encrypted }))
-                       })
-               } catch (err) {
-                       throw new Error('Failed to load wallet', { cause: err })
+               } else {
+                       seed = Bip39.fromPhrase(secret)
+                               .then(bip39 => {
+                                       _mnemonic = new Uint8Array(_encoder.encode(bip39.phrase ?? '')).buffer
+                                       const derive = type === 'BLAKE2b'
+                                               ? Promise.resolve(bip39.toBlake2bSeed())
+                                               : bip39.toBip39Seed(mnemonicSalt ?? '')
+                                       return derive.then(s => s.buffer)
+                               })
                }
+               return seed.then(seed => {
+                       _seed = seed
+                       return WalletAesGcm.encrypt(type, key, _seed, _mnemonic)
+                               .then(({ iv, encrypted }) => ({ iv, salt: keySalt, encrypted }))
+               })
+       } catch (err) {
+               throw new Error('Failed to load wallet', { cause: err })
        }
+}
 
-       // Action for selecting method execution
-       #parseAction (data: { [key: string]: unknown }) {
-               if (data.action == null) {
-                       throw new TypeError('Wallet action is required')
-               }
-               if (data.action !== 'STOP'
-                       && data.action !== 'config'
-                       && data.action !== 'create'
-                       && data.action !== 'derive'
-                       && data.action !== 'load'
-                       && data.action !== 'lock'
-                       && data.action !== 'sign'
-                       && data.action !== 'unlock'
-                       && data.action !== 'update'
-                       && data.action !== 'verify') {
-                       throw new TypeError('Invalid wallet action')
-               }
-               return data.action
+// Action for selecting method execution
+function _parseAction (data: { [key: string]: unknown }) {
+       if (data.action == null) {
+               throw new TypeError('Wallet action is required')
        }
+       if (data.action !== 'STOP'
+               && data.action !== 'config'
+               && data.action !== 'create'
+               && data.action !== 'derive'
+               && data.action !== 'load'
+               && data.action !== 'lock'
+               && data.action !== 'sign'
+               && data.action !== 'unlock'
+               && data.action !== 'update'
+               && data.action !== 'verify') {
+               throw new TypeError('Invalid wallet action')
+       }
+       return data.action
+}
 
-       // Worker message data itself
-       #parseData (data: unknown) {
-               if (data == null) {
-                       throw new TypeError('Worker received no data')
-               }
-               if (typeof data !== 'object') {
-                       throw new Error('Invalid data')
-               }
-               return data as { [key: string]: unknown }
+// Worker message data itself
+function _parseData (data: unknown): Record<string, unknown> {
+       if (data == null) {
+               throw new TypeError('Worker received no data')
+       }
+       if (typeof data !== 'object') {
+               throw new Error('Invalid data')
        }
+       return data as Record<string, unknown>
+}
 
-       // Salt created to derive CryptoKey from password; subsequently required to
-       // derive the same key for unlock requests
-       #parseKeySalt (action: string, data: { [key: string]: unknown }): ArrayBuffer {
-               if (action === 'unlock') {
-                       if (data.keySalt instanceof ArrayBuffer) {
-                               return data.keySalt
-                       } else {
-                               throw new TypeError('Key salt required to unlock wallet')
-                       }
+// Salt created to derive CryptoKey from password; subsequently required to
+// derive the same key for unlock requests
+function _parseKeySalt (action: string, data: { [key: string]: unknown }): ArrayBuffer {
+       if (action === 'unlock') {
+               if (data.keySalt instanceof ArrayBuffer) {
+                       return data.keySalt
                } else {
-                       return crypto.getRandomValues(new Uint8Array(32)).buffer
+                       throw new TypeError('Key salt required to unlock wallet')
                }
+       } else {
+               return crypto.getRandomValues(new Uint8Array(32)).buffer
        }
+}
 
-       // Initialization vector created to encrypt and lock the vault; subsequently
-       // required to decrypt and unlock the vault
-       #parseIv (action: string, data: { [key: string]: unknown }) {
-               if (action === 'unlock') {
-                       if (!(data.iv instanceof ArrayBuffer)) {
-                               throw new TypeError('Initialization vector required to unlock wallet')
-                       }
-               } else if (data.iv !== undefined) {
-                       throw new Error('IV is not allowed for this action', { cause: action })
+// Initialization vector created to encrypt and lock the vault; subsequently
+// required to decrypt and unlock the vault
+function _parseIv (action: string, data: { [key: string]: unknown }) {
+       if (action === 'unlock') {
+               if (!(data.iv instanceof ArrayBuffer)) {
+                       throw new TypeError('Initialization vector required to unlock wallet')
                }
-               return data.iv
+       } else if (data.iv !== undefined) {
+               throw new Error('IV is not allowed for this action', { cause: action })
        }
+       return data.iv
+}
 
-       // Algorithm used for wallet functions
-       #parseType (action: string, data: { [key: string]: unknown }) {
-               if (['create', 'load', 'unlock'].includes(action)) {
-                       if (data.type !== 'BIP-44' && data.type !== 'BLAKE2b' && data.type !== 'Exodus' && data.type !== 'Ledger') {
-                               throw new TypeError(`Type is required to ${action} wallet`)
-                       }
-               } else if (data.type !== undefined) {
-                       throw new Error('Type is not allowed for this action', { cause: action })
+// Algorithm used for wallet functions
+function _parseType (action: string, data: { [key: string]: unknown }) {
+       if (['create', 'load', 'unlock'].includes(action)) {
+               if (data.type !== 'BIP-44' && data.type !== 'BLAKE2b' && data.type !== 'Exodus' && data.type !== 'Ledger') {
+                       throw new TypeError(`Type is required to ${action} wallet`)
                }
-               return data.type
+       } else if (data.type !== undefined) {
+               throw new Error('Type is not allowed for this action', { cause: action })
        }
+       return data.type
 }
 
-const v = new VaultWorker()