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

@chainberry/chainberry-wallet

v1.0.4

Published

Blockchain wallet provider library — sign, broadcast, balance, fee estimation for EVM, BTC, LTC, BCH, SOL, TRX, XRP, TON

Readme

@chainberry/chainberry-wallet

Pure TypeScript wallet provider library — no NestJS dependency. Handles EVM (ETH/POL/BNB), Solana, TRON, TON, and Bitcoin chain operations with a unified provider interface.

Setup

Add to your app's tsconfig.app.json references:

{
  "path": "../../libs/chainberry-wallet/tsconfig.lib.json"
}

Core concepts

  • IWalletChainProvider — common interface for all chains (send, estimateFee, getBalances)
  • WalletProviderRegistry — routes (token, network) to the correct provider, no if-chains
  • CredentialUtils — hybrid AES-256-GCM encrypt/decrypt; takes IKeyProvider to abstract key storage
  • IKeyProvider — abstraction over key wrapping (AWS KMS, RSA, or any other backend)

Minimal setup (no framework)

import {
  CredentialUtils,
  EvmChainProvider,
  WalletProviderRegistry,
  type IKeyProvider,
  type WalletConfig,
} from "@chainberry/chainberry-wallet";
import { constants, privateDecrypt, publicEncrypt } from "crypto";
import fs from "fs";

// 1. Build a key provider (RSA example — swap for AWS KMS in production)
const privateKeyPem = fs.readFileSync("private.pem", "utf-8");
const publicKeyPem  = fs.readFileSync("public.pem", "utf-8");
const rsaOpts = { padding: constants.RSA_PKCS1_OAEP_PADDING, oaepHash: "sha256" } as const;

const keyProvider: IKeyProvider = {
  wrapKey:   (aesKey)     => Promise.resolve(publicEncrypt({ key: publicKeyPem, ...rsaOpts }, aesKey).toString("base64")),
  unwrapKey: (wrappedKey) => Promise.resolve(privateDecrypt({ key: privateKeyPem, ...rsaOpts }, Buffer.from(wrappedKey, "base64"))),
};

// 2. Build credential utils
const credentialUtils = new CredentialUtils(keyProvider);

// 3. Config (all RPC URL fields optional — fall back to public endpoints)
const config: WalletConfig = {
  WALLET_ENV: "testnet",
  ETH_RPC_URL: "https://my-rpc.example.com",
};

// 4. Create providers and registry
const registry = new WalletProviderRegistry([
  new EvmChainProvider(config, credentialUtils),
]);

Encrypt a private key (wallet creation)

CredentialUtils.hybridEncrypt wraps a raw private key into the WalletData format expected by providers:

const kmsKeyId = "my-kms-key-id"; // or any string identifier for the key pair

const encrypted = await credentialUtils.hybridEncrypt(rawPrivateKeyHex, kmsKeyId);
// encrypted = { ciphertext, iv, tag, wrappedKey, kmsKeyId }

// Store encrypted in DB as WalletData:
const walletData = {
  address:         "0xYourAddress",
  encryptedSecret: encrypted.ciphertext,
  iv:              encrypted.iv,
  tag:             encrypted.tag,
  wrappedKey:      encrypted.wrappedKey,
  kmsKeyId:        encrypted.kmsKeyId,
};

Send a transaction

const provider = registry.getProviderForSend("ETH", "ETH");
if (!provider) throw new Error("No provider for ETH on ETH");

// walletData comes from your database (encrypted at creation time)
const walletData = {
  address:         "0xSenderAddress",
  encryptedSecret: "<base64 ciphertext>",
  iv:              "<base64>",
  tag:             "<base64>",
  wrappedKey:      "<base64>",
  kmsKeyId:        "my-kms-key-id",
};

const txHash = await provider.send({
  token:     "ETH",
  network:   "ETH",
  toAddress: "0xRecipientAddress",
  amount:    "0.05",   // in ETH (human-readable)
  wallet:    walletData,
});

console.log("tx:", txHash);
// "0xabc123..."

// Same pattern for USDC on Polygon:
const usdcHash = await provider.send({
  token:     "USDC",
  network:   "POL",
  toAddress: "0xRecipientAddress",
  amount:    "10.00",
  wallet:    walletData,
});

Estimate fee before sending

const fee = await provider.estimateFee({
  token:     "ETH",
  network:   "ETH",
  toAddress: "0xRecipientAddress",
  amount:    "0.05",
  wallet:    walletData,
});

console.log(`Fee: ${fee.estimatedFee} ${fee.feeToken} (${fee.estimatedFeeInBaseUnit} ${fee.baseUnit})`);
// "Fee: 0.000021 ETH (21000000000000 wei)"

Get balances

const evmProvider = registry.getProviderByNetwork("ETH")!;

