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

@sidhujag/sysweb3-keyring

v2.1.15

Published

Keyring Manager for UTXO and Web3 Wallets

Readme

Sysweb3-keyring

A stateless, multi-chain keyring manager for Syscoin and Ethereum-based networks.

Overview

The sysweb3-keyring provides a unified interface for managing accounts, transactions, and hardware wallets across Syscoin (UTXO) and Ethereum (EVM) networks. The KeyringManager operates statelessly, relying on external state providers (like Redux) for account and network data.

Key Features

  • Stateless Architecture: No internal state storage - integrates with external state management
  • Multi-Chain Support: Handles both UTXO (Syscoin) and EVM (Ethereum) networks
  • Hardware Wallet Support: Trezor and Ledger integration
  • Secure Session Management: Encrypted private key handling with session transfer
  • Transaction Management: Full transaction lifecycle support for both network types

Vault encryption compatibility

New vaults require WebCrypto and use PBKDF2-HMAC-SHA-512 (900,000 production iterations) with AES-256-GCM. Missing WebCrypto rejects the operation without writing a weaker fallback vault. Restore WebCrypto availability before retrying an unmarked legacy vault.

Historical fallback vaults used a 20,000-iteration key that also encrypted their persisted account keys. After authenticating such a CBC vault, the keyring records the explicit pbkdf2-sha512-20000 compatibility profile and rewraps the vault with AES-GCM. This preserves existing account access across restart; it is not a full rekey of those account records to 900,000 iterations. The profile is written before rewrapping, so interruption of either write remains recoverable. Arbitrary KDF profiles or parameters are rejected.

Storage clients must return their asynchronous write/delete promises. The core adapter preserves them so rejected writes do not silently advance vault migration. Fresh wallet creation additionally requires conditional batch creation. Native chrome.storage.local and browser.storage.local use a prefix-scoped Web Lock around the absence check and awaited write, coordinating contexts in the same extension origin and storage partition. Custom clients must provide an atomic createItemsIfAbsent(items) backend operation over prefixed, JSON-serialized values; the built-in memory client provides this operation synchronously. There is no sequential or unlocked fallback. A backend shared across storage partitions needs its own atomic implementation or a single owner.

Installation

npm install @sidhujag/sysweb3-keyring [email protected]
# or
yarn add @sidhujag/sysweb3-keyring [email protected]

Usage

Basic Setup

import { createKeyringHost, KeyringManager } from '@sidhujag/sysweb3-keyring';

// Create a vault state getter function (e.g., from Redux store)
const vaultStateGetter = () => store.getState().vault;

const walletHost = createKeyringHost(walletStorage);
const walletOptions = {
  ledgerHost: authorizedLedgerHostPort,
  captureEvmSubmission: captureReviewedSubmission,
};
const keyringManager = await KeyringManager.createInitialized(
  walletHost,
  seedPhrase,
  password,
  vaultStateGetter,
  walletOptions
);

The application supplies fixed wallet storage and a durable submission capability. The capability checks the active approval and persists exact signed bytes before the SDK can broadcast them.

Account Management

// Get active account
const activeAccount = keyringManager.getActiveAccount();

// Create new account
const newAccount = await keyringManager.addNewAccount('My Account');

// Switch active account
await keyringManager.setActiveAccount(accountId, KeyringAccountType.HDAccount);

// Import account from private key
const importedAccount = await keyringManager.importAccount(
  privateKey,
  'Imported Account'
);

Network Management

// Set network (automatically switches between UTXO/EVM signers)
await keyringManager.setSignerNetwork(networkConfig);

// Get current network
const network = keyringManager.getNetwork();

Transaction Operations

Syscoin (UTXO) Transactions

const quote = await keyringManager.syscoinTransaction.getEstimateSysTransactionFee(
  { amount: reviewedAmount, receivingAddress: reviewedRecipient },
  draftUtxoRead
);

// Review the exact PSBT and capture its approval before signing.
const signedPsbt = await keyringManager.syscoinTransaction.signPSBT(
  { psbt: reviewedPsbt },
  approvedUtxoRead
);

const receivingAddress = await keyringManager.updateReceivingAddress(accountUtxoRead);
const changeAddress = await keyringManager.getNewChangeAddress(accountUtxoRead);

