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

voidchain-web3

v0.6.2

Published

TypeScript Web3 client for VoidChain session-based JSON-RPC APIs.

Readme

voidchain-web3

TypeScript client for VoidChain's session-based JSON-RPC API.

The library uses VoidChain chain_* methods for login, queries, transfers, deployment, and contract execution. Contract calldata and return values are encoded and decoded with viem, so contract calls feel close to viem while still using VoidChain's current session model.

中文操作手册见 docs/操作文档.md。

Install

npm install voidchain-web3

Publish to npm / 发布到 npm

发布到 npm 前需要先编译,因为 npm 用户安装后实际使用的是编译后的 dist 文件,不是 src 源码。

npm.cmd test
npm.cmd run typecheck
npm.cmd run build
npm.cmd pack --dry-run

发布前还要确认 package.json 里的版本号是 npm 上没有发布过的新版本:

npm.cmd version patch
npm.cmd publish

如果已经手动修改过版本号,就不需要再执行 npm.cmd version patch,完成检查和构建后直接发布即可。

Clients

New projects should usually split public reads and wallet/session operations:

import {
  createVoidChainPublicClient,
  createVoidChainWalletClient,
} from 'voidchain-web3'

const publicClient = createVoidChainPublicClient({
  endpoint: 'https://<voidchain-endpoint>',
  sliceno: 0,
})

const walletClient = createVoidChainWalletClient({
  endpoint: 'https://<voidchain-endpoint>',
  sliceno: 0,
})

Existing code can keep using the combined client:

import { createVoidChainClient } from 'voidchain-web3'

const client = createVoidChainClient({
  endpoint: 'https://<voidchain-endpoint>',
  sliceno: 0,
})

createVoidChainClient() contains both Public Client and Wallet Client methods.

Register And Login

const registered = await walletClient.register({
  account: '<new-account>',
  password: '<password>',
})

const account = await walletClient.login({
  account: '<new-account>',
  password: '<password>',
})

console.log(registered.address)
console.log(account.address)

register() creates an account but does not log it in. Transaction methods and mnemonic export require an active login session.

Public Queries

const blockNumber = await publicClient.getBlockNumber({ sliceno: 0 })

const block = await publicClient.getBlockByNumber({
  number: Number(blockNumber),
  sliceno: 0,
})

const addressInfo = await publicClient.getAddressInfo({
  address: account.address,
  blockTag: 'latest',
  sliceno: 0,
})

const nonce = await publicClient.getTransactionCount({
  address: account.address,
  sliceno: 0,
})

console.log(block?.number)
console.log(addressInfo.Balance)
console.log(nonce)

Public pubChainQuery calls use JSON-RPC id: 1, so they are not affected by a logged-in session.

const chainId = await publicClient.getChainId() // number, queried from the node
const balance = await publicClient.getBalance({
  address: account.address,
  blockTag: 'latest',
  sliceno: 0,
}) // bigint in native base units (VoidChain native coin has 9 decimals)

const detail = await publicClient.getBlock({ number: 100, sliceno: 0 })
console.log(detail.parentHash, detail.stateRoot, detail.Txns)

const blocks = await publicClient.scanBlocks({ begin: 100, sliceno: 0 })
console.log(blocks[0]?.Senders, blocks[0]?.Data)

getBlock() now uses native queryBlock and returns full block details, including the node's Txns field. It is no longer an alias of getBlockByNumber(), which still returns a summary from queryBlocks. Native query failures are propagated. scanBlocks() performs one scanBlock request (up to 100 blocks per the 8.1 API), returns an array, and does not automatically poll or decode Base64 Data. Both height inputs must be nonnegative safe integers. The default HTTP transport keeps safe JSON integers as number and parses larger integers as bigint, so nanosecond timestamps and large native balances are not rounded. A custom transport is responsible for preserving numbers before returning its response.

Watch the latest height without duplicate callbacks:

const unwatchHeight = publicClient.watchBlockNumber({
  emitOnBegin: true,
  pollingIntervalMs: 3000,
  sliceno: 0,
  onBlockNumber(blockNumber) {
    console.log(blockNumber)
  },
})

For an indexer, resume from a stored inclusive height and persist nextBlock only after the batch has been processed:

const unwatchBlocks = publicClient.watchBlocks({
  fromBlock: Number(await loadCursor()),
  pollingIntervalMs: 3000,
  sliceno: 0,
  async onBlocks(blocks, { nextBlock }) {
    await saveBlocks(blocks)
    await saveCursor(nextBlock)
  },
  onError(error) {
    console.error(error)
  },
})