const balances = await evmProvider.getBalances({
  network:     "ETH",
  address:     "0xYourAddress",
  tokenFilter: undefined,   // omit to get all tokens (ETH + USDC + USDT)
});

// [
//   { token: "ETH",  balance: "1.234", balanceInBaseUnit: "1234000000000000000", baseUnit: "wei",        decimals: "18" },
//   { token: "USDC", balance: "50.0",  balanceInBaseUnit: "50000000",            baseUnit: "tokenUnits", decimals: "6"  },
//   { token: "USDT", balance: "0.0",   balanceInBaseUnit: "0",                   baseUnit: "tokenUnits", decimals: "6"  },
// ]

All providers and supported networks

import {
  BtcChainProvider,
  EvmChainProvider,
  SolChainProvider,
  TonChainProvider,
  TrxChainProvider,
} from "@chainberry/chainberry-wallet";

const registry = new WalletProviderRegistry([
  new EvmChainProvider(config, credentialUtils),  // ETH, POL, BNB  + USDC/USDT
  new SolChainProvider(config, credentialUtils),  // SOL             + USDC/USDT
  new TrxChainProvider(config, credentialUtils),  // TRX             + USDT
  new TonChainProvider(config, credentialUtils),  // TON
  new BtcChainProvider(config, credentialUtils),  // BTC
]);

registry.getAllNetworks();
// ["ETH", "POL", "BNB", "SOL", "SOL Devnet", "TRX", "TON", "BTC"]

WalletConfig defaults

All RPC URL fields are optional. Omitting them uses public endpoints (suitable for testnet/development):

| Key | Default | |---|---| | WALLET_ENV | "testnet" | | ETH_RPC_URL | https://ethereum-rpc.publicnode.com | | POL_RPC_URL | https://polygon-mainnet.gateway.tenderly.co | | BNB_RPC_URL | https://bsc.drpc.org | | SOL_RPC_URL | https://api.mainnet-beta.solana.com | | TRX_RPC_URL | https://api.trongrid.io | | TON_RPC_URL | https://toncenter.com/api/v2 | | TATUM_API_KEY | (required for BTC) |


AWS KMS key provider

import { KMSClient, EncryptCommand, DecryptCommand } from "@aws-sdk/client-kms";
import type { IKeyProvider } from "@chainberry/chainberry-wallet";

const kms = new KMSClient({ region: "eu-central-1" });

const kmsKeyProvider: IKeyProvider = {
  async wrapKey(aesKey, keyId) {
    const { CiphertextBlob } = await kms.send(new EncryptCommand({ KeyId: keyId, Plaintext: aesKey }));
    return Buffer.from(CiphertextBlob!).toString("base64");
  },
  async unwrapKey(wrappedKey, keyId) {
    const { Plaintext } = await kms.send(new DecryptCommand({
      KeyId: keyId,
      CiphertextBlob: Buffer.from(wrappedKey, "base64"),
    }));
    return Buffer.from(Plaintext!);
  },
};

NestJS integration

In NestJS wrap each provider with @Injectable() and resolve config from ConfigService. See the vault adapter pattern for the full reference implementation already wired in this monorepo.


React Native — private key in device secure store

Required polyfills

CredentialUtils uses Node's built-in crypto module (createCipheriv, randomBytes, etc.) and Buffer — neither exists in React Native's JS engine out of the box.

Expo Go not supportedreact-native-quick-crypto contains native code. You must use a development build (npx expo run:ios / npx expo run:android or EAS Build).

npx expo install react-native-quick-crypto buffer

app.json — register the config plugin so Expo links the native module:

{
  "expo": {
    "plugins": ["react-native-quick-crypto"]
  }
}

metro.config.js — create this file in your project root if it doesn't exist:

const { getDefaultConfig } = require('expo/metro-config');
const config = getDefaultConfig(__dirname);

config.resolver.extraNodeModules = {
  ...config.resolver.extraNodeModules,
  crypto: require.resolve('react-native-quick-crypto'),
  buffer: require.resolve('buffer'),
};

module.exports = config;

App entry point (index.js / App.tsx, before any other import):

import { install } from 'react-native-quick-crypto';
install(); // replaces global crypto with native implementation

import { Buffer } from 'buffer';
global.Buffer = global.Buffer ?? Buffer;

Without these steps CredentialUtils will throw Cannot find module 'crypto' at runtime.


npx expo install react-native-keychain

Implement IKeyProvider so the ephemeral AES-256 key never leaves hardware-backed storage (iOS Keychain / Android Keystore). The actual private key stays AES-encrypted in WalletData; only the wrapping key touches the device secure store.

import * as Keychain from 'react-native-keychain';
import {
  CredentialUtils,
  EvmChainProvider,
  WalletProviderRegistry,
  type IKeyProvider,
  type WalletConfig,
} from '@chainberry/chainberry-wallet';

const keychainKeyProvider: IKeyProvider = {
  async wrapKey(aesKey: Buffer, keyId: string): Promise<string> {
    await Keychain.setGenericPassword(keyId, aesKey.toString('base64'), {
      service: keyId,
      accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
    });
    // "wrappedKey" stored in WalletData is the service name used for retrieval
    return keyId;
  },
  async unwrapKey(_wrappedKey: string, keyId: string): Promise<Buffer> {
    const credentials = await Keychain.getGenericPassword({ service: keyId });
    if (!credentials) throw new Error(`No key in keychain for service: ${keyId}`);
    return Buffer.from(credentials.password, 'base64');
  },
};

const credentialUtils = new CredentialUtils(keychainKeyProvider);
const config: WalletConfig = { WALLET_ENV: 'mainnet' };

const registry = new WalletProviderRegistry([
  new EvmChainProvider(config, credentialUtils),
  // add more providers as needed
]);

Key generation

hybridEncrypt(plaintext, keyId) expects the private key as a UTF-8 string. Each chain has its own format:

| Chain | @trustwallet/wallet-core | Chain-specific lib | Expected string format | |---|---|---|---| | EVM (ETH/POL/BNB) | hdWallet.getKeyForCoin(CoinType.ethereum).data() | ethers.Wallet.createRandom().privateKey | hex, 0x prefix OK | | SOL | hdWallet.getKeyForCoin(CoinType.solana).data() | Keypair.generate().secretKey | hex, 32 or 64 bytes | | TRX | hdWallet.getKeyForCoin(CoinType.tron).data() | TronWeb.createAccount().privateKey | hex, no 0x | | TON | hdWallet.getKeyForCoin(CoinType.ton).data() | mnemonicToPrivateKey(...) | exactly 64 hex chars (32-byte seed) | | BTC | hdWallet.getKeyForCoin(CoinType.bitcoin).data() | ECPair.makeRandom() | hex string |

TrustWallet Core (used by the vault, covers all chains) returns raw bytes — convert uniformly:

import { initWasm } from '@trustwallet/wallet-core';

const { HDWallet, CoinType } = await initWasm();
const hdWallet = HDWallet.create(128, ''); // 128-bit entropy = 12-word mnemonic

// EVM
const evmKey = hdWallet.getKeyForCoin(CoinType.ethereum);
const rawPrivateKeyHex = '0x' + Buffer.from(evmKey.data()).toString('hex');
const address = hdWallet.getAddressForCoin(CoinType.ethereum);

For EVM-only apps, ethers.js is a lighter alternative:

import { Wallet } from 'ethers';

const wallet = Wallet.createRandom();
const rawPrivateKeyHex = wallet.privateKey; // "0x3a4b..." — 66 chars
const address = wallet.address;

Encrypt on creation, send on use

// On wallet creation — encrypt private key; AES wrapping key goes to device keychain
const keyId = `wallet-${address}`;  // unique per wallet — use address or a UUID, NOT userId (one user can have many wallets)
const encrypted = await credentialUtils.hybridEncrypt(rawPrivateKeyHex, keyId);
// Store { ciphertext, iv, tag, wrappedKey, kmsKeyId } in AsyncStorage or your DB
// wrappedKey here is just the keyId string (the keychain service name)

// On send — keychain is read automatically via unwrapKey inside the provider
const txHash = await registry.getProviderForSend('ETH', 'ETH')!.send({
  token: 'ETH', network: 'ETH',
  toAddress: '0xRecipient', amount: '0.05',
  wallet: { address, ...encrypted },
});

Biometric protection (optional)

Add accessControl to require Face ID / fingerprint before the AES key can be retrieved:

// wrapKey — add accessControl when storing
await Keychain.setGenericPassword(keyId, aesKey.toString('base64'), {
  service: keyId,
  accessible: Keychain.ACCESSIBLE.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
  accessControl: Keychain.ACCESS_CONTROL.BIOMETRY_ANY_OR_DEVICE_PASSCODE,
});

// unwrapKey — prompt appears automatically on retrieval
const credentials = await Keychain.getGenericPassword({
  service: keyId,
  authenticationPrompt: { title: 'Authenticate to sign transaction' },
});

Custom key provider (testing)

Any object satisfying IKeyProvider works:

import type { IKeyProvider } from "@chainberry/chainberry-wallet";

export const inMemoryKeyProvider: IKeyProvider = {
  wrapKey:   async (aesKey) => aesKey.toString("hex"),
  unwrapKey: async (wrappedKey) => Buffer.from(wrappedKey, "hex"),
};

Logger

All providers accept an optional ILogger as their third constructor argument. Any object with debug / warn / error / log methods works (NestJS Logger, winston, pino, etc.):

import { Logger } from "@nestjs/common";

const provider = new EvmChainProvider(config, credentialUtils, new Logger("EVM"));