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

@anywalletkit/extension-kit

v0.3.1

Published

Chromium (MV3) wallet-extension core for Any Wallet Kit: scrypt + AES-256-GCM vault with passkey (WebAuthn PRF) unlock, auto-locking session, dApp request router with per-origin permissions and an approval queue, signing-request checks, swap / Earn plan r

Readme

@anywalletkit/extension-kit

npm

The security-relevant core of a non-custodial Chromium (MV3) wallet extension, built on the Any Wallet Kit chain and protocol packages: an encrypted vault, the unlocked-key session with auto-lock, the dApp request router with per-origin permissions and an approval queue, signing-request classification, and the review helpers for swaps and Earn plans. No UI, no @types/chrome, no network code of its own: the chrome adapters take structural objects (StorageAreaLike, AlarmsLike), so everything runs under Vitest with in-memory adapters.

Install

npm install @anywalletkit/extension-kit
# or: pnpm add @anywalletkit/extension-kit  ·  yarn add @anywalletkit/extension-kit

On npm: @anywalletkit/extension-kit, published from the release workflow with provenance.

This package is for wallet developers who build their own extension. It is a library, not an extension: the popup, approval windows, injected providers, service-worker wiring, build and release pipeline are the wallet's. The Vaulty browser extension is one product built on it.

pnpm add @anywalletkit/extension-kit @anywalletkit/core @anywalletkit/keyring

What it provides

