npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@qbtc/vault

v1.1.0

Published

Password-protected BIP-39 seed storage with locking, lifecycle events and encrypted application data. Storage and application-specific parameters are supplied by the consumer.

Readme

@qbtc/vault

Password-protected BIP-39 seed storage for wallets. Owns the in-memory seed, lock state, password changes and lifecycle events. The application supplies storage and its frozen application-data encryption label.

Uses @qbtc/crypto for Argon2id, AES-GCM, BIP-39/32 and application-data encryption. No filesystem, browser-extension, Electron or chain-client dependencies. Ships compiled ESM, TypeScript declarations and sources.

Install

pnpm add @qbtc/vault @qbtc/crypto

@qbtc/crypto is a peer dependency (^1.1.0): the vault uses the copy your application already has, so the HDKey it returns and the crypto errors it propagates are the same classes you import yourself. Pin both packages to exact versions.

Node 22+ or a runtime with Web Crypto, TextEncoder and TextDecoder is required. In a browser, use a secure context and bundle the package.

Usage

import { Vault, type VaultStorage } from '@qbtc/vault';

// Implement these operations using your application's persistence layer.
// Writes must be atomic: a rejected write must preserve the previous blob.
declare const storage: VaultStorage;
declare const mnemonic: string;
declare const password: string;

const vault = new Vault(storage, {
  // Use the existing profile.appDataInfo for an established application.
  appDataInfo: 'mychain/app-data/v1',
  autoLockMs: 5 * 60 * 1000,
});

const unsubscribe = vault.on((event) => {
  if (event.type === 'locked' || event.type === 'destroyed') {
    // Drop derived keys, sensitive UI state and application caches here.
  }
});

await vault.create(mnemonic, password); // persists and unlocks
vault.noteActivity(); // call on real user interaction
const encrypted = await vault.sealData('private application data');
vault.lock();
await vault.unlock(password);
const plaintext = await vault.openData(encrypted);
vault.lock();
unsubscribe();

Use InMemoryVaultStorage in tests and demos. Persistent storage adapters belong to the host: files, IndexedDB and browser.storage.local all fit the same interface. The package has no default storage key or file path.

API

| Method | Behavior | |---|---| | getStatus() | Resolves to empty, locked or unlocked. | | isUnlocked() | Synchronously reports whether this instance holds a seed. | | create(mnemonic, password) | Validates a BIP-39 phrase, refuses to overwrite storage, then persists and unlocks. | | unlock(password) | Opens an existing vault. When already unlocked, refreshes the timer without checking the password again. | | lock(reason?) | Immediately wipes the resident seed and cancels earlier session operations. Reason is manual (default) or timeout. | | destroy() | Immediately wipes the seed, cancels earlier session work and queues deletion of the stored blob. | | changePassword(oldPassword, newPassword) | Re-encrypts the mnemonic; works locked or unlocked and preserves that state unless a lock occurs. | | revealMnemonic(password) | Requires an unlocked vault and a fresh correct password. | | getMasterKey() | Returns a new secret BIP-32 HDKey; the caller owns its lifetime. | | sealData(plaintext) / openData(blob) | Encrypt/decrypt strings using a key derived from the primary seed and appDataInfo. Require unlock. A lock during sealData does not discard the finished ciphertext; a lock during openData withholds the plaintext. | | noteActivity() | Refreshes the internal timer when unlocked. | | on(listener) | Synchronous event subscription; returns an unsubscribe function. Listeners should not throw. |

Events: created, unlocked, locked (with reason), destroyed. Destroy emits destroyed, not a separate locked event. Repeated lock does not emit another event; repeated unlock does not emit unlocked.

Exported errors: WalletLockedError, InvalidPasswordError, InvalidMnemonicError, VaultExistsError, VaultEmptyError, UnlockThrottledError (with retryAfterMs). Malformed blobs and application data may also raise VaultFormatError or AppDataError from @qbtc/crypto. Storage errors propagate unchanged, except for a best-effort KDF upgrade.

Password attempt limits

