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

@jaw.id/frametx-kit

v0.1.1

Published

Read, build, hash, sign, price, simulate and broadcast EIP-8141 frame transactions as hegota-testnet accepts them.

Readme

frametx-kit

npm CI

TypeScript for EIP-8141 frame transactions as hegota-testnet (chain ID 8141) actually accepts them: the composed envelope that also carries EIP-8250 keyed nonces and EIP-8272 recent-root references.

Decode, build, hash, sign, price, dry-run and broadcast.

Install

bun add @jaw.id/frametx-kit viem

viem is a peer dependency, not a bundled one. frameActions extends a viem client, so your client and this library have to be the same viem.

Use

import { createPublicClient, http } from 'viem'
import { frameActions } from '@jaw.id/frametx-kit/viem'

const client = createPublicClient({
  transport: http('https://rpc1.privacy.ethrex.xyz'),
}).extend(frameActions)

const tx = await client.getFrameTransaction({ hash })
const receipt = await client.getFrameTransactionReceipt({ hash })

Building a transaction? Call assertValidFrameTx(tx) before encodeFrameTx(tx). The encoder itself does not validate, because sig_hash is defined over a re-encoding and a validating encoder would reject the live chain data this library is meant to survey.

import { assertValidFrameTx, encodeFrameTx, signFrameTx } from '@jaw.id/frametx-kit'

const signed = await signFrameTx(tx, privateKey)
assertValidFrameTx(signed)
const raw = encodeFrameTx(signed)

signFrameTx also takes an account instead of a key — anything with an address and a raw-digest sign, which covers viem's privateKeyToAccount and mnemonicToAccount, a toAccount source, and your own wrapper around a hardware wallet, an HSM or a remote signer. It signs every SECP256K1 entry whose msg is empty, and refuses if the entry's resolved signer is not that account's address.

import { privateKeyToAccount } from 'viem/accounts'

const signed = await signFrameTx(tx, privateKeyToAccount(privateKey))

// a remote signer needs nothing else:
const signed = await signFrameTx(tx, {
  address: '0x…',
  sign: ({ hash }) => myKms.signDigest(hash), // r||s||v, v as 27/28 or a bare 0/1
})

A raw-digest sign is required. signMessage will not do: it EIP-191-prefixes its argument, so the signature recovers to nothing. A JsonRpcAccount (a browser wallet) cannot sign a raw digest at all — both are refused with an error rather than producing a transaction the chain silently rejects.

P256 and ARBITRARY entries are left untouched; build those signatures yourself and let assertValidFrameTx check them. signFrameTx also handles one signer at a time, so a transaction with entries for two different signers needs the bytes assembled by hand.

Sending walks the strict path for you — assertValidFrameTx, encodeFrameTx, eth_sendRawTransaction — and checks that the hash the node returns is keccak256 of the bytes it was given. The receipt wait is frame-aware: a skipped frame comes back as 'skipped', not as a revert.

const hash = await client.sendFrameTransaction({ transaction: signed })
const receipt = await client.waitForFrameTransactionReceipt({ hash })

Passing every check here does not guarantee admission. On this chain the VERIFY prefix must call APPROVE, which a plain EOA sender cannot do, so a broadcastable transaction needs a sender contract and a funded key — both are yours, not this library's. Dry-run with client.simulateFrameTransaction({ raw }) before spending.

Two things that will bite you

Frame receipt status is three-valued'failure', 'success', 'skipped'. A skipped frame never executed and its gas was refunded. It is not a revert.

The chain's public RPC serves no raw transaction bytes. getFrameTransaction reads the node's decoded JSON, re-encodes it, and refuses to return anything whose re-encoding does not reproduce the transaction hash. What you get back is a transaction this library can put back on the wire byte-for-byte.

Gas and rule sets

Gas is priced under one of three rule sets, because the deployed binary and the pinned EIP text disagree:

| Rule set | Meaning | |---|---| | 'chain' | ethrex 31b532266, what the nodes actually run | | 'pins' | the pinned EIP text | | 'head' | current drafts, for anticipating the next re-genesis |

import { compareRuleSets, decodeFrameTx } from '@jaw.id/frametx-kit'
console.log(compareRuleSets(decodeFrameTx(raw), 'chain', 'pins'))

The head EIP-8272 draft alters the envelope, not just the price: the recent-root field is gone and references travel as a leading VERIFY frame against 0x0000000000000000000000000000000000008272, 72 bytes each. compareRuleSets prices whichever side names 'head' over toHeadShape(tx), which applies exactly that change as a transformation of the transaction, so a reference-carrying transaction is surveyed rather than refused.

import { toHeadShape } from '@jaw.id/frametx-kit'
compareRuleSets(tx, 'pins', 'head') // prices the head side over toHeadShape(tx)
toHeadShape(tx) // the same transform on its own; identity when no reference is carried

The synthetic frame claims zero execution and state. Zero state is normative: the draft pins limits.state for the recent root verifier frame, along with its mode, target, flags and value. limits.execution is the one figure it leaves open — that falls out of the STATICCALL and one SLOAD per tuple — so every limit-derived term of the head price is still a floor, fine to compare against and wrong to budget with. That is why frameTxGas(tx, 'head') still throws for a reference-carrying transaction rather than hand you a floor that reads like a budget. 'head' otherwise prices identically to 'pins'; the EIP-8250 change it models is an execution-time charge against limits.state, published as HEAD_KEYED_NONCE_STATE_GAS.

Every gas constant is written as its published figure rather than derived. ethrex's own suite missed its intrinsic dropping from 15000 to 12000 across 1372 tests by deriving the expected value from the constant under test.

Tests

bun run test        # hermetic, no network
bun run test:live   # hits rpc1.privacy.ethrex.xyz
bun run typecheck

The default suite runs entirely off checked-in fixtures. Those are real transactions captured with bunx tsx scripts/capture-fixtures.ts; each records the client version and genesis hash it was taken against, so a re-genesis fails loudly rather than silently.

Contributing

Contributions welcome. Read CONTRIBUTING.md first — this wire format has traps, and several things in src/ that look like code smells are load-bearing.

Docs

License

MIT