| Area | Exports | What | | --- | --- | --- | | Vault | Vault, chromeLocalVaultStorage, memoryVaultStorage, VAULT_SCRYPT, VAULT_LIMITS, VAULT_MIN_PASSWORD_LENGTH | every secret at rest in chrome.storage.local, two key layers (a KEK wraps a DEK, the DEK seals each secret); passkey (WebAuthn PRF) unlock as a second wrap of the same DEK | | Session | Session, chromeSessionStorage, chromeAlarms, SESSION_DEFAULT_AUTOLOCK_MS | the DEK in chrome.storage.session with an expiry and an alarm; getKey() re-checks the expiry itself | | Keyring adapters | chromeKeyringStorage, vaultSecretStorage | @anywalletkit/keyring metadata as plain JSON, secrets through the vault, one DEK use per call | | Relay | PageRequest / RelayRequest shapes and guards, isRelayMethod, pageRequestFrom, stampOrigin, checkSenderOrigin, RELAY_ERRORS | page ⇄ content script ⇄ service worker messages; the content script takes only messages the page window itself posted (pageRequestFrom(event, window): an iframe's parent.postMessage is not the site), and the origin is the browser's (port.sender), never the page's | | Router | RequestRouter, READ_METHODS, SIGNING_METHODS, toRelayError | what a dApp may call: accounts only for connected origins, chain add / switch through the approval window, reads from the wallet's own RPCs, eth_sign never offered, everything else 4200 | | Permissions | PermissionStore, CHAIN_ACCOUNT_KEYS | per-origin accounts (EVM, Solana, TON, Tron, Bitcoin, NEAR) and chain | | Approvals | ApprovalQueue, ReviewDeclined, QueueLimitError, APPROVALS_MAX_PENDING_PER_ORIGIN | one approval window at a time, at most 3 pending requests per origin and one pending connect; transport notes (setVia) for WalletConnect peers | | Signing | classifySignRequest, prepareTransaction, createSignHandler, messageText | connected-address and chain checks, EIP-7702 delegations from sites refused, nonce / gas / fees from the wallet's RPC | | Networks | NetworksTable, DEFAULT_NETWORKS, parseAddChainParams, Solana / TON / Tron endpoints | EVM networks with public RPC failover lists; wallet_addEthereumChain only for catalog chains, https only, eth_chainId verified, site-supplied RPCs stamped custom | | Review gate | ReviewGate, evmCallMatcher, chainTxMatcher | after one sheet reviewed a whole plan, exactly those operations pass once within a TTL | | Swaps | quoteRoutes, reviewSwapPlan, executionFloor, SwapsStore, SwapTracker, parseSwapId | progressive quotes over swap2 providers, one review sheet per plan (a gasless order is shown and matched by its EIP-712 hash), pre-approvals bound to the reviewing account (meta.account), pending swaps resumed across worker restarts | | Earn | reviewEarnPlan, earnPlanMatchers | one sheet for an Earn module's plan (approvals + call), bound to meta.account | | TON Connect | parseConnectRequest, parseSendTransaction, parseSignData, tonProofMessage, tonSignDataMessage | the pure-data half of TON Connect v2 (the digests ton_proof and signData sign, to verify them); the account signs with signTonProof / signTonConnectData (wallet-ton) — sign() refuses 32-byte digests | | Phishing | PhishingDetector | MetaMask eth-phishing-detect list format, shipped with the build (never fetched at runtime); a listed site is refused (refusal → refuse on ApprovalQueue and RequestRouter, 4100, no window); fuzzy lookalikes, brand-in-label, punycode warn | | SIWE | parseSiwe, siweProblems, describeSiwe | EIP-4361 sign-in messages: domain vs. the asking site, address, chain, expiry, nonce (warnings) | | Address poisoning | lookalikeOf, RecipientBook | warns when a new recipient imitates one the user confirmed | | WebAuthn | verifyAssertion, approvalChallenge, derToRaw, rawToDer | ES256 assertion check for "passkey on every signature"; the worker never trusts the page's word |

Security model (summary)

  • Keys at rest. Envelope v2: scrypt N=2^17 r=8 p=1 (@noble/hashes) → AES-256-GCM wraps a random 32-byte DEK; the DEK seals each secret under AAD forgewallet-vault/secret/<id> (boxes cannot be swapped). The DEK box's AAD binds the exact KDF parameters, so an envelope whose salt or cost was edited on disk fails to open; unlock refuses sub-floor costs (weak-kdf), and costs or sizes above VAULT_LIMITS (scrypt N ≤ 2^20, r ≤ 16, p ≤ 4; PBKDF2 ≤ 5 000 000 iterations; 1 000 secrets, 32 passkeys, 4 MiB) before any KDF runs (unsupported-envelope). Writes to the envelope are serialized (one Vault per storage area), so parallel imports keep every secret. v1 envelopes (PBKDF2-SHA256, ≥ 600 000 iterations) still open and are re-sealed as v2. Passwords shorter than 8 characters are refused by the vault itself. Wrong password and tampering are the same failure (wrong-password). create never overwrites; reset is an explicit destroy().
  • Passkeys. A passkey wraps the same DEK under HKDF-SHA256(PRF output, salt = credential id); the password stays the recovery path.
  • Re-keying. changePassword re-wraps the same DEK, and removePasskey does not take back a DEK the passkey already unwrapped. After a suspected compromise, rotateVaultKey(password) makes a fresh DEK, re-seals every secret, drops the passkeys (register them again) and writes one envelope; hand the returned DEK to session.unlock, which replaces the old one. setSecret refuses a DEK that does not open the vault's secrets.
  • Unlocked state. Only the DEK is kept, in chrome.storage.session (memory-only, trusted contexts), with an expiry enforced on every read and an alarm that locks; key buffers this package owns are zeroized after use. unlock / lock / touch / getKey run one at a time, so activity racing a lock never brings the key back.
  • Errors to pages. toRelayError(e, { log }) passes the wallet's own refusals (EIP-1193 codes) and a node's JSON-RPC error (code, revert data) with URLs removed; anything else is -32603 Internal error on the page and goes to log.
  • Origins. The content script relays only what the page window itself posted (pageRequestFrom: event.source is the page, event.origin its origin), so an ad or embed in an iframe cannot speak as the site. The service worker answers a relay message only when the content script's stamped origin equals port.sender.origin. Permissions are keyed by that origin, never by a URL a page or a WalletConnect peer claims.
  • Requests. Everything that reveals accounts or moves funds goes through the approval queue; blind signing (eth_sign) is not offered; typed data for another chain, deployments and site-originated EIP-7702 delegations are refused; read-only RPC goes to the wallet's own endpoints.
  • Network. The kit makes no requests of its own beyond the RPC / provider calls the wallet wires in; the phishing list is data the wallet ships.

What stays the wallet's job: the extension-page CSP (script-src 'self', no remote images), a host allowlist for the worker's fetch, isolating third-party network SDKs (WalletConnect, Omniston) in a sandbox page, the reproducible release build and the store account.

Using the chrome adapters

import {
  Vault, Session, PermissionStore, NetworksTable, ApprovalQueue, RequestRouter,
  chromeLocalVaultStorage, chromeSessionStorage, chromeAlarms, chromeJsonStorage,
  chromeKeyringStorage, vaultSecretStorage, checkSenderOrigin, isRelayRequest, PORT_NAME,
} from '@anywalletkit/extension-kit'

// service worker
const vault = new Vault({ storage: chromeLocalVaultStorage(chrome.storage.local) })
const session = new Session({ storage: chromeSessionStorage(chrome.storage.session), alarms: chromeAlarms(chrome.alarms) })

// onboarding
const dek = await vault.create(password)          // refuses if a vault exists or the password is too short
await vault.setSecret(dek, 'main', utf8(seedPhrase))
await session.unlock(dek)                         // dek is zeroized here

// @anywalletkit/keyring on top of the vault
const keyringStorage = chromeKeyringStorage(chrome.storage.local)
const secrets = vaultSecretStorage(vault, session) // VaultError('locked') once the session expired

// the dApp side
const permissions = new PermissionStore(chromeJsonStorage(chrome.storage.local, 'my-wallet.permissions'))
const networks = new NetworksTable(chromeJsonStorage(chrome.storage.local, 'my-wallet.networks'))
const approvals = new ApprovalQueue(myApprovalWindow) // chrome.windows.create({ type: 'popup' }) behind ApprovalWindow
const router = new RequestRouter({ permissions, networks, approvals, accounts, rpc, emit, sign })

chrome.runtime.onConnect.addListener((port) => {
  if (port.name !== PORT_NAME) return
  port.onMessage.addListener(async (msg) => {
    if (!isRelayRequest(msg)) return
    const origin = checkSenderOrigin(port.sender, msg.origin)
    if (!origin) return // forged origin: dropped, not answered
    const result = await router.handle(origin, msg.chain, msg.method, msg.params)
    // post a RelayResponse { kind: 'response', id: msg.id, … } back on the port
  })
})

memoryVaultStorage, memorySessionStorage, memoryAlarms, memoryJsonStorage and memoryApprovalWindow are the in-memory twins for tests.