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

@prism-ing/wallet

v1.0.4

Published

Production-grade, framework-agnostic wallet SDK. OWS signing + ZeroDev + Squads recovery. No third-party API keys required.

Readme

@prism-ing/wallet

Self-custodial wallet SDK. Hardware-backed key security via iOS Secure Enclave, social recovery via ZeroDev (EVM) and Squads V4 (Solana). No third-party API keys required.

For cross-chain balances, deposits, and smart account addresses, add @prism-ing/onebalance.

Install

pnpm add @prism-ing/wallet

Optional peer dependencies (install only what you need):

pnpm add @zerodev/sdk    # On-chain session key enforcement
pnpm add @sqds/multisig  # Solana social recovery
pnpm add viem            # EVM utilities

Why Prism Wallet?

| | Prism | Coinbase AgentKit | Raw viem/ethers | |---|---|---|---| | No seed phrases | Yes | No | No | | Session key policies | Yes (6 dimensions, on-chain) | No | No | | Solana support | Yes | No | No | | Result API (no throws) | Yes | No | No | | Self-custodial | Yes | Partial | Yes | | Zero required API keys | Yes | No | Yes |

Quickstart

Base wallet (signing + recovery, no API key)

import { createProductionWallet } from '@prism-ing/wallet';

const result = await createProductionWallet({
  signer: { walletName: 'my-agent' },
});

if (!result.ok) {
  console.error(result.error.code);
  process.exit(1);
}

const wallet = result.value;
console.log('EVM address:', wallet.evmAddress);
console.log('Solana address:', wallet.solanaAddress);

Same wallet name = same keys = same addresses, every time. Keys persist encrypted at ~/.ows/.

Used in an AI agent (ElizaOS, LangChain, AutoGPT)? Pass wallet.signer directly to your framework's signing hook — it's structurally compatible with any interface that accepts signMessage / signTypedData.

Enhanced wallet (with OneBalance account abstraction)

import { createProductionWallet } from '@prism-ing/wallet';
import { createOneBalanceProvider } from '@prism-ing/onebalance';

const result = await createProductionWallet(
  { signer: { walletName: 'my-agent' } },
  { accountAbstraction: createOneBalanceProvider({ apiKey: process.env.ONEBALANCE_API_KEY }) },
);

if (!result.ok) {
  console.error(result.error.code);
  process.exit(1);
}

const wallet = result.value;
console.log('Smart account:', wallet.smartAccountAddress);

const balance = await wallet.getBalance();
console.log(`Balance: $${balance.totalUsd}`);

When you inject an AccountAbstractionProvider, the factory returns an EnhancedWalletAccount with getBalance(), deposit(), getTransactionStatus(), and a guaranteed smartAccountAddress.

Signing Backends

createProductionWallet auto-selects the correct backend for your platform. If you use the lower-level createWallet, choose a backend explicitly:

| Backend | Factory | Platform | Key Storage | Use Case | |---------|---------|----------|-------------|----------| | OWS | createOWSSigningBackend(config) | Node.js | Encrypted at ~/.ows/ (AES-256-GCM, scrypt KDF) | Production agents — persistent, encrypted keys | | Node Software | createNodeSigningBackend() | Node.js | In-memory only | Dev/testing — random keypairs, never persisted | | Secure Enclave | createSecureEnclaveBackend(config) | iOS | Hardware-encrypted in iOS Keychain | Mobile apps — biometric-gated signing |

import { createWallet, createOWSSigningBackend } from '@prism-ing/wallet';

const result = await createWallet(
  { signer: { walletName: 'my-agent' } },
  { createSigner: createOWSSigningBackend },
);

For testing with ephemeral keys:

import { createWallet, createNodeSigningBackend } from '@prism-ing/wallet';

const result = await createWallet(
  { signer: { walletName: 'test' } },
  { createSigner: createNodeSigningBackend },
);

Architecture

Two layers compose to give you a persistent, recoverable wallet. Account abstraction is an optional third:

Signing Backends (platform-specific key management)
  ├── iOS Secure Enclave: keys encrypted by hardware P-256 key, biometric-gated
  ├── OWS (Node.js): secp256k1 + ed25519 from BIP-39, encrypted at ~/.ows/
  └── Node Software: in-memory signing via @noble/curves (dev/testing)

Smart Contract Wallet (the actual "wallet")
  ├── Signer key authorizes operations, but is NOT the wallet itself
  ├── Funds live in the smart contract, not controlled by the raw key
  ├── Signer can be rotated without moving funds (recovery)
  └── On-chain policy enforcement via ZeroDev permission validators

