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

solana-private-state-toolkit

v0.1.0

Published

Privacy infrastructure for Solana: private but verifiable state using cryptographic commitments without zero-knowledge proofs. Build apps with encrypted off-chain state and 81-byte on-chain commitments.

Readme

Private State Toolkit (PST)

npm version License: MIT

Private State Toolkit is privacy infrastructure for Solana programs. It enables private but verifiable application state without zk by storing only cryptographic commitments on-chain and keeping encrypted state off-chain. This is not a private payments app — it's a reusable primitive for builders.


Installation

npm install @thewoodfish/private-state-toolkit

Or with yarn:

yarn add @thewoodfish/private-state-toolkit

Why this exists

Many Solana apps need integrity without public data leakage:

  • Games with hidden state
  • Identity attestations or claims
  • App configuration or secrets
  • Collaborative apps with private state

PST gives you:

  • On-chain verifiability (hash commitment + nonce)
  • Off-chain privacy (encrypted payload stays with the app/user)
  • Minimal on-chain footprint (cheap, indexer‑resistant)

Core idea

  1. App encrypts state locally (AES‑256‑GCM).
  2. It computes commitment = sha256(nonce || encrypted_payload).
  3. On-chain account stores only:
    • authority
    • commitment
    • nonce
    • policy
  4. Update is valid only if:
    • old_commitment matches
    • nonce rule obeys policy

Everyone can verify ordering and consistency — no one sees the payload without the key.


Quick Start

1. Install the SDK

npm install @thewoodfish/private-state-toolkit

2. Set up environment

# .env file
PST_PROGRAM_ID=4FeUYtneSbfieLwjUT1ceHtv8nDXFk2autCZFyDhpkeD

3. Initialize private state

import { Connection, Keypair } from "@solana/web3.js";
import { initPrivateState, encryptPayload, UpdatePolicy } from "@thewoodfish/private-state-toolkit";

const connection = new Connection("https://api.devnet.solana.com");
const payer = Keypair.generate(); // Your wallet keypair
const encryptionKey = Buffer.from("your-32-byte-key"); // Store securely!

// Your app state
const appState = { counter: 0, gameState: "active" };

// Encrypt and create commitment
const encrypted = encryptPayload(Buffer.from(JSON.stringify(appState)), encryptionKey);

// Initialize on-chain
const { statePubkey, signature } = await initPrivateState(
  connection,
  payer,
  encrypted,
  UpdatePolicy.StrictSequential
);

console.log("PST account created:", statePubkey.toBase58());

4. Update private state

import { updatePrivateState, commitment } from "@thewoodfish/private-state-toolkit";

// Update your state
const newState = { counter: 1, gameState: "active" };
const newEncrypted = encryptPayload(Buffer.from(JSON.stringify(newState)), encryptionKey);

// Compute new commitment
const newCommitment = commitment(newNonce, newEncrypted);

// Submit update
const sig = await updatePrivateState(
  connection,
  payer,
  statePubkey,
  oldCommitment,
  oldNonce,
  newCommitment,
  newNonce
);

5. Use in your Solana program (CPI)

use anchor_lang::prelude::*;
use private_state_toolkit::cpi::accounts::AssertState;
use private_state_toolkit::program::PrivateStateToolkit;

#[derive(Accounts)]
pub struct ValidatePrivateState<'info> {
    #[account(mut)]
    pub private_state: AccountInfo<'info>,
    pub pst_program: Program<'info, PrivateStateToolkit>,
}

pub fn my_instruction(
    ctx: Context<ValidatePrivateState>,
    expected_commitment: [u8; 32],
    expected_nonce: u64,
) -> Result<()> {
    // Validate private state before executing logic
    let cpi_ctx = CpiContext::new(
        ctx.accounts.pst_program.to_account_info(),
        AssertState {
            private_state: ctx.accounts.private_state.to_account_info(),
        },
    );

    private_state_toolkit::cpi::assert_state(
        cpi_ctx,
        expected_commitment,
        expected_nonce,
    )?;

    // Your program logic here - state is validated!
    Ok(())
}

Composability via CPI (assert_state)

PST now exposes a CPI‑friendly validation hook:

pub fn assert_state(
  ctx: Context<AssertState>,
  expected_commitment: [u8; 32],
  expected_nonce: u64
) -> Result<()>
  • Read‑only (no mutation)
  • Deterministic + cheap
  • Works from any program via CPI

CPI example (from pst_consumer)

let cpi_accounts = private_state_toolkit::cpi::accounts::AssertState {
    private_state: ctx.accounts.private_state.to_account_info(),
};
let cpi_ctx = CpiContext::new(
    ctx.accounts.pst_program.to_account_info(),
    cpi_accounts,
);
private_state_toolkit::cpi::assert_state(
    cpi_ctx,
    expected_commitment,
    expected_nonce,
)?;

This lets any program gate actions on fresh private state without decrypting.


Update policies

PST supports two minimal, real update policies:

pub enum UpdatePolicy {
  StrictSequential = 0,  // next_nonce == stored_nonce + 1
  AllowSkips = 1,        // next_nonce > stored_nonce
}

Why it matters:

  • StrictSequential for deterministic apps (games, turn‑based state)
  • AllowSkips for async/offline workflows (batched or delayed updates)

Policies are enforced on-chain during update and can be changed via set_policy.


Local/Chain Sync Protocol (2‑phase)

PST avoids race conditions with a two‑phase local protocol:

Files:

  • state/state.committed.json = last confirmed state (local source of truth)
  • state/state.pending.json = staged update awaiting confirmation
  • demo-key.json = demo encryption key

Flow:

  1. inc.ts writes state.pending.json before submitting the transaction.
  2. After confirmation, it atomically promotes to state.committed.json.
  3. watch.ts treats chain nonce/commitment as source of truth and emits status:
    • IN_SYNC — chain == committed
    • PENDING — pending exists, chain == committed
    • LANDED_PENDING — chain == pending (auto‑promote)
    • STALE — chain ahead of committed
    • DIVERGED — local ahead of chain

This avoids “sleep and retry” hacks and works reliably under websocket lag.


Demo: Private Counter

What it proves

  • Counter value is encrypted locally
  • On-chain only stores commitment + nonce + policy
  • Real‑time updates via WebSocket subscriptions
  • Observer can verify updates but cannot decrypt

Setup and Installation

Prerequisites

  • Rust 1.75+
  • Solana CLI 1.18+
  • Anchor CLI 0.30.1
  • Node.js 18+

Install dependencies

npm install

Build programs

anchor build

Deploy to devnet

# Configure Solana CLI for devnet
solana config set --url https://api.devnet.solana.com

# Airdrop SOL for deployment
solana airdrop 2

# Deploy both programs
anchor deploy

# Note the program IDs and update Anchor.toml if different

Run tests

anchor test

Run (devnet)

export PST_PROGRAM_ID=<deployed_program_id>
export SOLANA_RPC_URL=https://api.devnet.solana.com

# terminal 1
npx ts-node scripts/init.ts

# terminal 2
TS_NODE_CACHE=false npx ts-node scripts/watch.ts

# terminal 3
npx ts-node scripts/inc.ts
npx ts-node scripts/inc.ts

# optional observer (no key)
TS_NODE_CACHE=false npx ts-node scripts/observer.ts

Policy selection

# strict (default)
POLICY=strict npx ts-node scripts/init.ts

# allow_skips
POLICY=allow_skips npx ts-node scripts/init.ts

# demo skip in allow_skips mode
npx ts-node scripts/inc.ts --skip 3

Inspect pending vs committed

ls state
cat state/state.pending.json
cat state/state.committed.json

You should see:

  • state.pending.json appear immediately on inc.ts start
  • state.committed.json update only after confirmation

Demo: CPI consumer (composability)

This demo proves any program can gate actions using PST without decrypting.

export PST_PROGRAM_ID=<deployed_pst_id>
export PST_CONSUMER_PROGRAM_ID=<deployed_consumer_id>
export SOLANA_RPC_URL=https://api.devnet.solana.com

npx ts-node scripts/consumer_demo.ts

Flow:

  1. Initializes a PST private counter
  2. Initializes a consumer program referencing that PST account
  3. Calls gated_action with expected commitment/nonce (succeeds)
  4. Updates PST
  5. Calls gated_action again with new expected values (succeeds)

SDK highlights

  • AES‑256‑GCM encryption helpers
  • Commitment hash = sha256(nonce || payload)
  • Manual account decoding (skip discriminator, parse u64 LE, policy)
  • assertState + setPolicy helpers
  • WebSocket subscription helper

Why indexers can’t see your data

Indexers only see:

  • Account authority
  • Commitment hash
  • Nonce increments
  • Policy byte

They cannot derive or decrypt the payload without the key.


Honest limitations

  • This is not anonymity — observers still see account + update cadence
  • Payload durability depends on your off‑chain storage
  • No zk proofs — integrity is commitment‑based only

Project structure

programs/private_state_toolkit/src/lib.rs
programs/pst_consumer/src/lib.rs
sdk/index.ts
scripts/init.ts
scripts/inc.ts
scripts/watch.ts
scripts/observer.ts
scripts/consumer_demo.ts
scripts/fs_atomic.ts
README.md

PST makes private state easy on Solana: verifiable updates on‑chain, encrypted data off‑chain, zero zk complexity, and composable CPI validation.