unwatchHeight()
unwatchBlocks()

watchBlocks() removes duplicate heights and emits only the contiguous range starting at fromBlock. If fromBlock is omitted, it starts after the current latest block. The callback's nextBlock is the inclusive height for resuming after a restart.

Transfer And Wait

const txHash = await walletClient.sendTransfer({
  from: account.address,
  to: '0x<receiver-address>',
  value: 1000000000n,
  sliceno: 0,
})

const receipt = await publicClient.waitForTransactionReceipt({
  hash: txHash,
  sliceno: 0,
  pollingIntervalMs: 3000,
  timeoutMs: null,
})

if (receipt.status === 2) {
  console.log('success')
}

timeoutMs: null means wait without a local timeout. Pending transactions are only queried again; the SDK never resends the original write transaction while waiting.

Legacy Cross-Space Transfer

The legacy cross-space flow uses two session-based writes. First call transsend() to debit the source space and create the outgoing record. After that transaction is confirmed, call transrecv() with the same sender, source nonce, asset, and value to credit the destination space.

const sendHash = await walletClient.transsend({
  value: 18n,
  sliceno: 0,
  destsliceno: 2,
})

const sendReceipt = await publicClient.waitForTransactionReceipt({
  hash: sendHash,
  sliceno: 0,
  timeoutMs: null,
})

if (sendReceipt.nonce === undefined) {
  throw new Error('VoidChain receipt did not include the source nonce')
}

const receiveHash = await walletClient.transrecv({
  sender: account.address,
  nonce: sendReceipt.nonce,
  value: 18n,
  sliceno: 2,
  sousliceno: 0,
})

Omit address for native TC, or pass the asset contract address for a token. transsend.sliceno is the source space; transrecv.sliceno is the destination space. sousliceno is the source space and keeps the VoidChain protocol's documented spelling. Both methods require an active VoidChain session and return a transaction hash. This SDK does not submit transrecv() automatically.

Raw Contract Call

Use call() when the DApp or wallet plugin already has raw calldata. The SDK returns raw Hex and does not need the ABI.

import { decodeFunctionResult, encodeFunctionData } from 'voidchain-web3'

const abi = [
  {
    type: 'function',
    name: 'balanceOf',
    stateMutability: 'view',
    inputs: [
      { name: 'account', type: 'address' },
      { name: 'id', type: 'uint256' },
    ],
    outputs: [{ name: '', type: 'uint256' }],
  },
] as const

const data = encodeFunctionData({
  abi,
  functionName: 'balanceOf',
  args: ['0x<owner-address>', 1n],
})

const returnData = await walletClient.call({
  from: account.address,
  to: '0x<contract-address>',
  data,
  sliceno: 0,
})

const balance = decodeFunctionResult({
  abi,
  functionName: 'balanceOf',
  data: returnData,
})

walletClient.call() and combined client.call() wrap VoidChain chain_queryTransaction and require a session and from. They return content.ret_data with a 0x prefix. Empty return data becomes "0x".

For a public raw call without a login session or ABI:

const raw = await publicClient.call({
  to: '0x1111111111111111111111111111111111111111',
  data: '0x12345678', // replace with the target function's encoded calldata
  sliceno: 0,
})

publicClient.call() uses eth_callSlice and returns raw Hex. from is optional; value accepts bigint, number, or Hex (use bigint for large amounts). Optional gas, gasPrice, and nonce use Hex. No historical block parameter is exposed because the 8.1 call API does not document one.

ABI Contract Helpers

const balance = await publicClient.readContract({
  address: '0x<contract-address>',
  abi,
  functionName: 'balanceOf',
  args: ['0x<owner-address>'],
  sliceno: 0,
})

const name = await walletClient.readContract({
  address: '0x<contract-address>',
  abi,
  functionName: 'name',
  args: [],
  from: account.address,
  sliceno: 0,
})

const txHash = await walletClient.writeContract({
  address: '0x<contract-address>',
  abi,
  functionName: 'transfer',
  args: ['0x<receiver-address>', 1000n],
  from: account.address,
  sliceno: 0,
})

publicClient.readContract() uses eth_callSlice and does not require a session. walletClient.readContract() uses VoidChain chain_queryTransaction and requires a login session. Contract helpers use viem ABI tools for calldata and return-value encoding.

Contract Events

Use getContractEvents() when you want decoded contract events instead of raw logs.

import {
  createVoidChainPublicClient,
  parseAbiItem,
} from 'voidchain-web3'