Account Abstraction Provider (optional — e.g., @prism-ing/onebalance)
  ├── ERC-4337 counterfactual smart account from signer's public key
  ├── Resource Locks: instant cross-chain execution
  └── Aggregated balance across all supported chains

Key insight: The signer key is not the wallet. It authorizes operations on a smart contract. If compromised, guardians can rotate it out without moving funds. This separation is fundamental to every security property.

Private Key Management

Security Model by Platform

| Property | Node.js (Agent) | iOS (Mobile) | |----------|-----------------|--------------| | Key storage | Encrypted at ~/.ows/ (AES-256-GCM, scrypt KDF) | Encrypted by Secure Enclave P-256 key, stored in iOS Keychain | | Auth | Passphrase or API key | Biometric (Face ID / Touch ID) | | Key isolation | In-process only, never serialized | Decrypted in-process only after biometric, never serialized | | EVM signing | Software secp256k1 | Software secp256k1, biometric-gated | | Solana signing | Software ed25519 | Software ed25519, biometric-gated | | Seed phrases | None exposed. OWS encrypts mnemonic internally. | None. Keys are non-exportable. |

Agent Mode

// First run: creates wallet, encrypts keys at ~/.ows/
// Every subsequent run: loads same wallet, same addresses
const result = await createProductionWallet({
  signer: { walletName: 'trading-agent' },
});

With passphrase protection:

const result = await createProductionWallet({
  signer: {
    walletName: 'treasury',
    passphrase: process.env.WALLET_PASSPHRASE,
  },
});

With OWS agent API key (policy-gated signing):

const result = await createProductionWallet({
  signer: {
    walletName: 'treasury',
    apiKey: 'ows_key_a1b2c3d4...',
  },
});

iOS Mobile App

import { createProductionWallet } from '@prism-ing/wallet';
import { createOneBalanceProvider } from '@prism-ing/onebalance';
import { SecureEnclaveModule } from './native/SecureEnclaveModule';

const result = await createProductionWallet(
  { signer: { walletName: 'user' } },
  {
    nativeBridge: SecureEnclaveModule,
    biometricPrompt: 'Authorize this trade',
    recoveryBackends: { evm: evmBackend, solana: solanaBackend },
    accountAbstraction: createOneBalanceProvider({ apiKey: config.oneBalanceApiKey }),
  },
);

On iOS, createProductionWallet detects the platform and auto-selects the Secure Enclave backend when nativeBridge is provided.

Custom Signer (bring your own keys)

Routers and the wallet factory accept any PrismSigner. No dependency on @prism-ing/wallet for signing:

import type { PrismSigner } from '@prism-ing/wallet';

const mySigner: PrismSigner = {
  evmAddress: myAddress,
  solanaAddress: mySolanaAddress,
  signMessage: (msg) => mySigningLib.sign(msg),
  signTypedData: (payload) => mySigningLib.signTypedData(payload),
  signTransaction: (tx) => mySigningLib.signSolana(tx),
};

Why Result Types?

Traditional wallet libraries throw on failure. With Prism, every async operation returns a Result<T, E> — check .ok before using the value. TypeScript enforces this at compile time.

// Without Result types — silent footgun
try {
  const wallet = await someLib.createWallet(config);
  // wallet might be undefined, might have thrown
} catch (e) {
  // e is `unknown` — you have no idea what failed
}

// With Prism — exhaustive, typed, no surprises
const result = await createProductionWallet(config);
if (!result.ok) {
  // result.error is WalletError — a discriminated union with specific codes
  if (result.error.code === 'PROVIDER_TIMEOUT') {
    // safe to retry
  }
  return;
}
const wallet = result.value; // TypeScript knows this is WalletAccount

Session Keys

Session keys are ephemeral, policy-scoped signers for agents and automated processes. They wrap the root PrismSigner and enforce constraints on every signing operation.

Agent safety: A session with allowedAssets: ['ob:usdc'], maxAmountPerOp: '1000000000', and expiresAt 1 hour from now cannot sign anything outside that policy — even if the agent framework passes a different payload. ZeroDev reverts on-chain if violated. Rogue agents cannot drain the wallet.

Basic Usage

import { createSessionKeyManager, validateSessionOperation } from '@prism-ing/wallet';

const manager = createSessionKeyManager(wallet.signer);

const session = manager.createSessionKey({
  expiresAt: Math.floor(Date.now() / 1000) + 3600,
  allowedAssets: ['ob:usdc'],
  maxAmountPerOp: '1000000000',
  maxTotalAmount: '5000000000',
  allowedChains: [8453, 42161],
  allowedRecipients: ['0xabc...'],
});

// Use the session signer — policy enforced on every sign call
// session.signer is a PrismSigner scoped to the policy

