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

purechain-sdk

v0.0.2

Published

Client library for the PureChain network family (geth, besu, dag variants)

Readme

purechain

A client library for the PureChain network family. Three variants — geth, besu, dag — behind one interface, so application code does not change when the variant does.

Status: the geth variant (the live public PureChain network) is implemented and tested against the real chain. besu and dag are stubs with the full interface in place; every call raises NotImplementedError naming the variant.

npm install purechain-sdk

Published as purechain-sdk on npm, matching the Python package of the same name on PyPI. The bare purechain name was taken in 2019 by an unrelated package. Note that the Python one is imported as purechain, since a distribution name and an import name are separate things there.

Usage

import { createClient, PrivateKeySigner } from "purechain-sdk";

const client = createClient({ signer: new PrivateKeySigner(process.env.PRIVATE_KEY!) });

const hash = await client.sendTransaction({ to: "0xabc...", value: 1n });
const receipt = await client.waitFor(hash);
console.log(receipt.status, receipt.blockNumber);

Point it at your own node, or at a private deployment with its own genesis:

const client = createClient({
  url: "http://localhost:8545",
  network: { name: "devnet", chainId: 424242n },
});

What this library does differently

PureChain is a permissioned, free-gas EVM network, and three of its properties break assumptions that general-purpose Ethereum libraries build in. Each one is handled here by default rather than left to the caller.

Fees are zero, and the oracle is never consulted. Transactions are built with zero fees and signed locally; eth_gasPrice is not called. A node started without the --gpo.* flags reports a non-zero price on a chain whose base fee is pinned to zero, so trusting the oracle is how callers end up overpaying — or getting rejected. Override with fees if a network ever charges:

createClient({ fees: { kind: "tip", tipWei: 1_000_000_000n } });

Blocks are not produced on a fixed interval. Smart Auto Mining seals only while transactions are pending and pauses when the network is idle, so a static head is healthy rather than stalled. waitFor therefore defaults to inclusion, not a confirmation count — waiting for depth on a chain that goes quiet right afterwards would never resolve. Depth is opt-in and always bounded:

await client.waitFor(hash);                                   // inclusion
await client.waitFor(hash, { until: "final", confirmations: 3 });

A timeout against a head that never moved raises ChainIdleError rather than TimeoutError, so "the network is quiet" is distinguishable from "something went wrong".

There is no replace-by-fee. A pending transaction cannot be bumped or cancelled at zero fee — the pool requires a strictly higher fee, and nothing is higher than zero. Sends are serialised per sender so concurrent calls cannot collide on a nonce, because the usual escape hatch does not exist here.

Capabilities are detected, not assumed

Which JSON-RPC namespaces are available is a property of the node you connected to, not of the variant. The public endpoints run --http.api eth,net,web3, so clique_*, txpool_*, admin_* and debug_* are absent even though purechain-geth implements them.

const caps = await client.capabilities();
caps.zeroFee;                     // true
caps.replaceByFee;                // false
caps.subscriptions;               // false — public endpoints are HTTP-only
caps.has("clique_getSigners");    // false on the public RPC
caps.validatorApi;                // "clique" | "qbft" | "ibft" | null

Anything outside the core surface is reachable through the raw escape hatch, once you have checked for it:

if (caps.has("clique_getSigners")) {
  const signers = await client.rpc("clique_getSigners", []);
}

Events

eth_subscribe is unavailable on the public endpoints, so watching polls by default and tolerates idle gaps. Delivery is ordered and gap-free.

const sub = await client.watchLogs(
  contract.filter("Transfer"),
  (log) => console.log(contract.decodeLog(log)?.args),
);
await sub.close();

Contracts

import { Contract, deployContract } from "purechain-sdk";

const token = new Contract("0xabc...", abi, client);
const balance = await token.read<bigint>("balanceOf", [address]);
await token.writeAndWait("transfer", [to, 100n]);

const { contract, address } = await deployContract(client, { abi, bytecode });

Gas is free, so an account with a zero balance can deploy and call. No balance pre-check is performed; a balance is only needed to move value.

Development

npm install
npm test           # offline unit tests
npm run test:live  # plus read-only tests against the public network
npm run typecheck  # src and tests
npm run build

