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

smail-cli

v0.1.3

Published

Send and open SMAIL links from a terminal or an agent, on Robinhood Chain

Readme

smail-cli

Pay someone who has no account, from a terminal.

A snail is ETH sealed against a key that exists only in a link. Whoever holds the link takes the contents, so paying costs nothing but handing the link over: no address to ask for, no registration, no wallet extension. A private link goes one further: claiming it is a zero-knowledge proof, made here in JavaScript, so nothing on chain ties the claim to the sealing.

npm install -g smail-cli

Walkthrough

1. Add your .env.local

Nothing works until this exists. Create a file called .env.local in your project and put two variables in it:

SMAIL_PRIVATE_KEY=0x…            # the key that signs and pays gas
SMAIL_NETWORK=robinhood-testnet   # or robinhood, or anvil

No export, no quotes needed. Both are tolerated if you paste them anyway.

The file is found on its own: the directory you are standing in, then its parents up to the repository root, then one level down, which is where a framework tends to leave one. Exported shell variables work too and always win over a file, which is how a container should inject a key. Whatever holds that key holds the money: keep the file out of git.

Two more are optional. The network's own relayer is used by default, so claims and returns are delivered for nothing and need no ETH:

SMAIL_RPC=https://…               # a node of your own
SMAIL_RELAYER_URL=https://…       # another relayer, or `none` to submit from your key

Check it landed:

smail whoami
0xf39F…2266  holding 0.5 ETH
on Robinhood Chain testnet via https://rpc.testnet.chain.robinhood.com
snail 0x1cf9…b10b  (sponsored by 0x9a48…925d: sealing costs 1% on top, delivery is free)
private pool: none recorded
read from /Users/you/project/.env.local

No relayer configured: this key submits every transaction, so the sender is visible.

Can: seal, open, refund, status.

It prints the file it read, so you are never guessing which key is about to sign. If more than one candidate turns up it refuses to choose and asks you to name it with --env <path>.

Whether the contract is sponsored is read off the contract itself. A SponsoredSmail is funded with 1% on top of the amount; that fee pays a relayer network to deliver the claim and the refund, so the recipient gets the whole amount and needs no ETH, and an expired snail only ever goes back to the address that funded it. A plain Snail charges nothing at sealing, and a relayer, if you use one, takes its fee out of the snail when it is opened.

2. Rehearse

--dry-run builds the transaction and prints it without signing or sending. Worth doing once before this is wired into anything that spends on its own.

smail seal --amount 0.01 --dry-run

3. Send

smail seal --amount 0.01 --expiry 24h --memo "invoice 1101"
Sealed 0.01 ETH on Robinhood Chain testnet, claimable for 1 day, paying 0.0101 ETH: the amount and a 0.0001 ETH fee that has a relayer deliver it for nothing.
Funded from 0xf39F…2266: the chain sees this address pay. --private seals a private
link instead, whose claim nothing on chain ties back to you.

Claim link   https://smail.cash/claim#s1.BSc7nv0…
Return link  https://smail.cash/refund#r1.B4jwdCU…~0x7e43…

Anyone holding the claim link can take the contents, so send it the way you
would send cash. Keep the return link: after the window shuts it is the only
way to get the money back.

Transaction  https://explorer.testnet.chain.robinhood.com/tx/0x2a927023701c…

--password locks the link so the recipient needs both halves; send the password another way. --unlock 1h refuses claims for the first hour; --expiry never makes a snail that can never be taken back.

Two links, and they are not interchangeable. The claim link is a bearer instrument: hand it to the recipient and nobody else. The return link is yours, it is the only way to reclaim the value after the window shuts, and it cannot be regenerated from anything.

4. Check

smail status "https://smail.cash/claim#s1.BSc7nv0…"
funded on Robinhood Chain testnet, holding 0.01 ETH, memo "invoice 1101".
Claim window shuts 2026-09-27T07:41:48.000Z.

  funded    0.01 ETH  from a wallet  https://explorer.testnet.chain.robinhood.com/tx/0x2a92…