const erc20Abi = [
  parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 value)'),
]

const transfers = await publicClient.getContractEvents({
  address: '0x<erc20-contract>',
  abi: erc20Abi,
  eventName: 'Transfer',
  args: {
    from: '0x<sender-address>',
  },
  fromBlock: 1000n,
  toBlock: 'latest',
  sliceno: 0,
})

for (const event of transfers) {
  console.log(event.eventName)
  console.log(event.args)
  console.log(event.transactionHash)
}

getContractEvents() builds event topics from the ABI, calls eth_getLogsSlice, and decodes each log with viem. It returns an array because a block range can contain many matching events.

Use watchContractEvent() to poll new blocks and receive decoded events:

const unwatch = publicClient.watchContractEvent({
  address: '0x<erc20-contract>',
  abi: erc20Abi,
  eventName: 'Transfer',
  fromBlock: 'latest',
  pollingIntervalMs: 3000,
  sliceno: 0,
  onLogs(logs) {
    console.log(logs)
  },
  onError(error) {
    console.error(error)
  },
})

// Stop watching later.
unwatch()

The watcher keeps lastScannedBlock in memory. Persist it in your app or backend if you need to resume after a page refresh or process restart.

Common viem ABI helpers are re-exported from voidchain-web3:

import {
  decodeAbiParameters,
  decodeErrorResult,
  decodeEventLog,
  decodeFunctionData,
  decodeFunctionResult,
  encodeAbiParameters,
  encodeEventTopics,
  encodeFunctionData,
  getAbiItem,
  parseAbi,
  parseAbiParameters,
  parseAbiItem,
} from 'voidchain-web3'

import type {
  Abi,
  AbiEvent,
  AbiFunction,
  AbiParameter,
} from 'voidchain-web3'

When the ABI is declared as const (or inline), contract function names, arguments, return values, event names, filter arguments, and decoded event arguments are inferred by TypeScript. readContract() accepts only view and pure functions; writeContract() accepts only payable and nonpayable functions. A dynamically typed Abi retains the broader fallback types.

Deploy Contract

const txHash = await walletClient.deployContract({
  from: account.address,
  abi,
  bytecode: '0x60a0604052...',
  args: [],
  sliceno: 0,
})

const receipt = await publicClient.waitForTransactionReceipt({
  hash: txHash,
  sliceno: 0,
})

console.log(receipt.contractAddress)

Simulate Deploy Contract

Use encodeDeployData() to locally encode bytecode + constructor args, then pass the encoded deployment data to simulateDeployContract().

import {
  createVoidChainWalletClient,
  encodeDeployData,
} from 'voidchain-web3'

const walletClient = createVoidChainWalletClient({
  endpoint: 'https://<voidchain-endpoint>',
  sliceno: 0,
})

const account = await walletClient.login({
  account: '<account>',
  password: '<password>',
})

const data = encodeDeployData({
  abi,
  bytecode: '0x60a0604052...',
  args: [],
})

const simulation = await walletClient.simulateDeployContract({
  from: account.address,
  data,
  value: 0n,
  sliceno: 0,
})

console.log(simulation.returnData)
console.log(simulation.request)

encodeDeployData() is local encoding only: it does not access the chain, does not sign, and does not require a session. simulateDeployContract() asks a VoidChain node to execute against current chain state, but it does not deploy the contract, send a transaction, or mutate state. Deployment simulation intentionally omits to because the contract address does not exist yet.

You can also estimate deployment gas with eth_estimateGas by passing from + data + value and omitting to:

const gas = await publicClient.eth.estimateGas({
  from: account.address,
  data,
  value: '0x0',
})

Mnemonic APIs

VoidChain account mnemonic import/export and local mnemonic derivation are separate features.

Node Import And Export

const exported = await walletClient.exportMnemonic()

const mnemonicBase64 = exported.mnemonicBase64
const mnemonic = exported.mnemonic

exportMnemonic() calls VoidChain chain_exportKeystore with opcode=Account&subcode=exportMnem. Base64 is encoding, not encryption. Do not print, log, commit, or expose mnemonic values.

const restored = await walletClient.importMnemonic({
  account: '<account>',
  password: '<password>',
  mnemonicBase64: '<base64-encoded-mnemonic>',
})

Local Derivation

import {
  DEFAULT_ETHEREUM_PATH,
  createMnemonic,
  findDerivationPath,
  isValidMnemonic,
  mnemonicToAccount,
  mnemonicToAddress,
  mnemonicToPrivateKey,
} from 'voidchain-web3'