// Revoke when the task is complete
manager.revokeSessionKey(session.sessionId);

Spend Persistence

By default, cumulative spend tracking for maxTotalAmount lives in memory and resets on process restart. For long-running agents, persist it to disk:

import { createSessionKeyManager, createFileSpendPersistence } from '@prism-ing/wallet';

const persistence = createFileSpendPersistence(); // stores at ~/.prism/sessions/
const manager = createSessionKeyManager(wallet.signer, undefined, persistence);

If maxTotalAmount is set on a policy, you must provide either a SessionKeyBackend (on-chain enforcement) or a SpendPersistence adapter. Without one, the manager throws at creation time.

On-Chain Enforcement (ZeroDev)

import { createZeroDevSessionBackend, createSessionKeyManager } from '@prism-ing/wallet';

const sessionBackend = createZeroDevSessionBackend({
  registerOnChain: async (sessionAddress, policies) => txHash,
  revokeOnChain: async (sessionAddress) => txHash,
  defaultGasBudgetWei: 100_000_000_000_000_000n,
  callPolicyVersion: '0.0.4',
});

const manager = createSessionKeyManager(wallet.signer, sessionBackend);

Social Recovery

Recovery rotates the smart contract's authorized signer without moving funds. No API key required. When using the optional ZeroDev or Squads V4 recovery backends, your use of those services is subject to their respective terms of service (ZeroDev, Squads).

Setting Up Recovery (EVM — ZeroDev)

const recovery = wallet.recover();

// Register a passkey guardian (iCloud Keychain)
const passkey = await recovery.setupPasskey();

// Add a second device as guardian
await recovery.addDeviceGuardian(deviceEvmAddress, deviceSolanaAddress);

// Add a trusted contact
await recovery.addContactGuardian(contactEvmAddress);

Setting Up Recovery (Solana — Squads V4)

Squads V4 multisig recovery uses a threshold of members to authorize signer rotation on Solana.

import { createSquadsRecoveryBackend, FULL_PERMISSIONS, VOTER_PERMISSIONS } from '@prism-ing/wallet';

const solanaBackend = createSquadsRecoveryBackend({
  bridge: {
    async executeConfigTransaction(actions) {
      // Your Squads V4 SDK integration:
      // create config tx → propose → approve → execute
      return txSignature;
    },
    async getThreshold() { return 2; },
    async getMemberCount() { return 3; },
  },
  autoIncrementThreshold: true, // bump threshold when adding members (default)
});

const result = await createProductionWallet(
  { signer: { walletName: 'user' } },
  { recoveryBackends: { solana: solanaBackend } },
);

Permission presets: FULL_PERMISSIONS (proposer + voter + executor) for primary guardians, VOTER_PERMISSIONS (voter only) for contacts who should only approve recovery but not initiate it.

Performing Recovery

// On a new device, with new keys:
const newWallet = await createProductionWallet({
  signer: { walletName: 'recovered-wallet' },
});

const recovery = oldWalletRecoveryManager;
const recoveryTx = await recovery.initiateRecovery(newWallet.value.signer);
// Smart contract's authorized signer now points to newWallet.signer

Pending Recovery (iOS)

On iOS with Secure Enclave, createProductionWallet may return a WalletPendingRecovery instead of a full wallet. This happens when recovery guardians must be configured before the wallet is usable.

import { createProductionWallet, isPendingRecovery } from '@prism-ing/wallet';

const result = await createProductionWallet(
  { signer: { walletName: 'user' } },
  { nativeBridge: SecureEnclaveModule, accountAbstraction: provider },
);

if (!result.ok) { /* handle error */ }

const wallet = result.value;

if (isPendingRecovery(wallet)) {
  // Must set up at least one guardian before wallet activates
  const recovery = wallet.recover();
  await recovery.setupPasskey();
  await recovery.addDeviceGuardian(deviceEvmAddress, deviceSolanaAddress);

  // Now activate the full wallet
  const activated = wallet.activateWallet();
  if (!activated.ok) { /* handle error */ }
  // activated.value is a WalletAccount (Base or Enhanced)
} else {
  // Already a full wallet — use normally
}

API Reference

createProductionWallet(config, options?)

Creates a wallet with platform-appropriate signing. Returns Result<BaseWalletAccount, WalletError> without a provider, or Result<EnhancedWalletAccount, WalletError> with one.

interface WalletConfig {
  signer: SignerConfig;
  recovery?: RecoveryConfig;
}

interface ProductionWalletOptions {
  nativeBridge?: SecureEnclaveNativeBridge;
  keyTag?: string;
  biometricPrompt?: string;
  recoveryBackends?: RecoveryBackends;
}