status takes a claim link, a return link (the funder's own way to check), a private link, or --id with the claim key's address. It reads the contract and every transaction that touched the snail.

5. Receive

On the other side, with its own key configured, the recipient runs:

smail open "https://smail.cash/claim#s1.BSc7nv0…"

No flag pays the key's own address; --to <address> pays another. With SMAIL_RELAYER_URL set, the claim key in the link signs and the relayer submits, so the opener needs no ETH. A snail releases exactly once, which is what makes a link safe to send over a channel you do not control.

6. Take it back

smail refund "https://smail.cash/refund#r1.B4jwdCU…~0x7e43…"

Only after the claim window has shut, and only with the return link. A sponsored snail goes back to the address that funded it, whoever runs this.

Private links

smail seal --private --amount 0.01
smail open "https://smail.cash/claim#p1.…"

A private link (p1.) carries two random secrets instead of a key, and is sealed into PrivateSnail, a pool of fixed sizes: 0.001, 0.005, 0.01, 0.05 and 0.1 ETH. Sealing costs the amount plus 1%, prepaid for whoever relays the claim. Opening reads the pool's whole deposit log (never asking the node about one link), rebuilds the tree, and proves the claim on this machine; it takes a few seconds. Through a relayer that carries private links the relayer is paid the prepaid 1%; from your own key, you get it back with the amount.

No window, no memo, no password, and no return link: the sender keeps the same link and takes the money back by opening it, for as long as nobody else has.

What it hides, exactly. The sealing shows the funder, the size and a commitment. The claim shows the recipient, the size and the relayer. Nothing on chain pairs the two, so a claim hides among every unclaimed private link of the same size, which is little in a small pool. The sender can always see when and where it was claimed. PROTOCOL.md §10 has the rest.

Private links need a PrivateSnail recorded for the network (pool and poolFirstBlock in smail.json, or SMAIL_POOL and SMAIL_POOL_FIRST_BLOCK) and, to open one, the prover's artifacts (withdraw.wasm, withdraw_final.zkey). They ship inside smail-sdk, so an installed CLI finds them on its own; a checkout finds them in circuits/build, and SMAIL_ZK_DIR points at a copy anywhere else. smail whoami reports both.

The pool's history is kept between commands in ~/.cache/smail (or SMAIL_CACHE_DIR), starting from a copy shipped in the package, so a private command reads only the blocks since the last one. The copy is checked against the pool itself before it is used, and can be deleted at any time.

For programs

One command, two shapes. Read by a person it is prose; piped into anything else it is JSON, decided by whether stdout is a terminal rather than by remembering a flag. --json and --human force it either way.

LINK=$(smail seal --amount 0.01 | jq -r .claimLink)
curl -X POST "$WEBHOOK" -d "{\"pay\": \"$LINK\"}"

Every command returns ok, and failures carry error on stderr with exit code 1, so a caller can branch without parsing sentences.

smail status "$LINK" | jq -e '.status == "claimed"' && echo paid

Commands

| | | |---|---| | seal --amount <eth> | Seal a snail. --expiry 24h\|never, --unlock 1h, --memo <text>, --password <text>, --dry-run | | seal --private --amount <eth> | Seal a private link. --dry-run | | open <link> | Open a claim link or a private link. --to <address>, --password <text>, --dry-run | | refund <return link> | Take an expired snail back. --to <address> (not on a sponsored snail), --dry-run | | status <link> | Read the contract and the receipt. --id for a claim key address | | whoami | The key, network, contracts, relayer and prover in use |

Global: --json, --human, --env <path>, --verbose.

Environment: SMAIL_PRIVATE_KEY, SMAIL_NETWORK (robinhood, robinhood-testnet, anvil), SMAIL_RPC, SMAIL_RELAYER_URL, SMAIL_SNAIL, SMAIL_FIRST_BLOCK, SMAIL_POOL, SMAIL_POOL_FIRST_BLOCK (contract defaults come from smail.json at build time), SMAIL_APP_ORIGIN, SMAIL_ZK_DIR.

The shielded pool this CLI used to drive (shield, unshield, notes, open --shield) is retired. Those commands and flags are refused by name.

Or the library

The CLI is a thin wrapper over smail-sdk, the same package the web app is built on: keys, links, locks, the release signatures, the private-link tree and prover, and the contract calls. Its README carries the API.

Development

npm run build --workspace smail-cli
npm test --workspace smail-cli     # parsers, and the anvil end-to-end run

The end-to-end test deploys a Snail, a SponsoredSmail in front of a stub relayer network, and (with forge on PATH) a PrivateSnail from contracts/script/DeployPrivate.s.sol; runs every command in-process against them; stands up a relayer in a few lines for each; and asserts the chain after each step. It skips loudly without anvil, and skips the private-link steps without forge or the artifacts in circuits/build.