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

@sigsafe/core

v0.1.0

Published

Decode any signable EVM payload into a structured, human-readable intent with risk flags.

Readme

@sigsafe/core

Decode any signable EVM payload into a structured, human-readable intent — with risk flags — before you sign.

In 2025, signature-phishing drained $83.85M across 106,106 victims. The attacks don't steal keys; they trick users into signing away funds — an unlimited Permit, a setApprovalForAll, an EIP-7702 delegation — shown as an opaque blob the user can't read. @sigsafe/core decodes the static structure of the payload itself (the actual bytes being signed), so there's no simulation to spoof and no RPC round-trip required.

It is harm-reduction, not a firewall. A decoder tells you what a payload is, not every downstream effect. Pair it with simulation for full coverage.

Install

npm install @sigsafe/core
# viem is a peer-level dependency and is pulled in automatically

Quickstart

import { decode } from "@sigsafe/core";

// A malicious unlimited Permit signature (EIP-712 typed data)
const intent = await decode(typedData, { chainId: 1 });

intent.summary;
// "Off-chain permit (no gas): let 0xdead…beef spend UNLIMITED USD Coin from your wallet."
intent.risk;    // "CRITICAL"
intent.action;  // "PERMIT"
intent.flags;   // [{ id: "unlimited-approval", severity: "CRITICAL", confidence: "high", ... }]

decode() never throws on malformed input — it returns an UNKNOWN intent with a parse-error flag instead.

What it accepts

| Input | Example | | --- | --- | | EIP-712 typed data | eth_signTypedData_v4 payload (object or JSON string) | | EIP-2612 / DAI / Permit2 permits | the typed-data permit standards | | EIP-7702 authorizations | { chainId, address, nonce, r?, s?, yParity? } | | Raw transactions | 0x02… (EIP-1559), legacy, etc. | | Calldata | the data field — approve, transfer, setApprovalForAll, … | | Raw transaction objects | { to, data, value, chainId } | | personal_sign messages | plain text, SIWE logins, or raw hashes |

The output: DecodedIntent

interface DecodedIntent {
  summary: string;        // one-sentence plain English, safe to show a user
  action: Action;         // PERMIT | TOKEN_APPROVAL | DELEGATION | ...
  risk: RiskLevel;        // SAFE | INFO | WARNING | CRITICAL (max across flags)
  flags: RiskFlag[];      // every risk raised, sorted by severity
  inputType: InputType;
  details: IntentDetails; // structured, discriminated by `kind`
  raw: string;
  chainId?: number;
}

Each RiskFlag carries a confidence ("low" | "medium" | "high") so a consumer can tune its own threshold — show everything in a wallet UI, but block only CRITICAL + high in an automated bot — instead of treating every flag equally.

Risk rules

| Rule | Catches | Severity | | --- | --- | --- | | known-drainer | spender/delegate on a blocklist (or your customBlocklist) | CRITICAL | | eip7702-delegation | EIP-7702 account delegation | CRITICAL | | permit-to-eoa | approval/permit to a wallet, not a contract (needs rpcUrl) | CRITICAL | | unlimited-approval | max-uint approval/permit | CRITICAL / WARNING | | setapprovalforall | collection-wide NFT approval | CRITICAL / WARNING | | ownership-transfer | transferOwnership / renounceOwnership | WARNING | | chain-mismatch | typed-data chainId ≠ the chain you're on | CRITICAL | | unknown-spender | bounded approval to an unlabelled address | WARNING / INFO | | expired-deadline / far-future-deadline | permit deadline sanity | INFO | | zero-address | transfer/approval to 0x0 | WARNING | | blind-hash-sign | personal_sign of a raw 32-byte hash | WARNING |

Offline vs online

Works fully offline from static data. Provide an rpcUrl to unlock two best-effort enrichments (both fail-safe — a dead RPC never breaks a decode):

  • permit-to-eoaeth_getCode confirms whether a spender is a contract or a personal wallet (the strongest drainer signal).
  • Token metadatasymbol() / decimals() for accurate amounts and labels. Without it, bounded amounts are reported honestly in base units rather than guessing 18 decimals.
await decode(input, { chainId: 1, rpcUrl: "https://…", customBlocklist: ["0x…"] });
await decode(input, { offline: true }); // skip all network calls

Hex messages vs calldata. A hex-encoded personal_sign message is byte-identical to calldata. Auto-detection can't tell them apart, and guessing wrong is unsafe — so pass the method you already know:

import { InputType } from "@sigsafe/core";
await decode(hexMessage, { inputType: InputType.PERSONAL_SIGN });

Limitations

  1. A decoder is not a simulator — it reports what a payload is, not its downstream effects.
  2. The blocklist is reactive; the structural rules are the real defence against new drainers.
  3. It cannot stop a user who reads the CRITICAL warning and signs anyway.
  4. permit-to-eoa needs an rpcUrl to confirm EOA-vs-contract.

License

MIT © Prazwal Ratti