Skip to content

suvvvv/penumbra

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

8 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Penumbra β€” Private Comp Infrastructure on Solana

The only Solana unlock that nobody sees coming. Private token vesting, payroll, and grants on the same rails. Built on Umbra Privacy.

πŸ”— Live demo: https://penumbra-three.vercel.app πŸ›  Deployed program (devnet): 6ZajuboUT9uBgnY9RBbHdErx1rTg6kMjSFkocT1WLbdr Β· deploy tx 🎯 Notarization tx: Solscan β€” memo "Penumbra" πŸ’» Source: https://github.com/suvvvv/penumbra


Every Solana token unlock is public. Every cliff gets front-run. Every DAO contributor has their comp history doxxed by default. Penumbra ends that.

Module Who it's for What it does
Vest (hero) Token foundations, VC funds Unobservable vesting. Cliffs complete without a public signal. Investors still get scoped attestations.
Stream DAOs, grant programs Private payroll, retainers, research grants.
Audit CPAs, fund admins Accept a scoped viewing key, decrypt just the authorized window, export an 8949-ready CSV.

The problem

Vesting is broken

Every unlock on Solana is public. Analysts and MEV bots short into every cliff. Median price action after a 4-5% supply unlock: βˆ’14 to βˆ’18% within 72 hours. Foundations "solve" this with OTC sales, delayed vesting, or silent off-chain distributions β€” all terrible compromises.

Comp is worse

Every DAO contributor paid in stablecoins hands any block-explorer user their full earnings history. Recruiters, competitors, exes, and adversaries can read it like a CV.

The missing primitive

Transparency to the right parties (cap table, fund admin, tax authority) without transparency to everyone else. Public blockchains don't offer this. Umbra does.

The solution

Penumbra is three modules on one privacy rail:

Vest β€” unobservable token unlocks

  1. Foundation shields the full vest supply into an Umbra ETA owned by a VestSchedule PDA.
  2. Our Anchor program (programs/penumbra-scheduler) records the cadence on-chain. No amounts, no recipients on-chain.
  3. A keeper calls tick on cadence. The program CPIs into Umbra's getEncryptedBalanceToReceiverClaimableUtxoCreatorFunction. A receiver- claimable UTXO lands in the stealth pool.
  4. Recipients claim privately. Gas paid by the Umbra relayer.
  5. Foundation issues per-investor, per-fiscal-year viewing keys via getComplianceGrantIssuerFunction. Cap-table transparency preserved.

Stream β€” private payroll & grants

Same primitives, different cadence. DAOs pay contributors without ever broadcasting the amount or recipient.

Audit β€” scoped decrypt

Accountant loads a grant ID, clicks decrypt, gets a CSV. Umbra's MPC re-encrypts just the authorized window for the grantee's X25519 key.

Why this is only possible with Umbra

Each of these matters, and together they don't exist anywhere else:

  • Encrypted Token Accounts (ETAs) β€” private-by-default balances, Rescue- ciphered, PDA-controllable. Vanilla Solana Confidential Transfer extensions can't be held by arbitrary PDAs without losing viewing-key separation.
  • Stealth-pool UTXOs β€” Groth16-proof receiver-claimable UTXOs. Sender ↔ recipient link severed cryptographically, not by heuristic obfuscation.
  • Time-hierarchical MVKs β€” a single Poseidon hash chain turns "give me a fiscal-year viewing key" into one SDK call. This is the compliance primitive that nobody else has, and it's the reason Vest can offer real cap-table transparency at all.
  • L1/viewing key decoupling β€” PDAs hold funds, X25519 keys read balances. A vesting PDA can schedule disbursements while the foundation MVK alone reads the history.

How Penumbra uses the Umbra SDK

Eight distinct @umbra-privacy/sdk factories plus our own Anchor program that CPIs into Umbra.

