Skip to content

Tooling

Tooling — Solana Token Distribution Protocol

Section titled “Tooling — Solana Token Distribution Protocol”

Tools, dependencies, and code conventions for Solana TDP.

  1. Framework
  2. Testing
  3. Dependencies
  4. Repository layout
  5. Code conventions
  6. API Stack

Anchor 0.32.1 — Solana program framework. Provides #[account] and #[derive(Accounts)] macros for account validation, automatic IDL generation, and CPI helpers.

Anchor 0.32.1 is chosen over the latest Anchor v1 for stability. Anchor v1 introduced breaking changes to the CLI and program macros. While newer, v1’s tooling ecosystem is still settling — key crates, testing libraries, and documentation are still being ported. 0.32.1 has mature documentation, broad crate compatibility, and a well-understood upgrade path.


Two test layers covering program logic and consumer integration.

Terminal window
cargo build # Rust compilation
cd apps/solana-tdp-anchor
anchor build # BPF compilation (for on-chain deployment)

Run with cargo test — no SVM/validator required. Tests live in #[cfg(test)] mod tests {} blocks co-located with the source they cover.

TypeScript integration tests (vitest + anchor-litesvm)

Section titled “TypeScript integration tests (vitest + anchor-litesvm)”

Run with:

Terminal window
cd apps/solana-tdp-anchor
pnpm test

Tests use vitest with the anchor-litesvm npm package via LiteSVMProvider. Each test creates fresh token mints, keypairs, and stream fixtures — fully isolated, no local validator needed. Each test file covers one instruction family.

Helpers: tests/helpers.ts provides now() and clockNow() (SVM-based clock helper). PDA derivation helpers (findStreamPDA, findVaultPDA, findCreatorConfigPDA) and event parsing (parseEvents, findEvent) live in @solana-tdp/sdk.

Test files:

FileCoverage
solana-tdp.000.create-stream.test.ts9 tests — happy path, cliff variant, 4 validation rejections, DurationTooShort, InsufficientBalance, StreamCreated event
solana-tdp.001.withdraw.test.ts15 tests — partial/full vesting, cumulative tracking, cliff/start/cancelled rejections, ExceedsClaimable, TokensClaimed event, closure, 25%/50% percentages, third-party/creator rejections
solana-tdp.002.cancel.test.ts8 tests — pre-start/partial/post-end splits, double-cancel rejection, StreamCancelled event, closure
solana-tdp.003.milestone.test.tsMilestone stream creation, trigger, withdraw, cancel
solana-tdp.005.security-audit.test.ts19 tests — signer authority, PDA uniqueness, overflow, account ownership, state transitions, wrong-account attacks, timestamp boundaries
fixtures.tsShared test fixtures (token mints, accounts, PDAs)
helpers.tsnow() and clockNow() SVM-based clock helpers
utils.tsTest utility functions

Storybook browser tests (vitest + Playwright)

Section titled “Storybook browser tests (vitest + Playwright)”

Run with:

Terminal window
cd apps/storybook
pnpm test:storybook # Storybook tests only

The web frontend has 55+ Storybook stories tested via @storybook/addon-vitest with Playwright (Chromium). Each story is rendered in a headless browser and tested for interaction correctness (clicks, form fills, callback assertions) and accessibility (axe-core).

Playwright browsers are installed automatically via the postinstall script (playwright install chromium).


// apps/solana-tdp-anchor/package.json (dependencies)
{
"@coral-xyz/anchor": "^0.32.1",
"@solana/web3.js": "^1.98.4"
}

Monorepo managed by pnpm workspaces.

