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

nos-rescue

v0.1.0

Published

Rescue staked NOS (and all SOL/SPL tokens) from a compromised Solana wallet — a separate fee payer covers every fee and rent, so the hacked wallet never needs SOL

Readme

nos-rescue

Rescue staked NOS (and any other SOL / SPL tokens) from a compromised Solana wallet, when the attacker holds the same private key.

The trick: a separate, uncompromised wallet pays every transaction fee and every rent deposit. The compromised wallet therefore never needs — and never keeps — any SOL. That alone stops most attackers, because without SOL they cannot broadcast a single transaction. This tool then races them: it unstakes, and withdraws each released slice of NOS straight to a safe wallet as it vests, while continuously sweeping out anything (SOL or tokens) that lands in the compromised wallet.

⚠️ This is an emergency-response tool for your own compromised wallet. You need the compromised wallet's private key, a funded fee-payer wallet, and a safe destination address.

For victims: run it right now, no clone needed

Your wallet was just hacked and you found this page? Three ways to run the tool, easiest first:

  1. One HTML file (recommended): download nos-rescue.html from the latest release, double-click it, fill in the wallets, click Start rescue. Everything runs locally in your browser tab; keys never leave it. Verify the file's SHA-256 against the release notes before pasting keys in. (If your browser refuses file://, run npx serve in the download folder and open the printed URL.)
  2. npx (terminal): npx nos-rescue status <your-wallet> to look, npx nos-rescue rescue … to act. Requires Node.js ≥ 20.18. See Usage.
  3. Hosted app: the docs site at https://laurensv.github.io/nos-rescue/ explains everything and hosts a copy of the app — convenient for a first look, but download the file and run it locally for the real rescue.

Whichever you pick: create a fresh fee-payer wallet with a little SOL (~0.1), choose a safe destination the attacker can't touch, and try dry run first if you have the nerve to spare.

How Nosana unstaking works (why this is a race)

  • NOS is staked with a lock duration (14–365 days).
  • unstake starts a linear release: after unstaking, NOS vests linearly over duration, and any released amount can be withdrawn.
  • Whoever holds the key can unstake and withdraw. If the attacker also unstakes, the vesting clock is shared — every second, a bit more becomes withdrawable, and it's first-come-first-served on each slice.

So the strategy is:

  1. Unstake immediately (the attacker will too; the clock is the same for both).
  2. Keep sweeping all SOL out of the compromised wallet. This is the real defense: with no SOL, the attacker can't broadcast a single transaction, so they can't withdraw anything. Sweeping is event-driven: a websocket watcher on the wallet and its token accounts triggers a sweep in well under a second when anything lands; interval polling is only the backup.
  3. Withdraw the released NOS to the safe wallet on a sensible cadence (see below), and do a final withdraw + close at full vest.

The on-chain withdraw and rewards claim both let you name the destination token account, so NOS goes directly to the safe wallet, never through the compromised one.

How often should you withdraw?

A slice of NOS is released every second, so "withdraw every slice" would mean a transaction every second — pointless fee-burn. What actually matters:

  • The sweep is the defense. As long as the compromised wallet has no SOL, the attacker cannot withdraw at all, so you don't need to race on withdrawals — you can safely batch them.
  • Withdrawing is a safety net in case the attacker manages to get a withdraw through (e.g. they bring their own fee payer). Smaller intervals shrink the amount exposed if that happens, at the cost of more fees.

So the cadences are decoupled:

  • CLI: sweeps instantly on websocket events (polling every --sweep-interval, default 15s, as backup) and withdraws at most every --withdraw-interval (default 3600 = hourly; set 0 to only withdraw once fully vested; --no-auto-withdraw to never auto-withdraw). A final withdraw + stake-account close always fires at full vest.
  • Web UI: the loop sweeps continuously but does not auto-withdraw (a browser tab isn't a reliable long-running daemon). Click Withdraw released NOS whenever you want to bank what's vested so far; the final withdraw + close still happen automatically at full vest.

Two ways to run it

  • Web UI (local browser page) — easiest for a non-technical victim. Paste the two keys and the safe address, watch a live activity log. npm run web.
  • CLI — best for unattended/server use near a fast RPC. node dist/cli.js ….

Both drive the exact same rescue engine (src/lib/).

Install

npm install
npm run build   # builds the CLI (dist/)

Requires Node.js ≥ 20.18.

Web UI

npm run web           # dev server at http://localhost:5173
# or produce a static bundle you can open/host locally:
npm run web:build     # outputs to dist-web/
npm run web:preview
# or build the single self-contained file (what releases ship):
npm run web:build:single   # outputs dist-single/nos-rescue.html

Open the page, fill in Network, an optional fast RPC, the compromised wallet key, the fee payer key, and the safe destination address, then:

  • Check status — read-only view of stake, vesting timeline, pending rewards, balances.
  • Start rescue — runs the daemon in the tab: sweeps continuously and unstakes, but does not auto-withdraw. Keep the tab open; the loop lives there.
  • Withdraw released NOS — banks whatever has vested so far to the safe wallet. Click it whenever; the final withdraw + close still fire automatically at full vest.
  • Stop — halts after the current iteration.

Keys are pasted directly into the page (base58 or a keypair.json array), held only in the tab's memory, and never sent anywhere except as signed transactions to your RPC. Run it on a machine you trust.

The three wallets

| Role | What it is | Needs SOL? | |------|-----------|-----------| | Compromised | The hacked wallet holding the stake. You provide its key. | No | | Fee payer | A separate, clean wallet you control. Pays all fees + rent. | Yes — fund it with some SOL | | Safe | Destination address. Receives all rescued NOS/SOL/tokens. | No |

Usage

Provide keys via flags, environment variables, or interactive hidden prompt. Every node dist/cli.js below can be replaced with npx nos-rescue if you installed nothing.

# Inspect a wallet's stake / rewards / balances (read-only, no keys needed)
node dist/cli.js status <WALLET_ADDRESS>

# Full rescue daemon (recommended): sweep, unstake, withdraw-as-it-vests, close, keep sweeping
node dist/cli.js rescue \
  --rpc "https://your-fast-rpc" \
  --safe <SAFE_ADDRESS> \
  --hacked-key ./hacked.json \
  --payer-key ./payer.json

# Or with environment variables (keeps keys out of shell history)
export NOS_RESCUE_RPC="https://your-fast-rpc"
export NOS_RESCUE_SAFE="<SAFE_ADDRESS>"
export NOS_RESCUE_HACKED_KEY="<base58 or path>"
export NOS_RESCUE_PAYER_KEY="<base58 or path>"
node dist/cli.js rescue

Individual one-shot commands are also available:

node dist/cli.js unstake     # claim rewards to safe wallet, close reward acct, unstake (atomic)
node dist/cli.js withdraw    # withdraw currently-released NOS to the safe wallet
node dist/cli.js sweep       # move all SOL + SPL tokens out (add --watch to loop)

Keys accepted as base58 private key or a path to a keypair.json (the Solana CLI array format).

Dry run

Add the global --dry-run flag to any command to simulate every transaction instead of sending it — a sanity check before anything moves. Each transaction is built and signed exactly as it would be for real, run through simulateTransaction, and logged with its outcome. rescue --dry-run does one full pass and exits.

node dist/cli.js --dry-run rescue   # shows exactly what would happen; nothing is sent

RPC endpoint

Works out of the box on a public RPC, but a public RPC is slow and rate-limited — in a race against an attacker that can cost you slices. Strongly recommended: a fast private RPC (Helius / QuickNode / Triton free tiers all work) via --rpc or NOS_RESCUE_RPC.

The websocket endpoint defaults to the RPC URL with ws(s)://. If your provider uses a different websocket URL (or you run against a local validator, where it's port 8900), pass --ws <url> or NOS_RESCUE_WS.

What the rescue daemon does each loop

  1. Ensures the safe wallet has a NOS token account (rent paid by the fee payer).
  2. Checks the fee payer's balance — if it can't pay for a transaction anymore, the rescue pauses and alerts every pass until you fund it (and warns early when it's merely low).
  3. Sweeps all SOL + SPL/Token-2022 balances from the compromised wallet to the safe wallet, and closes each drained token account so its ~0.002 SOL rent also goes to the safe wallet (empty accounts are closed for their rent too; wrapped SOL is unwrapped by closing). A pass runs instantly whenever the websocket sees value land.
  4. If still staked: claims pending rewards to the safe wallet, closes the reward account, and unstakes — all in one atomic transaction.
  5. If unstaked and auto-withdraw is on: withdraws the released NOS to the safe wallet on the configured cadence (default hourly).
  6. If the attacker re-stakes to grief you: detects it and unstakes again.
  7. When fully vested: withdraws the remainder and closes the stake account (rent released, then swept).
  8. Keeps sweeping incoming SOL/tokens forever (until you Ctrl-C).

Every transaction is signed by both the compromised wallet (as authority) and the fee payer (who pays), so the compromised wallet spends nothing.

Architecture

The core is a reusable, browser-safe library under src/lib/ (exported from src/lib/index.ts). Both front-ends are thin wrappers over it: the CLI in src/cli.ts and the local web UI in web/ (Vite + vanilla TS).

  • stake.ts — stake account decoding, live vesting math (ported from the on-chain StakeAccount::withdraw), unstake/withdraw/close instruction builders.
  • rewards.tsnosana-rewards claim / close instructions (hand-built, since @nosana/kit doesn't wrap them) so rewards route to the safe wallet.
  • sweep.ts — enumerate and move all SOL + SPL/Token-2022 balances, creating destination ATAs with fee-payer rent and closing drained source accounts (rent → safe wallet).
  • watch.ts — websocket subscriptions (wallet account + token accounts by owner, at processed commitment) that wake the sweep loop instantly, with auto-reconnect.
  • rescue.ts — the orchestration daemon.

Built on @nosana/kit, which natively supports a distinct fee payer (client.solana.feePayer) and per-ATA rent payer.

Verification

  • npm test runs the unit suite: the vesting math (withdrawableAmount) pinned against the on-chain formula across edge cases, sweep instruction building (transfer + close, rent reclaim, frozen accounts, wSOL unwrap), and the websocket notification parsing.
  • The sweep engine, websocket-triggered sweeping, ATA rent reclaim, dry-run, and fee-payer pause/resume have been executed end-to-end against a local solana-test-validator (real transactions; sub-second sweep reaction measured).
  • The unstake-with-rewards-claim and the withdraw-via-separate-fee-payer transactions have been simulated successfully against live mainnet stake accounts (see status on any real staker to inspect state). No mainnet transactions are broadcast during simulation.

Security notes

  • Private keys are only ever held in memory; nothing is written to disk. .gitignore excludes *.key and keypair*.json.
  • Prefer the interactive prompt or env vars over shell flags (flags land in shell history).
  • This tool moves assets out of a wallet you say is compromised. Double-check the --safe address — funds sent there are the whole point, and irreversible.