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/berry-signer

v1.0.5

Published

Pure signing library for multiple blockchain networks — EVM, BTC, LTC, SOL, TRX, XRP, TON

Readme

BerrySigner

Pure signing library for multiple blockchain networks. No network calls — takes an unsigned transaction and a private key, returns a signed transaction ready for broadcast.

Can run anywhere: browser, mobile wallet, browser extension, Node.js backend, or React Native.

Installation

npm install @chainberry/berry-signer

Install only the peer dependencies you need (one per chain):

# EVM (ETH, BNB, AVAX, ARB, OP, BASE, SONIC, POL, ...)
npm install ethers

# Bitcoin
npm install bitcoinjs-lib ecpair tiny-secp256k1

# Litecoin
npm install bitcoinjs-lib ecpair tiny-secp256k1

# Solana
npm install @solana/web3.js

# TRON
npm install tronweb

# XRP Ledger
npm install xrpl tiny-secp256k1

# TON
npm install @ton/core @ton/crypto @ton/ton

Supported Chains

| Chain | Function | Returns | |-------|----------|---------| | EVM (ETH, BNB, POL, AVAX, ARB, OP, BASE, SONIC, …) | signEvmTransaction | Signed tx hex string | | Bitcoin | signBtcTransaction | Raw tx hex string | | Litecoin | signLtcTransaction | Raw tx hex string | | Solana | signSolTransaction | Signed tx base64 string | | TRON | signTrxTransaction | Signed tx JSON string | | XRP Ledger | signXrpTransaction | Signed tx blob hex string | | TON | signTonTransaction | { bocBase64, txHash } |


Private Key Format

All functions accept the private key as a hex string, with or without 0x prefix — unless noted otherwise.

| Chain | Accepted formats | |-------|-----------------| | EVM | 0x... or plain hex | | Bitcoin / Litecoin | Plain hex (32 bytes) | | Solana | Hex (32 or 64 bytes) or base58 | | TRON | Plain hex (no 0x) | | XRP | Plain hex (32 bytes) | | TON | Hex (32 bytes), with or without 0x |


API Reference

EVM — signEvmTransaction

import { signEvmTransaction, EvmUnsignedTx } from "@chainberry/berry-signer";

const signedTxHex: string = await signEvmTransaction(privateKeyHex, unsignedTx);

EvmUnsignedTx type:

type EvmUnsignedTx = {
  to: string;         // recipient address
  chainId: number;    // e.g. 1 (ETH), 56 (BNB), 42161 (ARB)
  nonce: number;      // from eth_getTransactionCount
  gasLimit: string;   // estimated gas, as decimal string

  value?: string;     // amount in wei, as decimal string (omit for token transfers)
  data?: string;      // hex-encoded calldata (required for ERC-20 transfers)

  // EIP-1559 (preferred — use these if the network supports them):
  maxFeePerGas?: string;          // decimal string, in wei
  maxPriorityFeePerGas?: string;  // decimal string, in wei

  // Legacy (fallback — use if maxFeePerGas is not available):
  gasPrice?: string;  // decimal string, in wei
};

Returns a serialized signed transaction hex string, ready for eth_sendRawTransaction.

Note: All numeric fields (gasLimit, value, gasPrice, etc.) are strings, not BigInt — this keeps the type JSON-serializable and avoids precision loss across network boundaries.


Bitcoin — signBtcTransaction

import { signBtcTransaction } from "@chainberry/berry-signer";

const signedTxHex: string = signBtcTransaction(privateKeyHex, unsignedPsbtBase64, isMainnet);

| Param | Type | Description | |-------|------|-------------| | privateKeyHex | string | 32-byte private key as hex | | unsignedPsbtBase64 | string | PSBT (Partially Signed Bitcoin Transaction) in base64 — build with UTXOs and outputs | | isMainnet | boolean | true for mainnet, false for testnet |

Returns the raw transaction hex ready for broadcast.


Litecoin — signLtcTransaction

import { signLtcTransaction } from "@chainberry/berry-signer";

const signedTxHex: string = signLtcTransaction(privateKeyHex, unsignedPsbtBase64, isMainnet);

Same signature as signBtcTransaction. Uses Litecoin-specific network params (bech32 prefix ltc / tltc).


Solana — signSolTransaction

import { signSolTransaction } from "@chainberry/berry-signer";

const signedTxBase64: string = signSolTransaction(privateKey, unsignedTxBase64);

| Param | Type | Description | |-------|------|-------------| | privateKey | string | 32-byte seed (hex or base58) or 64-byte keypair (hex or base58) | | unsignedTxBase64 | string | Serialized Transaction object in base64 — must already have recentBlockhash set |

Returns a base64-encoded signed transaction, ready for sendRawTransaction.

Note: Solana is the only chain that accepts base58 private keys (Phantom wallet export format). Both 32-byte seeds and 64-byte keypairs are accepted.


TRON — signTrxTransaction

import { signTrxTransaction } from "@chainberry/berry-signer";

const signedTxJson: string = await signTrxTransaction(privateKey, unsignedTx, rpcUrl);

| Param | Type | Description | |-------|------|-------------| | privateKey | string | 32-byte private key as hex (no 0x) | | unsignedTx | unknown | Raw transaction object from transactionBuilder — TronWeb format | | rpcUrl | string | Any reachable TRON node URL (e.g. https://api.trongrid.io) |

Returns a JSON string of the signed transaction object.

Note: rpcUrl is required by the TronWeb SDK constructor but no network call is made during signing. You can pass any reachable TRON node — it will not be contacted.

Expiration: TRON transactions expire 60 seconds after creation by default. If you're signing client-side and broadcasting server-side with any delay, extend the expiration before signing using tronWeb.transactionBuilder.extendExpiration(tx, 600) (10 minutes).


XRP Ledger — signXrpTransaction

import { signXrpTransaction } from "@chainberry/berry-signer";
import type { Payment } from "xrpl";

const signedTxBlob: string = signXrpTransaction(privateKeyHex, preparedTx);

| Param | Type | Description | |-------|------|-------------| | privateKeyHex | string | 32-byte private key as hex | | preparedTx | Payment | XRPL Payment object with Sequence, Fee, and LastLedgerSequence already set |

Returns a hex-encoded signed transaction blob ready for submit.

Note: The Payment type is from the xrpl package. You must fill in Sequence, Fee, and LastLedgerSequence before signing (via xrpl's autofill or manually from network data).


TON — signTonTransaction

import { signTonTransaction, SignedTonTx } from "@chainberry/berry-signer";

const result: SignedTonTx = signTonTransaction(privateKeyHex, {
  toAddress,
  amount,   // in TON (e.g. "1.5"), NOT in nanoton
  seqno,    // fetch from network first; use 0 for uninitialized wallets
  memoId,   // optional: memo string (for exchange deposits, CEX tagging)
});

// result.bocBase64 — signed BOC, ready for broadcast
// result.txHash   — pre-computed tx hash (useful as broadcast fallback identifier)

SignedTonTx type:

type SignedTonTx = {
  bocBase64: string;  // base64-encoded signed bag-of-cells
  txHash: string;     // hex-encoded transaction hash
};

Uninitialized wallet: If seqno === 0, the wallet init cell is automatically included in the BOC. This is required for a wallet that has never sent a transaction (newly funded address).

txHash usage: TON's sendBoc API does not return a transaction hash. txHash is pre-computed from the cell hash before broadcast and can be used to look up the transaction on-chain.


Full Examples

EVM — Native transfer

import { ethers } from "ethers";
import { signEvmTransaction } from "@chainberry/berry-signer";

const provider = new ethers.JsonRpcProvider("https://arb1.arbitrum.io/rpc");

// Fetch network data (read-only — no private key needed yet)
const [feeData, nonce] = await Promise.all([
  provider.getFeeData(),
  provider.getTransactionCount(fromAddress, "latest"),
]);

const unsignedTx = {
  to: toAddress,
  chainId: 42161, // Arbitrum One
  nonce,
  gasLimit: "21000",
  maxFeePerGas: feeData.maxFeePerGas!.toString(),
  maxPriorityFeePerGas: feeData.maxPriorityFeePerGas!.toString(),
  value: ethers.parseEther("0.01").toString(),
};

// Sign locally — private key never leaves this environment
const signedTx = await signEvmTransaction(privateKeyHex, unsignedTx);

// Send signed tx to server for broadcast (or broadcast directly)
await fetch("/api/broadcast", {
  method: "POST",
  body: JSON.stringify({ signedTx, network: "arbitrum" }),
});

EVM — ERC-20 token transfer

import { ethers } from "ethers";
import { signEvmTransaction } from "@chainberry/berry-signer";

const provider = new ethers.JsonRpcProvider("https://arb1.arbitrum.io/rpc");
const usdcAddress = "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"; // USDC on Arbitrum
const erc20Interface = new ethers.Interface(["function transfer(address,uint256)"]);

// Build calldata for ERC-20 transfer
const data = erc20Interface.encodeFunctionData("transfer", [
  toAddress,
  ethers.parseUnits("10.0", 6), // USDC has 6 decimals
]);

const [feeData, nonce, gasLimit] = await Promise.all([
  provider.getFeeData(),
  provider.getTransactionCount(fromAddress, "latest"),
  provider.estimateGas({ from: fromAddress, to: usdcAddress, data }),
]);

const unsignedTx = {
  to: usdcAddress,          // send to the token contract, not the recipient
  chainId: 42161,
  nonce,
  gasLimit: gasLimit.toString(),
  maxFeePerGas: feeData.maxFeePerGas!.toString(),
  maxPriorityFeePerGas: feeData.maxPriorityFeePerGas!.toString(),
  value: "0",               // no ETH value for token transfers
  data,                     // encoded transfer(address,uint256)
};

const signedTx = await signEvmTransaction(privateKeyHex, unsignedTx);

TON — Native transfer

Step 1 — Fetch seqno from network:

const response = await fetch("https://toncenter.com/api/v2/runGetMethod", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ address: walletAddress, method: "seqno", stack: [] }),
});
const data = await response.json();

