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

@valve-tech/wallet-crypto

v0.19.0

Published

Wallet-derived encryption keys + AES-GCM authenticated envelopes for viem-based dapps. Deterministic, versioned keys derived from a personal_sign signature (same wallet + same purpose + same version → same CryptoKey, across devices). Non-extractable WebCr

Readme

@valve-tech/wallet-crypto

Wallet-derived encryption keys + AES-GCM authenticated envelopes for viem-based dapps. Same wallet + same purpose + same version derives the same CryptoKey on any device, forever. Encrypt with AAD-bound envelopes; decryption is single-state authenticated.

Pure functions. Peer-deps viem; runtime is WebCrypto.

npm install @valve-tech/wallet-crypto viem

What it solves

Every wallet-gated product that wants to cloud-sync encrypted blobs has to write:

  1. A deterministic key-derivation step (so the encrypted blob can be opened on the user's next device without server-side key escrow).
  2. An AEAD encrypt/decrypt envelope (so the cloud can't read blobs and so tampering is detected).

Both have well-trodden footguns: weak key derivation that lets a malicious app extract the key, AES-GCM IV reuse, missing AAD that allows downgrade attacks. This package handles them once.

API

import {
  deriveWalletEncryptionKey,
  encryptEnvelope,
  decryptEnvelope,
  formatKeyDerivationMessage,
  WalletDeclined,
  WalletUnavailable,
  DecryptionFailed,
} from '@valve-tech/wallet-crypto'

deriveWalletEncryptionKey({ signer, purpose, version, usages? })

Returns a non-extractable AES-GCM CryptoKey.

const key = await deriveWalletEncryptionKey({
  signer: walletClient,
  purpose: 'explore-workspaces',
  version: 1,
})

How it works: signs formatKeyDerivationMessage({ purpose, version }) via personal_sign, SHA-256s the signature bytes to get 256 bits of key material, imports as extractable: false. The signature bytes never leave the function.

Versioning + key rotation. Bumping version invalidates every blob encrypted under the old key. Migration is per-product:

  1. App reads its old blob, decrypts with v1 key.
  2. App derives v2 key.
  3. App re-encrypts with v2, writes back.
  4. App updates its persisted "current version" flag.

The library doesn't own this loop — it owns deriving distinct keys per version.

encryptEnvelope({ key, plaintext, aad? })

Returns { ciphertext, nonce }. The nonce is the 12-byte AES-GCM IV — not an auth nonce. Don't confuse them.

const { ciphertext, nonce } = await encryptEnvelope({
  key,
  plaintext: new TextEncoder().encode(blob),
  aad: new TextEncoder().encode('envelope-v1'),
})

Use aad to bind protocol metadata (envelope version, app id) so a downgrade attack can't swap an old ciphertext for a new one. The AAD isn't encrypted, just authenticated — it must be re-supplied on decrypt or decryption fails.

decryptEnvelope({ key, ciphertext, nonce, aad? })

Returns plaintext bytes. Throws DecryptionFailed for any cause — wrong key, tampered ciphertext, wrong AAD, wrong IV. The failure is deliberately not differentiated.

formatKeyDerivationMessage({ purpose, version })

Returns the EXACT plaintext the wallet will sign. Exposed so consumers can preview it (e.g. show the user what's about to be signed) or use the same template for offline test fixtures.

Errors

All three are instanceof-checkable:

| Class | When | |---|---| | WalletDeclined | User rejected the signature prompt (EIP-1193 4001 / class / message). | | WalletUnavailable | WalletClient has no account set. | | DecryptionFailed | AEAD failure — wrong key, tamper, AAD mismatch, or IV mismatch. |

End-to-end example

import { createWalletClient, custom } from 'viem'
import { mainnet } from 'viem/chains'
import {
  deriveWalletEncryptionKey,
  encryptEnvelope,
  decryptEnvelope,
} from '@valve-tech/wallet-crypto'

const walletClient = createWalletClient({
  chain: mainnet,
  transport: custom(window.ethereum),
})

// One-time per session, after wallet connect:
const key = await deriveWalletEncryptionKey({
  signer: walletClient,
  purpose: 'explore-workspaces',
  version: 1,
})

// Encrypt
const blob = JSON.stringify({ workspaces: [...] })
const { ciphertext, nonce } = await encryptEnvelope({
  key,
  plaintext: new TextEncoder().encode(blob),
  aad: new TextEncoder().encode('envelope-v1'),
})

// Send to backend (or IDB, S3, IPFS — your call)
await fetch('/api/sync', {
  method: 'PUT',
  body: JSON.stringify({
    ciphertext: btoa(String.fromCharCode(...ciphertext)),
    nonce: btoa(String.fromCharCode(...nonce)),
    envelope: 'v1',
  }),
})

// Decrypt later (same wallet, different device)
const decrypted = await decryptEnvelope({ key, ciphertext, nonce, aad: new TextEncoder().encode('envelope-v1') })
console.log(new TextDecoder().decode(decrypted))

Pitfalls

  1. nonce in encryptEnvelope/decryptEnvelope is NOT an auth nonce. It's the AES-GCM IV. The SIWE nonce from viem/siwe / @valve-tech/siwe-store is unrelated. Crossing them is the #1 reported caller error.

  2. Don't roll your own key derivation. A sha256(walletAddress + "my-app")-style scheme is publicly recoverable — any other app can derive the same key. The personal_sign step in this package is what makes the key wallet-private, because only the wallet can produce the signature.

  3. Don't console.log the result of signer.signMessage. The raw signature is the key seed; logging it leaks the encryption key. This package never returns or surfaces the signature; if you call signMessage yourself elsewhere with the same template, you're creating a new copy that can leak.

  4. AAD must match exactly on decrypt. This is a feature (downgrade resistance) but a footgun if you forget. Treat AAD as part of the ciphertext-envelope shape and version it alongside.

  5. version bumps invalidate prior blobs. Either migrate at bump-time or version-tag your stored blobs so you can read v1 with the v1 key and v2 with the v2 key.

Composition with sibling packages

  • @valve-tech/wallet-key-session — the memory-only lifecycle of the key this package derives (derive-once, wipe on account-change / tab-close). Wire deriveWalletEncryptionKey into its derive callback. For auth, use viem/siwe + @valve-tech/siwe-store.
  • @valve-tech/viem-errorsWalletDeclined is thrown via this package's isUserRejectionError detector under the hood, so you get the same three-signal coverage (EIP-1193 4001, class name, message regex) without extra work.

For AI agents

Machine-readable integration skills ship in this tarball under skills/. Run npx @valve-tech/agent-skills install to copy all installed @valve-tech/* skills into .claude/skills/, or read them in place at node_modules/@valve-tech/wallet-crypto/skills/.

License

MIT