// Add accountAbstraction to get an EnhancedWalletAccount:
interface ProductionWalletOptionsWithAA extends ProductionWalletOptions {
  accountAbstraction: AccountAbstractionProvider;
}

BaseWalletAccount

Returned when no AccountAbstractionProvider is injected.

interface BaseWalletAccount {
  readonly signer: PrismSigner;
  readonly evmAddress: Address;       // Raw EOA
  readonly solanaAddress: string;
  recover(): RecoveryManager;
}

EnhancedWalletAccount

Returned when an AccountAbstractionProvider is injected. Extends BaseWalletAccount.

interface EnhancedWalletAccount extends BaseWalletAccount {
  readonly smartAccountAddress: Address;  // Counterfactual ERC-4337 address
  getBalance(): Promise<UnifiedBalance>;
  deposit(params: DepositParams): Promise<TxResult>;
  getTransactionStatus(quoteId: string): Promise<TxResult>;
}

Type Guards

import { isEnhancedWallet, isPendingRecovery } from '@prism-ing/wallet';

if (isPendingRecovery(wallet)) {
  // wallet is WalletPendingRecovery — must set up guardians then call activateWallet()
} else if (isEnhancedWallet(wallet)) {
  // wallet is EnhancedWalletAccount — has getBalance(), deposit(), smartAccountAddress
} else {
  // wallet is BaseWalletAccount — signing + recovery only
}

AccountAbstractionProvider

The interface for opt-in account abstraction. OneBalance is the reference implementation.

interface AccountAbstractionProvider {
  predictAddress(signerAddress: Address, accountType: string): Promise<Address>;
  getBalance(accounts: readonly SmartAccount[]): Promise<UnifiedBalance>;
  deposit(params: DepositParams, signer: PrismSigner, accounts: readonly SmartAccount[]): Promise<TxResult>;
  getTransactionStatus(quoteId: string): Promise<TxResult>;
}

PrismSigner

The core signing interface. Routers depend on this, not on @prism-ing/wallet.

interface PrismSigner {
  signMessage(message: Hex): Promise<Hex>;
  signTypedData(payload: TypedDataPayload): Promise<Hex>;
  signTransaction<T>(tx: T): Promise<Result<T, WalletError>>;
  readonly evmAddress: Address;       // Raw EOA
  readonly solanaAddress: string;
}

Error Handling

All operations return Result<T, WalletError> instead of throwing:

const result = await createProductionWallet(config);

if (!result.ok) {
  if (isRetryableError(result.error)) {
    // PROVIDER_TIMEOUT, PROVIDER_API_ERROR — safe to retry
  }
  if (isUserFacingError(result.error)) {
    // SIGNING_REJECTED, INSUFFICIENT_BALANCE — show to user
  }
  return;
}

| Code | Retryable | User-Facing | Description | |------|-----------|-------------|-------------| | ENCLAVE_UNAVAILABLE | No | No | Secure Enclave not available on this platform | | PROVIDER_TIMEOUT | Yes | No | Account abstraction provider did not respond in time | | PROVIDER_API_ERROR | Yes | No | Account abstraction provider returned an error | | RECOVERY_GUARDIAN_INVALID | No | Yes | Invalid guardian address | | SIGNING_REJECTED | No | Yes | User rejected signing (biometric fail / cancel) | | ACCOUNT_NOT_INITIALIZED | No | No | Operation on uninitialized account | | INSUFFICIENT_BALANCE | No | Yes | Not enough balance for operation | | KEY_NOT_FOUND | No | No | Requested key pair not found in signing backend | | VALIDATION_ERROR | No | Yes | Input failed Zod validation | | SESSION_KEY_EXPIRED | No | No | Session key has expired or been revoked | | SESSION_KEY_POLICY_VIOLATION | No | Yes | Operation violates session key policy | | QUOTE_VERIFICATION_FAILED | No | No | Quote failed integrity verification |

Defense-in-Depth Layers

Layer 1: Key Storage
  iOS: Secure Enclave encryption + biometric gate
  Node: OWS scrypt + AES-256-GCM encrypted vault

Layer 2: Session Key Policies
  Client-side: JS enforcement before every signature
  On-chain: ZeroDev permission validators (UserOp reverts if violated)

Layer 3: Quote Verification (via @prism-ing/onebalance)
  6-point client-side integrity check before signing any quote

Layer 4: Recovery Challenges
  Dynamic keccak256 challenges bound to signer pair + 5-minute time window

Layer 5: Smart Contract Validation
  ERC-4337 smart account validates all operations on-chain
  ZeroDev Kernel v3.1 enforces session key policies at contract level

License

MIT