const mnemonic = createMnemonic()

if (!isValidMnemonic(mnemonic)) {
  throw new Error('Invalid mnemonic')
}

const account = mnemonicToAccount(mnemonic)

console.log(account.path) // m/44'/60'/0'/0/0
console.log(account.address)

The default derivation path is DEFAULT_ETHEREUM_PATH, which is MetaMask's common Ethereum path:

m/44'/60'/0'/0/0

You can derive another account by index:

const secondAddress = mnemonicToAddress(mnemonic, {
  addressIndex: 1,
})

Or use an explicit path. When path is provided, it takes priority over accountIndex and addressIndex.

const account = mnemonicToAccount(mnemonic, {
  path: "m/44'/60'/0'/0/1",
})

mnemonicToPrivateKey() is available for export, debugging, and migration flows:

const privateKey = mnemonicToPrivateKey(mnemonic)

Avoid passing private keys through business code or exposing them to DApps. Current VoidChain write transactions still use the session-based sendTransaction() flow, not local private-key signing.

findDerivationPath() can search common MetaMask-style account and address indexes:

const found = findDerivationPath(mnemonic, '0x<address>', {
  accountIndexEnd: 9,
  addressIndexEnd: 99,
})

console.log(found?.path)

Transport Options

const publicClient = createVoidChainPublicClient({
  endpoint: 'https://<voidchain-endpoint>',
  sliceno: 0,
  transportOptions: {
    timeoutMs: 10000,
    retryCount: 3,
    retryDelayMs: 150,
  },
})

Public queries use retry/backoff. State-changing operations such as register, login, transfers, deployment, and write-contract calls are not automatically retried.

Ethereum-Compatible RPC

Ethereum-compatible methods are available under client.eth or publicClient.eth.

const blockNumber = await publicClient.eth.blockNumberSlice({ sliceno: 0 })

const logs = await publicClient.eth.getLogsSlice({
  address: '0x<contract-address>',
  fromBlock: '0x0',
  toBlock: 'latest',
  topics: [],
  sliceno: 0,
})

VoidChain 8.1 slice methods are exposed with the same suffix:

eth.blockNumberSlice()              -> eth_blockNumberSlice
eth.getBlockByNumberSlice()         -> eth_getBlockByNumberSlice
eth.getTransactionCountSlice()      -> eth_getTransactionCountSlice
eth.sendRawTransactionSlice()       -> eth_sendRawTransactionSlice
eth.callSlice()                     -> eth_callSlice
eth.getBalanceSlice()               -> eth_getBalanceSlice
eth.getLogs()                       -> eth_getLogs
eth.getLogsSlice()                  -> eth_getLogsSlice

Older aliases such as eth.getSliceBalance() and eth.getSliceTransactionCount() are still available for compatibility.

Wallet Plugin Note

voidchain-web3 does not expose an EIP-1193 request() provider. A browser wallet plugin should implement provider method dispatch, origin permissions, confirmation UI, and page/service-worker communication itself.

A plugin can map:

eth_call            -> walletClient.call()
eth_sendTransaction -> walletClient.sendTransaction()

The plugin should never expose session ids, passwords, or mnemonics to DApps.

Errors

The client exports typed errors:

  • VoidChainRpcError
  • VoidChainResponseError
  • VoidChainContractError
  • VoidChainSessionError
  • VoidChainTimeoutError
  • VoidChainAbortError

VoidChain ret !== "0" responses throw VoidChainResponseError. Contract execution errors returned in content.err throw VoidChainContractError.

Local Signing

Local signing uses viem accounts and does not require a VoidChain session.

import {
  VOIDCHAIN_TESTNET_CHAIN_ID,
  mnemonicToAccount,
  personalSign,
  signAuthorization,
  signTypedDataV4,
} from 'voidchain-web3'

const account = mnemonicToAccount('<mnemonic>')

const messageSignature = await personalSign({
  account,
  message: 'hello voidchain',
})

const typedSignature = await signTypedDataV4({
  account,
  domain: {
    name: 'VoidChain App',
    version: '1',
    chainId: VOIDCHAIN_TESTNET_CHAIN_ID,
  },
  types: {
    Message: [{ name: 'contents', type: 'string' }],
  },
  primaryType: 'Message',
  message: {
    contents: 'Hello',
  },
})

const authorization = await signAuthorization({
  account,
  contractAddress: '0x<delegate-contract>',
  nonce: 0,
})

VoidChain chain IDs:

testnet: 36500
mainnet: 36000