unlock, revealMnemonic and changePassword share one failure counter and cooldown in each Vault instance. The first three authentication failures have no added delay. After the fourth failure, the cooldown is one second; further failures increase it to 2, 4, 8, 16 and then at most 30 seconds. Each failure still rejects with InvalidPasswordError. During the cooldown, password checks reject with UnlockThrottledError, including checks of the correct password. Its retryAfterMs reports the remaining wait. The error name is retained for compatibility even for reveal and password changes.

The cooldown is checked inside the operation queue before decryption. Requests refused during a cooldown do not run the KDF, add failures or extend the deadline. Switching methods, calling lock() or reporting activity does not reset the counter. An already-unlocked unlock() does not verify a password and therefore does not reset it either.

A successful password check resets the counter after the session checkpoint, even if a later password-change write fails. A successful decryption cancelled by a lock does not reset it. An authentication failure still counts if a lock arrives during the check; queued requests cancelled before checking a password do not count. Storage, format and unrelated crypto errors do not count as incorrect passwords. Successful destruction resets the counter after storage is cleared; a failed deletion keeps the throttle for the existing blob.

The counter is in memory only: a new instance or process restart resets it. It does not protect a copied blob from offline guessing or prevent a caller from modifying the implementation. A strong password and the KDF cost remain the protection against offline guessing. Keep the Vault instance alive across UI sessions; creating one instance per request defeats both throttling and the serialized operation queue.

Autolock and platform integration

The internal timer defaults to five minutes. Only explicit activity and successful create/unlock refresh it; key derivation and app-data reads do not extend the session. Timeouts must be finite, nonnegative and at most 2147483647 milliseconds.

An extension that enforces idle locking with browser alarms should pass autoLockMs: 0, track activity in its background service and call vault.lock('timeout') when due. Keep-alive, service-worker restarts and OS lifecycle events belong to the application. A new Vault instance always starts without an in-memory seed.

Concurrency and storage

Use one Vault instance per storage location. Password and storage operations are serialized within that instance. Multiple processes or instances writing the same location require coordination in the host.

lock() and destroy() immediately invalidate earlier queued/in-flight session operations. Those operations reject with WalletLockedError at their next checkpoint and cannot restore an unlocked state or return decrypted data after that lock. A new explicit unlock issued after a lock is a new request and is allowed.

An already-started storage write cannot be cancelled: a create or password change can finish writing while its caller receives WalletLockedError. The host must treat this as an interrupted operation, not proof that the write was rolled back. Destroy waits for earlier writes before clearing the blob. Do not perform automatic destructive recovery on an error.

Existing data

Preserves the v1 JSON blob used by the crypto package: v, kdf, kdfParams, salt, iv, ciphertext. Password normalization is NFKC. New vaults use Argon2id; legacy scrypt vaults remain readable and are re-encrypted with Argon2id on successful unlock. A failed upgrade write does not prevent unlocking the original data.

When replacing an existing implementation, keep its exact storage location and appDataInfo. Changing the label makes existing app-data unreadable. Changing the password preserves the app-data key because that key derives from the seed. Replacing the primary seed changes the key.

The vault stores one primary mnemonic without a BIP-39 passphrase. Additional wallets, imported private keys and watch-only sources belong to the application's wallet model.

Security boundaries

  • Keep the instance in the trusted process that performs signing. It is not a sandbox for its callers: getMasterKey() exposes a secret HDKey.
  • Lock wipes the seed owned by this instance. Derived keys, returned strings and host caches cannot be revoked or reliably zeroed by it.
  • Mnemonic and app-data byte buffers owned by this layer are wiped on completion and failure. JavaScript strings and runtime-internal copies cannot be guaranteed erased.
  • Password attempt limits are shared by unlock, reveal and password changes within one instance; they do not cover offline guessing. See the limits and reset behavior above. Hosts should handle UnlockThrottledError for all three methods and preserve retryAfterMs across their IPC boundary.
  • Password-strength requirements and request authorization belong to the host. This package does not enforce a password minimum.
  • The storage adapter is a trusted persistence boundary, not an arbitrary file-import API. Stored KDF parameters control work and memory cost; this package checks their shape but imposes no new cost ceiling on existing blobs. A host accepting untrusted imports must bound resource usage before opening them.

License

MIT.