package-lock.json pins exact versions; commit it. Use npm install <pkg>@latest to move a dependency forward deliberately, rather than letting a fresh install drift on its own.

There is a third suite that broadcasts real transactions to the public network. It is behind its own flag so it never runs by accident:

PURECHAIN_LIVE_WRITE=1 node --test test/live/write.test.ts

It generates a throwaway key and sends zero-value transfers to itself. No funding is needed — gas is free, which is precisely what the test proves.

Source imports carry .ts specifiers, so the test suite runs directly on Node with no build step; tsc rewrites them to .js on emit.

Design

These are the rules the library is built on. New code should follow them.

Layout

src/
  index.ts      public API
  wallet.ts     keys, mnemonics, keystore, signature verification
  units.ts      PCN <-> wei
  address.ts    validate, checksum, compare
  abi.ts        offline encode / decode
  metrics.ts    throughput, block timing, gas utilisation (reads)
  benchmark.ts  latency and throughput under load (BROADCASTS)
  core/         variant-agnostic types, errors, fee policy, capability detection
  client/       the PureChainClient interface and the createClient factory
  variants/
    evm/        shared EVM engine, signing, contracts, waiting, watching
    geth/       purechain-geth — implemented
    besu/       stub
    dag/        stub

The root modules are the namespaces from rule 9. metrics only reads; benchmark writes to the chain, which is why they are separate. They sit beside core, client and variants because they are top-level concerns, not a sub-part of any of them. The Python package has the same file names in the same places.

1. One interface, three variants

Every variant implements the same PureChainClient interface. That interface holds only what all three can genuinely do — the intersection, not the union.

This is why waitFor is built around a finality level, with a confirmation count only as an opt-in extra: a DAG has no block depth to count. Anything one variant can do beyond the interface sits behind a capability check, or on that variant's own class.

2. Three layers, one direction

core → client → variants. Code in core never imports from variants.

Nothing in core assumes blocks, a block interval, or an EVM. That single constraint is what keeps the DAG variant possible behind the same interface.

3. Detect, don't assume

What a node can do is a property of the node, not the variant. The same purechain-geth build exposes clique_* on your own machine and not on the public RPC.

Capabilities are read once when the client connects, then cached. Check them before using anything outside the core surface.

4. Defaults match this network, not the ecosystem

Zero fees, and the gas-price oracle is never called. Wait for inclusion, not depth. Poll for events instead of subscribing.

Each of those is unusual for an Ethereum library and correct here. Where the network forbids something outright — replace-by-fee — the library says so with a named error rather than failing in a confusing way.

5. Wrap the cryptography, own the policy

ethers does three jobs: signing, ABI coding, transport. This library decides fees, nonces, waiting, and retries.

ethers types never appear in the public API. That is what lets the Python port sit on web3.py and still behave identically.

6. Always leave an escape hatch

Blocks, transactions, receipts and logs all carry raw — the node's response untouched, including fields the typed surface does not name. Clients with a real backend also expose rpc(method, params), which reaches any method at all. Stubs do not, because they have nothing to call.

A typed API you cannot step outside of is a dead end on a network that adds its own methods.

7. Errors carry codes

Branch on err.code, never on the message text. Messages are written for humans and will change; codes will not. The code strings are identical in both languages.

8. Stubs are honest

An unfinished variant still exposes the whole interface, and every call fails with an error naming the variant. You find out at the call site, not three frames deep in a TypeError.

9. Objects hold state, namespaces hold pure functions

A client owns a connection, so it is an object. Creating a key or parsing an amount needs no state, so those are namespaces:

import { address, units, wallet } from "purechain-sdk";

const signer = wallet.create();          // no network needed
const wei = units.parsePCN("1.5");
const ok = address.isValid(someString);

There are four: wallet, units, address, and abi. Binding an ABI to a deployed address needs a client, so that stays on the Contract class rather than becoming a fifth namespace — one way to do it, not two.

10. The two libraries match

Same folders, same module names, same method names, same error codes. The only intended difference is casing: camelCase here, snake_case in Python.

A change to one library is a change to both.