apps/
├── api/ # Cloudflare Worker API (Hono + D1)
│ ├── src/
│ │ ├── index.ts # Hono app entry
│ │ ├── middleware/
│ │ │ ├── auth.ts # Privy JWT verification
│ │ │ ├── cors.ts # CORS config
│ │ │ └── rate-limit.ts # In-memory per-IP rate limiting
│ │ ├── routes/
│ │ │ ├── streams.ts # Stream recording endpoints
│ │ │ ├── users.ts # User profile endpoints
│ │ │ ├── organizations.ts # Org CRUD + members
│ │ │ ├── reconciliation.ts # On-chain reconciliation
│ │ │ ├── tokens.ts # Token metadata, R2 upload, visibility
│ │ │ └── waitlist.ts # Legacy waitlist endpoint
│ │ ├── services/
│ │ │ ├── stream-service.ts # Stream business logic
│ │ │ ├── user-service.ts # User profile logic
│ │ │ ├── org-service.ts # Org CRUD logic
│ │ │ ├── token-service.ts # Platform token creation, R2 metadata, visibility
│ │ │ └── reconciler.ts # Reconciliation logic
│ │ └── db/
│ │ ├── schema.ts # Drizzle schema
│ │ ├── index.ts # DB client
│ │ └── migrations/ # D1 migrations
│ ├── drizzle.config.ts # Drizzle Kit config
│ ├── wrangler.toml # CF Worker config + D1 binding
│ └── env.d.ts # Env type definitions
├── solana-tdp-anchor/
│ ├── programs/solana-tdp/src/
│ │ ├── lib.rs # Program entry + declare_id!
│ │ ├── errors.rs # Custom error codes
│ │ ├── events.rs # Anchor event definitions
│ │ ├── state/ # Account structs (StreamAccount, MilestoneStreamAccount, CreatorConfig)
│ │ │ ├── mod.rs
│ │ │ └── stream_account.rs
│ │ └── instructions/ # Instruction handlers
│ │ ├── mod.rs
│ │ ├── create_stream.rs
│ │ ├── withdraw.rs
│ │ ├── cancel.rs
│ │ ├── create_milestone_stream.rs
│ │ ├── trigger_milestone.rs
│ │ ├── withdraw_milestone.rs
│ │ └── cancel_milestone.rs
│ └── tests/
│ ├── solana-tdp.000.create-stream.test.ts
│ ├── solana-tdp.001.withdraw.test.ts
│ ├── solana-tdp.002.cancel.test.ts
│ ├── solana-tdp.003.milestone.test.ts
│ ├── solana-tdp.005.security-audit.test.ts
│ ├── fixtures.ts
│ ├── helpers.ts
│ └── utils.ts
├── dapp/ # React frontend (Vite + TanStack Router)
│ ├── app/
│ │ ├── components/
│ │ │ ├── solana/ # Wallet/auth components
│ │ │ ├── streams/ # Stream management UI
│ │ │ ├── tokens/ # Token selector
│ │ │ ├── layout/ # Navbar, footer
│ │ │ ├── marketing/ # Landing page sections
│ │ │ └── ui/ # Generic UI primitives
│ │ ├── hooks/
│ │ │ ├── tx/ # On-chain transaction hooks (use-create-stream, use-withdraw, etc.)
│ │ │ ├── use-stream.ts # On-chain queries
│ │ │ ├── use-stream-api.ts # Stream API hooks
│ │ │ ├── use-user-api.ts # User profile API hooks
│ │ │ ├── use-org-api.ts # Organization API hooks
│ │ │ └── use-program.ts # Anchor program instance
│ │ ├── lib/
│ │ │ ├── solana/ # Privy-backed hooks (useAuth, useAnchorSigner, useConnection)
│ │ │ └── api-client.ts # HTTP client for API
│ │ └── routes/
│ └── package.json
packages/
└── solana-tdp-sdk/ # TypeScript SDK (Anchor IDL + helpers)
  • state/ — Account structs and enums only. No logic.
  • instructions/ — One file per instruction handler. Each file contains accounts struct, validation, and handler function.
  • errors.rs — All custom Anchor error codes with descriptive messages.
  • events.rs — All events emitted by the program. One struct per event.

WhatConventionExample
Test filessolana-tdp.NNN.instruction.test.tssolana-tdp.000.create-stream.test.ts
PDA seedsLowercase static strings"stream", not "VestingSchedule"
Instruction filesMatch instruction name exactlycreate_stream.rs
TypeScript hooksKebab-caseuse-vesting-schedule.ts
API routesHono router per domainroutes/streams.ts
DB schemaDrizzle ORM, camelCase columnscreatorAddress not creator_address

Test files numbered by instruction execution order:

solana-tdp.000.create-stream.test.ts # stream must exist first
solana-tdp.001.withdraw.test.ts # then claim vested tokens
solana-tdp.002.cancel.test.ts # then cancel mid-stream
solana-tdp.003.milestone.test.ts # milestone lifecycle
solana-tdp.005.security-audit.test.ts # security attack-vector tests
[provider]
cluster = "localnet"
[scripts]
test = "pnpm exec vitest run"

LayerTechnologyPurpose
FrameworkHonoLightweight web framework for CF Workers
ORMDrizzle ORMType-safe SQL queries, migrations
DatabaseCloudflare D1SQLite at the edge
Object StoreCloudflare R2Token metadata JSON storage (TOKEN_ASSETS)
AuthPrivy JWTJWKS-based token verification
Compatibilitynodejs_compat flagNode.js API support in CF Workers
TablePurpose
usersUser profiles (linked to Privy DID)
organizationsTeam/org metadata
org_membersMany-to-many: users ↔ orgs with roles
streamsStream records (includes closed streams)
stream_eventsImmutable event log per stream (unique on stream_id + event_type + tx_signature)
MiddlewarePurpose
cors.tsCORS restricted to localhost + simplyvest.pages.dev + simplyvest.xyz
auth.tsPrivy JWT verification via JWKS (cached 1hr)
rate-limit.tsIn-memory per-IP rate limiting (30/min streams, 20/min users, 5/min waitlist)
ServicePurpose
stream-serviceStream business logic
user-serviceUser profile logic
org-serviceOrg CRUD logic
token-servicePlatform token creation, R2 metadata upload, token visibility
reconcilerOn-chain event reconciliation
TriggerFrequencyPurpose
ReconciliationEvery 15 minutesSync missed on-chain events to D1
Terminal window
pnpm dev:api # Start API worker locally (localhost:8787)
pnpm db:generate # Generate Drizzle migrations
pnpm db:migrate # Apply migrations (local D1)
pnpm db:migrate:remote # Apply migrations (remote D1)
pnpm db:reset # Drop all tables (local)
pnpm deploy:api # Deploy API worker to Cloudflare
pnpm --filter @solana-tdp/api test # Run API tests