Lifecycle stage Umbra SDK factory
Session init getUmbraClient, getUserRegistrationFunction
Treasury shielding getPublicBalanceToEncryptedBalanceDirectDepositorFunction
Treasury withdraw getEncryptedBalanceToPublicBalanceDirectWithdrawerFunction
Vest tick (CPI from PenumbraScheduler) getEncryptedBalanceToReceiverClaimableUtxoCreatorFunction + @umbra-privacy/web-zk-prover
Recipient scan getClaimableUtxoScannerFunction
Recipient claim (gasless) getReceiverClaimableUtxoToEncryptedBalanceClaimerFunction
Attestation issue getMasterViewingKeyDeriver β†’ getComplianceGrantIssuerFunction
Fund admin / CPA decrypt getSharedCiphertextReencryptorForUserGrantFunction

See app/app/docs/page.tsx for the recipes and app/lib/umbra/real.ts for the live wiring.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Next.js 16 app (app/)                                     β”‚
β”‚                                                            β”‚
β”‚   /                    overview + unlock narrative         β”‚
β”‚   /vest                foundation dashboard  (HERO)        β”‚
β”‚   /vest/simulator      unlock-FUD chart       (WOW)        β”‚
β”‚   /employer            stream / payroll                    β”‚
β”‚   /contributor         recipient claim + tax preview       β”‚
β”‚   /accountant          scoped decrypt + CSV export         β”‚
β”‚   /docs                six SDK recipes                     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚
                          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  app/lib/umbra/                                            β”‚
β”‚   service.ts   shared interface used by every UI           β”‚
β”‚   real.ts      live @umbra-privacy/sdk calls (8 factories) β”‚
β”‚   demo.ts      deterministic shim β€” reliable demos         β”‚
β”‚   hook.ts      useUmbra() picks impl based on toggle       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚
                          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  programs/penumbra-scheduler (Anchor)                      β”‚
β”‚   Β· initialize_schedule  Β· tick                            β”‚
β”‚   Β· cancel               Β· attest                          β”‚
β”‚   tick() CPIs into Umbra to post stealth-pool UTXOs.       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚
                          β–Ό
      Solana devnet Β· Umbra devnet indexer / relayer / MPC

Data flow β€” a vesting cliff completes privately

t-1d  keeper polls PenumbraScheduler PDAs, finds cliff_ts ≀ now
t+0   keeper.tick(schedule)
      β†’ PenumbraScheduler verifies due tranche index
      β†’ CPI into Umbra getEncryptedBalanceToReceiverClaimableUtxoCreatorFn
      β†’ UTXO lands in stealth pool keyed to recipient stealth addr
      β†’ program emits TrancheTicked (no amount, no recipient)
t+0.1h  recipient wallet scans pool β†’ finds UTXO via
        getClaimableUtxoScannerFunction β†’ claims gasless into ETA
t+quarterly  foundation.attest(investor_x25519, fy_window)
             β†’ Umbra compliance-grant PDA created
             β†’ event emitted; investor dashboard decrypts own history

Deployed artifacts

Setup

Requires Node β‰₯ 20. Anchor + Solana CLI optional (needed only to rebuild the program).

# Frontend
cd app
npm install
npm run dev
# β†’ http://localhost:3000

# Program (optional β€” already deployed to devnet)
cd programs/penumbra-scheduler
anchor build
anchor deploy --provider.cluster devnet

Environment variables (optional β€” defaults work for demo mode):

# app/.env.local
NEXT_PUBLIC_UMBRA_NETWORK=devnet           # or mainnet-beta
NEXT_PUBLIC_SOLANA_RPC_URL=https://api.devnet.solana.com
NEXT_PUBLIC_SOLANA_RPC_WS=wss://api.devnet.solana.com

Demo

See DEMO_SCRIPT.md β€” tight 4:30 walk-through, opens with the unobservable unlock and closes on the CSV export.