// exit_code !== 0 means wallet is not yet deployed — use seqno 0
const seqno = data.result?.exit_code === 0
  ? parseInt(data.result.stack[0][1], 16)
  : 0;

Step 2 — Sign locally:

import { signTonTransaction } from "@chainberry/berry-signer";

const { bocBase64, txHash } = signTonTransaction(privateKeyHex, {
  toAddress: "UQA...",   // recipient TON address
  amount: "1.5",         // in TON, not nanoton
  seqno,                 // 0 = wallet not yet deployed, init is included automatically
  memoId: "12345",       // optional: memo for exchange deposits
});

Step 3 — Broadcast:

await fetch("https://toncenter.com/api/v2/sendBoc", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ boc: bocBase64 }),
});

// txHash can be used to track the transaction on-chain
console.log("Track tx:", `https://tonscan.org/tx/${txHash}`);

Bitcoin — Native transfer

import { signBtcTransaction } from "@chainberry/berry-signer";

// unsignedPsbtBase64 is built by your server using UTXOs + fee rate
// (fetched from mempool.space or similar indexer)
const signedTxHex = signBtcTransaction(privateKeyHex, unsignedPsbtBase64, true);

// Broadcast via your server or directly to a BTC node
await fetch("/api/broadcast", {
  method: "POST",
  body: JSON.stringify({ signedTx: signedTxHex, network: "bitcoin" }),
});

Solana — Native transfer

import { signSolTransaction } from "@chainberry/berry-signer";

// unsignedTxBase64 is built by your server with recentBlockhash + instructions
const signedTxBase64 = signSolTransaction(privateKey, unsignedTxBase64);

await fetch("/api/broadcast", {
  method: "POST",
  body: JSON.stringify({ signedTx: signedTxBase64, network: "solana" }),
});

Design

The private key never leaves the signing environment. The client (or secure signing service) prepares and signs the transaction, then sends only the signed tx for broadcast.

Signing environment                           Broadcast environment
  │                                              │
  │── fetch nonce / seqno / fee from RPC ───────>│ (or fetched directly by signer)
  │                                              │
  │── build unsigned tx                          │
  │── sign with private key                      │
  │                                              │
  │── send signed tx only ──────────────────────>│── broadcast to network
  │                                              │
  │   (private key never crosses this boundary)  │

This separation makes BerrySigner useful in trust-minimized architectures — hardware wallets, MPC signers, browser extensions, or any environment where the signing key cannot be exposed to network-connected servers.


Chain IDs (EVM)

| Network | Chain ID | |---------|----------| | Ethereum | 1 | | BNB Smart Chain | 56 | | Polygon | 137 | | Avalanche C-Chain | 43114 | | Arbitrum One | 42161 | | Optimism | 10 | | Base | 8453 | | Sonic | 146 |


License

MIT