Each UTXO read owner supplies a bounded endpoint-specific fetch, an abort signal and a synchronous account, network and session guard. A signing owner also binds the approved PSBT to that context.

Ethereum (EVM) Transactions

// Submit a fully specified transaction after approval.
const submitted = await keyringManager.ethereumTransaction.sendFormattedTransaction(
  reviewedTransaction,
  undefined,
  { mode: 'reviewed-native', assertCurrent: assertReviewCurrent }
);

// The host owns the reviewed account/network/session and revokes its authority
// when that context changes or the approval is cancelled.
async function signReviewedMessage(reviewedMessage, accountAddress, authority) {
  return keyringManager.ethereumTransaction.signPersonalMessage(
    [reviewedMessage, accountAddress],
    authority // Required { assertCurrent, signal } from the active host approval.
  );
}

Hardware Wallet Support

Trezor uses @trezor/connect-webextension 10.0.1. The hardware adapter loads it only when a supported MV3 extension invokes Trezor; ordinary Node CommonJS/ESM imports and the software entry do not initialize browser transports. Unsupported hosts reject hardware operations instead of falling back to software signing.

The extension must configure externally_connectable for https://suite.trezor.io/* and host access to that origin, as required by the published Connect 10 webextension package. Connect selects Suite Desktop or the Suite Web popup. The old Connect 9 iframe, content script, and connectSrc settings are not part of this integration.

Connect 10 does not expose the old device event emitter. SDK connection status and cached model information update from request results and disposal; they are not immediate physical unplug notifications. Disposal immediately rejects pending SDK operations and fences late replies. Suite Web may leave its popup open; a late approval there is discarded locally rather than treated as a fresh approval. When an underlying request is still active, transport cleanup is deferred to avoid triggering an automatic fallback popup. New initialization fails with HARDWARE_SIGNER_BUSY until that request settles; local lock/disposal does not wait for the remote prompt. An upstream transport failure can still trigger remote fallback UI while that old request is pending; the local SDK will reject its result. A failed or cancelled operation never authorizes a cached transaction, and terminal disconnect/unsupported-host errors are not retried. Device pairing, display verification, signing, and reconnection still require physical-device qualification in the consuming host.

// Import Trezor account
const trezorAccount = await keyringManager.importTrezorAccount(
  'Trezor Account'
);

// Import Ledger account
const ledgerAccount = await keyringManager.importLedgerAccount(
  false,
  'Ledger Account'
);

State Management Integration

The KeyringManager requires a vaultStateGetter function that returns the current vault state:

// Example with Redux
const vaultStateGetter = () => ({
  accounts: {
    [KeyringAccountType.HDAccount]: {
      /* account data */
    },
    [KeyringAccountType.Imported]: {
      /* imported accounts */
    },
  },
  activeAccount: { id: 0, type: KeyringAccountType.HDAccount },
  activeNetwork: {
    /* network config */
  },
  // ... other vault state
});

// The keyring manager will call this function to get current state
const keyringManager = new KeyringManager(walletHost, walletOptions);
keyringManager.setVaultStateGetter(vaultStateGetter);

Architecture

The stateless design means:

  • No Internal State: The KeyringManager doesn't store account or network data
  • External State Provider: Relies on your application's state management (Redux, Context, etc.)
  • Session Management: Private keys are encrypted and managed securely in memory
  • Multi-Instance Support: Multiple KeyringManager instances can operate independently

API Reference

KeyringManager

Main class for keyring operations:

  • createInitialized(host, seedPhrase, password, vaultStateGetter, options) - Create and initialize keyring
  • addNewAccount(label?) - Create new HD account
  • setActiveAccount(id, type) - Switch active account
  • importAccount(privateKey, label?) - Import account from private key
  • setSignerNetwork(network) - Set active network
  • unlock(password) - Unlock keyring
  • lockWallet() - Lock keyring

Transaction Managers

  • syscoinTransaction - UTXO transaction operations
  • ethereumTransaction - EVM transaction operations