Demo vs live mode. The app ships with a demo-mode toggle (top nav). With it on, every SDK call runs against an in-memory shim with seeded Neuron Labs + Acme Labs data β€” so the video never breaks on an RPC flake. Flip it off and every call in app/lib/umbra/real.ts dispatches to @umbra-privacy/sdk@4 against Umbra devnet.

Repo layout

Umbra/
β”œβ”€β”€ app/                                # Next.js 16 frontend
β”‚   β”œβ”€β”€ app/
β”‚   β”‚   β”œβ”€β”€ page.tsx                    # landing
β”‚   β”‚   β”œβ”€β”€ vest/                       # foundation vesting dashboard
β”‚   β”‚   β”‚   β”œβ”€β”€ page.tsx
β”‚   β”‚   β”‚   └── simulator/page.tsx      # unlock-FUD chart (hero wow)
β”‚   β”‚   β”œβ”€β”€ employer/                   # stream / payroll
β”‚   β”‚   β”œβ”€β”€ contributor/                # recipient claim
β”‚   β”‚   β”œβ”€β”€ accountant/                 # scoped decrypt
β”‚   β”‚   β”œβ”€β”€ docs/                       # six SDK recipes
β”‚   β”‚   └── layout.tsx
β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ ui/                         # design system primitives
β”‚   β”‚   β”œβ”€β”€ layout/                     # top nav, providers
β”‚   β”‚   β”œβ”€β”€ vest/                       # dashboard + simulator
β”‚   β”‚   β”œβ”€β”€ employer/
β”‚   β”‚   β”œβ”€β”€ contributor/
β”‚   β”‚   └── accountant/
β”‚   └── lib/
β”‚       β”œβ”€β”€ constants.ts
β”‚       β”œβ”€β”€ types.ts
β”‚       β”œβ”€β”€ store/                      # Zustand (persisted, bigint-safe)
β”‚       └── umbra/
β”‚           β”œβ”€β”€ service.ts              # shared interface
β”‚           β”œβ”€β”€ real.ts                 # live @umbra-privacy/sdk calls
β”‚           β”œβ”€β”€ demo.ts                 # deterministic shim
β”‚           β”œβ”€β”€ seed.ts                 # stream seeded data
β”‚           β”œβ”€β”€ vest-seed.ts            # vesting seeded data
β”‚           β”œβ”€β”€ hook.ts                 # useUmbra()
β”‚           └── index.ts
β”œβ”€β”€ programs/
β”‚   └── penumbra-scheduler/             # Anchor program
β”‚       β”œβ”€β”€ Cargo.toml
β”‚       β”œβ”€β”€ src/lib.rs                  # all 4 instructions
β”‚       β”œβ”€β”€ tests/
β”‚       └── README.md
β”œβ”€β”€ Anchor.toml
β”œβ”€β”€ Cargo.toml
β”œβ”€β”€ DEMO_SCRIPT.md
└── README.md

Why Penumbra wins the Umbra hackathon track

Criterion How Penumbra scores
Core SDK integration 8 distinct Umbra factories across ETAs, stealth-pool UTXOs, compliance grants, MPC re-encryption β€” plus a custom Anchor program that CPIs into Umbra. Not cosmetic.
Innovation Private token vesting isn't on Umbra's prompt. It's the only compelling "only possible with Umbra" demo we found β€” needs PDA+ETA custody, stealth UTXOs, and time-hierarchical TVKs together.
Technical execution Anchor program + Next.js app + live/demo mode toggle + typed factory wrappers + BigInt-safe persist + deployable program.
Commercial potential Every Solana token project with β‰₯1 vest schedule is a buyer. Every DAO paying in stables is a secondary.
Impact Fixes unlock FUD, fixes contributor doxxing, keeps regulators and fund admins happy.
Usability Role-switchable, gasless claims, one-click attestations, interactive simulator.
Completeness Six working surfaces + Anchor program + docs page + simulator + README + demo script.

License

MIT.

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors