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

@crewhaus/tool-onchain

v0.8.0

Published

Deterministic onchain tools: ABI encoding and decoding, EIP-55 address checksums, EIP-712 and EIP-191 hashing, unit conversion and fixed-point DeFi maths

Readme

@crewhaus/tool-onchain

The onchain arithmetic, offline.

| Tool | Answers | |---|---| | AbiEncodeCall | what are the exact bytes of this contract call | | AbiDecode | what does this returned hex actually say | | FunctionSelector | what is this function's selector, or this event's topic | | AddressCheck | is this address well-formed, and does its checksum hold | | TypedDataHash | what digest would a wallet sign for this message | | TokenUnits | what is this amount in base units, exactly | | DefiMath | minimum out, maximum in, price impact, share, health factor |

No RPC, no network, no keys. These are the calculations that must be right before a transaction is composed — and a transaction is irreversible, so "plausible" is not good enough.

Nothing here signs

No private key is accepted, read or handled anywhere in this package, and a test asserts that no schema has a field one could be passed to.

TypedDataHash computes the digest a wallet would sign. That is the useful half, because it lets a signature request be checked before it is approved: hash the message a human was actually shown and compare it against what the dapp asked for. A malicious dapp's whole trick is the gap between those two.

Verified against published vectors, not against itself

A codec tested only against its own output agrees with itself and with nothing on a chain. So:

  • ABI encoding is checked against real ERC-20 transfer calldata, byte for byte, and selectors against the ones every block explorer shows.
  • EIP-712 is checked against the specification's own worked example — the type hash, the domain separator, the struct hash and the final digest.
  • EIP-55 is checked against the addresses in that specification.
  • Keccak-256, in @crewhaus/tool-encode, is checked against the standard vectors. It is not SHA3-256: same permutation, different padding, and substituting one gives a plausible digest that is wrong for every Ethereum purpose. WebCrypto offers neither, which is exactly the trap.

The mistakes it exists to prevent

  • A misplaced offset. Dynamic values move to a tail and leave an offset behind, relative to the start of the enclosing tuple — not the call. A wrong offset produces calldata a node accepts and a contract misreads.

  • Alignment. Fixed bytes are left-aligned, integers right-aligned. bytes4 and uint32 holding "the same" value are different words.

  • A canonical signature that differs from what was typed. uint is uint256 in the string the selector is hashed over, so writing uint and hashing it literally gives a selector no contract answers to.

  • Precision. A uint256 does not fit in a double; a balance read as a number loses its low digits, and two balances one wei apart become the same value. Integers are bigint or decimal strings throughout, and a number past the safe range is refused rather than accepted after it has already lost precision.

  • 0.1 * 1e18. TokenUnits is string arithmetic, and it refuses an amount with more decimal places than the token has rather than dropping the extra digits.

  • The direction of a rounding error in a bound. A minimum is rounded down and a maximum up; the other way round rejects a swap that was inside tolerance or accepts one that was not. Slippage is basis points, never a percentage, because a percentage invites a decimal and a decimal invites a float.

  • Data that decodes to far more than itself. Return data comes from whoever deployed the contract, and nothing in the ABI format stops two offsets pointing at the same bytes: a few kilobytes whose heads all share one tail decode to millions of values. AbiDecode (and every package that decodes through it) refuses data that decodes to more than four times its own size — no encoder writes such data — and checks an array's length against the bytes that follow it before building anything. It also counts the values it produces, tuples and arrays included, and stops at what the data could hold for those types if each word were read once. An empty tuple () is refused as a type to decode: no Solidity type is one, and it reads no bytes, so a type string full of them multiplied every word of data on its own. Type strings are capped at 8,192 characters and 32 levels of nesting.

  • An address re-typed between two calls. AbiEncodeCall and TypedDataHash (message fields and verifyingContract alike) check every address they encode the way AddressCheck does: 0x and 40 hex characters, and a mixed-case address must pass its EIP-55 checksum. All-lowercase and all-uppercase hex carry no checksum and are accepted, as ethers and viem accept them.

An address with no checksum is reported as valid and as unverifiable, rather than letting valid: true be read as "no typo". A position with no debt has no health factor rather than an infinite one — Infinity would read as safe to a caller comparing against a threshold.

Multicall3, as library code

encodeAggregate3, decodeAggregate3 and decodeRevertData are exported from the package but register no tool: packing a batch is only useful to something that can dial a chain, and nothing here dials anything. They pack aggregate3((address,bool,bytes)[]) and read (bool,bytes)[] back.

Two properties are the reason they exist:

  • A failed sub-call is data. With allowFailure — which defaults to true — the batch succeeds and the failed entry comes back with success: false and its revert bytes. A batch whose own answer cannot be read throws instead, because that is a different event: there are no partial results.
  • Results are matched by position, and nothing in the return blob names the call it answers. A blob holding a different number of results than the batch sent is refused rather than zipped against the calls, since a balance read against the wrong token is worse than no balance.

Revert bytes are decoded where they say something: Error(string) gives its reason, Panic(uint256) the documented meaning of its code. An unrecognised selector keeps its hex and gets no message — a guess at a custom error is worse than the bytes, which are at least searchable.

The canonical Multicall3 address is a default, not a constant: the deterministic deploy is at the same address on most chains, which is not the same as all of them, so every entry point takes it as an argument.

What it does not do

  • It does not reach a chain. No RPC calls, no balances, no gas estimates, no broadcasting. Those need a node and belong elsewhere.
  • It does not sign or hold keys.
  • It does not know what a contract does. AbiDecode decodes the types you name; naming the wrong ones produces confident nonsense, which is why data that ends early is an error rather than a short answer. So is a word that is not the encoding of its type — a uint8 holding 256, an address or bytes4 with non-zero padding, an int8 that is not sign-extended — which is how Solidity's own decoder treats it, and what reading a uint256 slot as a uint8 looks like.
  • It does not fetch an ABI. Signatures come from the caller.
  • It does not price anything. DefiMath computes against a reference you supply; where that reference came from is your problem.