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

@maxencerb/evs

v0.0.8

Published

Typed EVM read scripts in plain TypeScript: a callback builder compiled to runtime bytecode, executed via eth_call (deployless or state-override) with full viem inference

Readme

@maxencerb/evs

Typed EVM read scripts in plain TypeScript. Write a callback against a small builder API; evs compiles it to EVM bytecode and runs it through a single eth_call — no contract to deploy, no multicall glue, and the result is fully typed end-to-end through viem's inference. Unlike a multicall, values flow between calls on-chain: pool.token0()token0.symbol() is one round trip, not two.

Full documentation: https://evs.maxencerb.com — design docs and runnable examples: https://github.com/maxencerb/evs

Install

bun add @maxencerb/evs viem
# or: npm install @maxencerb/evs viem

ESM-only. TypeScript ≥ 5.5 (strict); peer dependency viem >= 2.14.1; Node ≥ 20.19 or Bun.

Example

import { evscript, t } from '@maxencerb/evs';
import { createPublicClient, erc20Abi, http } from 'viem';
import { mainnet } from 'viem/chains';

import { uniswapV3PoolAbi } from './abis'; // any `as const` ABI fragment

const poolMeta = evscript(
  { name: 'poolMeta', args: [t.address, t.address] },
  // args arrive as positional params after `s`, in declaration order
  (s, pool, user) => {
    const token0 = s.read({ address: pool, abi: uniswapV3PoolAbi, functionName: 'token0' });
    //    ^? Expr<'address'>  — feeds the next call ON-CHAIN
    const slot0 = s.read({ address: pool, abi: uniswapV3PoolAbi, functionName: 'slot0' });
    const symbol0 = s.read({ address: token0, abi: erc20Abi, functionName: 'symbol' });
    const dec = s.tryRead({ address: token0, abi: erc20Abi, functionName: 'decimals' });
    const decimals0 = s.select(dec.success, dec.value, 18); // default when the call fails
    const bal0 = s.read({
      address: token0,
      abi: erc20Abi,
      functionName: 'balanceOf',
      args: [user],
    });
    return s.return({ token0, symbol0, tick: slot0[1], decimals0, bal0 });
  },
);

const client = createPublicClient({ chain: mainnet, transport: http() });
const out = await client.readContract({
  ...poolMeta.compile().toViem(), // { abi, code } — deployless eth_call
  functionName: 'poolMeta',
  args: [pool, user], // typed readonly [pool: `0x${string}`, user: `0x${string}`]
});
// out: { token0: `0x${string}`; symbol0: string; tick: number; decimals0: number; bal0: bigint }

No as const on the script, no codegen step: the script is its own literal-typed ABI.

Execution modes

const compiled = poolMeta.compile();

// Deployless (default): plain 2-parameter eth_call — works on every standard RPC provider.
await client.readContract({ ...compiled.toViem(), functionName, args });

// State override: stable address(this), controllable msg.sender via `account`,
// composable with further overrides. Uses eth_call's third parameter — supported by
// geth/anvil/QuickNode/publicnode; NOT documented on Alchemy/Infura eth_call.
await client.readContract({
  ...compiled.toViem({ mode: 'stateOverride' }),
  functionName,
  args,
  account: caller,
});

Both modes are just eth_call: historical reads via blockNumber work, nothing is ever deployed, and gas is bounded only by the node's eth_call cap.

[!WARNING] s.env('caller') / s.env('address') are execution-frame-dependent. In the default deployless mode the script runs inside viem's wrapper: s.env('caller') is the wrapper contract (never your account) and s.env('address') is a per-script counterfactual CREATE2 address — neither is controllable, and a read like balanceOf(s.env('caller')) silently returns the wrapper's (zero) balance. Caller-relative reads require toViem({ mode: 'stateOverride' }) with the account call parameter; there is no deployless workaround. compile() warns (ENV_FRAME_DEPENDENT via onDiagnostic) when a script uses these env ops. timestamp/blocknumber/chainid are identical in both modes.

The one rule to remember

[!WARNING] Native JS if/for/&& does NOT branch on EVM values. The builder callback runs once, at build time; chain values are typed Expr handles. if (someExpr) is always truthy and records the branch unconditionally — JS cannot trap it. Use s.if / s.while / s.for / s.select for runtime values; plain JS control flow only over build-time values. Other misuses (x + 1, `${x}`, JSON.stringify(x)) throw EvsStagingError at the offending line, and the typescript/strict-boolean-expressions lint closes the truthiness gap.

More

  • Checked arithmetic (solc ≥ 0.8 Panic semantics), checked narrowing conversions.
  • Calls split by mutability: s.read (STATICCALL, view/pure), s.call (CALL, nonpayable — Uniswap quoters and other non-static reads), and s.simulate (a CALL write dry-run whose state is rolled back, yet whose return value is read back). Each has a try* variant → { success, value } with safe zero defaults; strict callee reverts bubble verbatim.
  • Loops over runtime arrays + s.newArray collection — the multicall replacement.
  • compile({ evmVersion: 'paris' | 'shanghai' | 'cancun' }) for pre-Shanghai chains.
  • Inspectable artifact: disassemble().format(), sourceMap, explainRevert(data) mapping reverts back to your source lines.

Docs, examples and the full API: https://github.com/maxencerb/evs

License

MIT © Maxence Raballand