The formatted-send options require an explicit mode and an approval guard. The guard must return undefined synchronously or throw to deny the operation. The SDK combines it with account, network and session checks at signing and broadcast. Once broadcast is acknowledged, the transaction remains recorded even if the approval has since expired.

Hardware Wallet Support

  • importTrezorAccount(label?) - Import Trezor account
  • importLedgerAccount(isConnected, label?) - Import Ledger account

Encrypted private records

An unlocked keyring can seal small application records without returning its mnemonic or a derived encryption key:

const domain = 'org.example.wallet.private-records.v1';
const recordId = crypto.randomUUID();
const profileId = await keyringManager.getPrivateRecordProfileId();
const envelope = await keyringManager.encryptPrivateRecord({
  domain,
  recordId,
  plaintext: new TextEncoder().encode('application-owned private data'),
});
// Store or back up only envelope; storage is owned by the calling application.
const plaintext = await keyringManager.decryptPrivateRecord({
  domain,
  recordId,
  envelope,
});
// Consume the bytes, then clear them when no longer needed.
plaintext.fill(0);

IEncryptedPrivateRecord contains only version: 1, domain, recordId, profileId, a base64 nonce and base64 ciphertext including its authentication tag. All application metadata should be encrypted inside plaintext. Domains and record IDs are 1–128 ASCII characters: an alphanumeric first character, followed by alphanumerics or ., _, :, /, -. Records may contain 0–65536 bytes. MAX_PRIVATE_RECORD_BYTES and MAX_PRIVATE_RECORD_LABEL_BYTES export these bounds.

The API uses native WebCrypto HKDF-SHA256 and AES-256-GCM, with a fresh random 96-bit nonce and a 128-bit tag. The authenticated tuple binds the format version, domain, record ID and profile ID. Profile and encryption derivations have separate HKDF labels. The root material is the NFKD-normalized mnemonic or extended private key already held by the unlocked session; derivation does not depend on the selected account, network or password. Rewrapping the same root with a new password therefore preserves access to these records.

Each operation rejects if its source session is locked, replaced or transferred before completion, even if the same buffers are later transferred back. These methods do not read or write wallet storage. Callers remain responsible for record IDs, persistence, backup, rollback detection and clearing returned bytes. The seed alone cannot restore randomly generated records: their encrypted backups are also required. Temporary byte buffers are cleared; JavaScript strings and native cryptographic allocations remain subject to runtime garbage collection rather than guaranteed memory erasure.

Security

  • initializeSession, initializeWalletSecurely, and the initialization factories create a new wallet only when both vault records are absent. Use unlock() to restore an existing wallet. Repeating initialization on a matching live session verifies the stored wallet without rewriting it.
  • Fresh vault salt and ciphertext are created together only if both are absent, through the core conditional-creation operation. Native extension contexts sharing an origin/storage partition are coordinated by Web Locks; custom backends must provide atomic createItemsIfAbsent. Unsupported adapters are rejected before writing. Existing or incomplete records are preserved.
  • UTXO signing authenticates the selected account's paths, public keys and spent scripts. A joint PSBT may include unfinished inputs for another signer: their HD/path hints are removed before the selected private signer runs, then their public metadata is restored to the returned partial PSBT. Another account in the same wallet remains unsigned. At least one unfinished input must authenticate to the selected account; a wholly finalized PSBT retains its existing handling. Standard P2WSH, wrapped P2WSH and P2SH multisig preserve external cosigner signatures and continuation. Hardware paths and single-address imports can reject unsupported joint inputs rather than widening signing authority.
  • Hardware signing preserves already-finalized external inputs without requesting their private derivations. Trezor receives them as EXTERNAL inputs; Ledger retains their final scripts in PSBTv2 and skips signing/finalizing them again. Unfinished foreign hardware inputs remain unsupported.
  • Persisted wallet and account secrets are encrypted; plaintext signing material is used in memory
  • Session data is cleared when the keyring is locked
  • Hardware wallet integration follows device security models
  • Secure memory management with explicit cleanup

License

MIT License

Ledger platform host

Ledger device access uses the Device Management Kit. The regular and software entries do not load a Ledger transport or device SDK. Browser integrations must create the heavy host from @sidhujag/sysweb3-keyring/esm/ledger-host in a visible extension page and inject its semantic LedgerHost port into the wallet authority:

// Visible device page only:
import { createLedgerDmkHost } from '@sidhujag/sysweb3-keyring/esm/ledger-host';
const host = createLedgerDmkHost({
  confirmBlindSigning: async (intent, signal) => {
    // Show the exact immutable intent and an explicit blind-signing warning.
    // Return true only after a fresh user decision; cancellation must return false.
    return showTrustedWalletConfirmation(intent, signal);
  },
});

// Wallet authority: use a secured semantic port to that page, not a raw APDU port.
const wallet = new KeyringManager(walletHost, {
  ledgerHost: authorizedLedgerHostPort,
  captureEvmSubmission: captureReviewedSubmission,
});

Use only the ESM host entry with a browser bundler and its required platform polyfills. cjs/ledger-host is not a supported entry. A service worker cannot own the WebHID device picker. The platform must bind the host to its own trusted page, revoke pending operations on lock/close, and never expose this port to dapps. A missing host fails with HARDWARE_SIGNER_UNAVAILABLE; there is no legacy LedgerJS fallback. Pass either a shared hardware manager or a Ledger host to KeyringManager, not both.

A platform that owns its transport can lazily import createSelectedLedgerDmkHost from @sidhujag/sysweb3-keyring/esm/ledger/dmk-selected-host.js with an ESM-aware bundler. Supply the platform's DMK and the same trusted approval callback. This entry leaves WebHID to the browser host and cannot connect until a single-use beginDeviceSelection capability accepts a device selected by the user. The platform still owns transport permissions and cancellation.

The default host performs no remote metadata lookup, transaction checks, or signing telemetry. The current Ethereum signer library can fall back to blind signing, so transaction and typed-data signing require per-request approval through the trusted confirmBlindSigning callback. Omitting it fails closed. Personal-message signing and address retrieval remain available offline. Signatures are checked against the expected account; UTXO signatures must also match the approved PSBT and its requested sighash coverage. The custom UTXO host supports the existing Syscoin and Bitcoin app protocols. Physical-device and firmware qualification remain separate from source and mocked-device tests.

Shared standard EVM fees

cjs/evm-fees is a dependency-free read-only entry. Both the existing EthereumTransactions.getFeeDataWithDynamicMaxPriorityFeePerGas method and restricted hosts use the same calculation:

import { getEvmFeeData } from '@sidhujag/sysweb3-keyring/cjs/evm-fees';

const fees = await getEvmFeeData(readOnlyProvider, {
  signal,
  assertCurrent: () => assertSameAccountAndNetwork(),
});

The reader supplies only getGasPrice(), getBlock('latest'), and send('eth_maxPriorityFeePerGas' | 'eth_feeHistory', params). It must validate its RPC responses and bind its endpoint/chain; this helper does not instantiate a provider, estimate gas, select an account, sign or broadcast. The host must retain request deadlines and perform its ordinary account/network/session checks before signing.

Results are frozen bigint fields: gasPrice, maxFeePerGas, and maxPriorityFeePerGas, with baseFeePerGas only when the observed header has that field. An absent base fee produces the existing legacy zero sentinels for the two EIP-1559 fields. A zero base fee or zero tip remains valid EIP-1559 data. An explicitly unsupported priority method may use actual recent fee-history rewards; other failures never turn into fallback fees. Quantities and calculated fees are bounded to uint256, history to the requested five blocks, and no signing authority or dependencies are loaded by this entry.

The optional minPriorityFee and whole-percent feeMultiplier preserve the existing configured recommendation policy (default multiplier 250). Options are snapshotted before asynchronous reads. The synchronous assertCurrent callback must return undefined; an async callback is rejected. Cancellation rejects pending discovery without permitting later results to produce a quote.

bufferedEvmGasLimit(estimate: bigint, minimum?: bigint) shares the existing successful gas estimate policy used by EthereumTransactions.getTxGasLimit: floor the estimate times120/100 and apply the configured minimum if larger. It includes ordinary native transfers. The helper performs no estimation, fallback request, transaction cap selection or broadcasting; callers enforce the selected network's limits after applying it. The SDK's existing estimation failure/fallback behavior is unchanged.