Appearance
Why Not X
This document captures the reasoning behind every significant technology choice versus its alternatives. Each section provides the decision context, the alternatives evaluated, and the detailed reasoning for why the chosen option won.
Version Compatibility Matrix
Core version constraints that govern all technology choices:
| Component A | Compatible With | Notes |
|---|---|---|
| Anchor CLI 0.32.1 | Solana CLI 2.3.0+ (Agave) | Tested target. Agave 3.x may work but untested. |
| Anchor CLI 0.32.1 | Rust 1.89.0+ | Required for stable IDL building |
| @coral-xyz/anchor 0.32.1 | @solana/web3.js 1.x | v2 (@solana/kit) NOT supported |
| @solana/spl-token 0.4.14 | @solana/web3.js 1.x | For Kit, use @solana-program/token-2022 instead |
| helius-sdk 2.2.x | @solana/kit 3.0.x (internal) | Uses Kit internally, no conflict with web3.js v1 in our code |
| TanStack Start ~1.167.x | React 19.x | Also supports React 18 |
| Drizzle ORM 0.45.x | Cloudflare D1 | Uses D1 batch API (no interactive transactions) |
| Astro 6.1.x | Starlight 0.38.x | Verified compatible |
| Better Auth 1.5.x | Drizzle 0.45.x + D1 | Native adapter, no custom config |
Version Pinning Strategy
| Package | Pin Strategy | Reason |
|---|---|---|
| Anchor | Exact (0.32.1) | Program framework — must match deployed program IDL |
| @coral-xyz/anchor | Exact (0.32.1) | Must match Anchor CLI and deployed program |
| @solana/web3.js | ^1.x | Stable v1 API, patches are safe |
| @solana/spl-token | ^0.4.x | Active development, minor bumps may add features |
| TanStack Start | ~1.167.x | Frequent patches, minor bumps may break during RC |
| Hono | ^4.12.x | Stable, semantic versioning respected |
| Drizzle ORM | ^0.45.x | Pre-1.0, but stable API surface |
| Astro | ^6.1.x | Major version locked, patches safe |
| Starlight | ^0.38.x | Pre-1.0, updates frequently |
| Better Auth | ^1.5.x | 1.x stable, patches/minors safe |
| helius-sdk | ^2.2.x | 2.x stable, patches safe |
Solana Framework: Anchor 0.32.1
Why NOT Anchor 1.0 RC
- Actively releasing breaking changes: rc.3, rc.4, rc.5 shipped within days of each other. A protocol that handles money cannot build on a moving target.
- Removes
#[interface]attribute: Required for transfer hook implementation (#[interface(spl_transfer_hook_interface::execute)]). Without it, implementing Token-2022 transfer hooks becomes significantly more complex. - Not production-ready: RC status means no stability guarantee. API surface can change between RCs without deprecation cycles.
- IDL generation changes: New IDL format may require SDK rewrite. We can't afford that risk during active development.
Why NOT Pinocchio (Native Rust)
- Pinocchio is designed for high-performance native programs where every compute unit matters.
- Anchor's IDL generation is essential for a protocol with complex PDA schemas (Mandate, Plan, PullApproval, Credential, TokenConfig, StreamMandate, AgentMandate — 7+ account types).
- Account validation via
#[account(...)]constraints catches entire classes of bugs at compile time. - Developer tooling (
anchor test,anchor build,anchor deploy) is mature and well-documented. - LiteSVM integration via
anchor-litesvmonly works with Anchor programs. - The ~5% CPI overhead from Anchor is acceptable for billing operations that run at keeper cadence (not per-transaction).
Solana TS Client: web3.js v1 + helius-sdk
Why NOT @solana/kit Directly
- Anchor's TypeScript client (
@coral-xyz/anchor) only supports web3.js v1. Kit is incompatible. - Using Kit directly would mean abandoning the Anchor TS client and hand-building all instruction serialization, account deserialization, and event parsing.
- Kit migration happens when Anchor 1.0 ships Kit support — that's the natural migration point, not a forced adoption during active development.
Why NOT Codama-Generated Kit Client
- Viable but adds significant build complexity (code generation pipeline).
- Less battle-tested than the Anchor TS client for complex PDA schemas.
- Solana Foundation recommends this for Kit adoption, but the Anchor client is more proven for our use case.
- Evaluate at Phase 1 when Anchor 1.0 may have Kit support.
Dashboard Framework: TanStack Start
Why NOT Next.js
- Vercel-centric: Next.js is optimized for Vercel deployment. Cloudflare Workers deployment is a second-class citizen with known limitations.
- Type-safe routing: TanStack Start provides full type safety for routes, search params, and server functions. Next.js's App Router has weaker type guarantees.
- Cloudflare integration: TanStack Start +
@cloudflare/vite-pluginprovides first-class D1 binding support in local development. - Hono integration: TanStack Start allows mounting Hono as a wildcard API route for
/api/*endpoints. Clean separation of SSR pages and API routes. - Not a framework tax: Next.js bundles a router, SSR engine, image optimizer, and more. We need a router with SSR — TanStack Start provides exactly that.
Why NOT Remix / React Router v7
- TanStack Start's server functions + type-safe routing are more ergonomic than Remix's loader/action model.
- Remix's Cloudflare story is less mature. The community template for TanStack Start + Hono + Cloudflare is better documented.
- React Router v7 merged with Remix but still carries legacy patterns that don't align with our stack.
API Framework: Hono
Why NOT Express
- Legacy architecture: Express was designed for Node.js HTTP. No Web Standards compliance.
- No edge runtime support: Express doesn't run on Cloudflare Workers, Deno, or Bun natively. Hono runs everywhere.
- Performance: Hono is significantly faster (no regex-based routing overhead).
- TypeScript-first: Hono has built-in type inference for routes, middleware, and request/response types. Express requires third-party types (
@types/express). - Middleware model: Hono's middleware is composable and follows Web Standards (Request/Response). Express middleware is Node.js-specific.
Database ORM: Drizzle
Why NOT Prisma
- No Cloudflare D1 support: Prisma doesn't support D1. This is a hard blocker — the dashboard database must be D1 (GA, good enough until PMF).
- Heavier: Prisma's query engine is a Rust binary (~40MB). Drizzle's core is ~7.4kb.
- Slower migrations: Prisma's migration system is designed for interactive transactions, which D1 doesn't support.
- Schema portability: Drizzle's dialect abstraction means the SQLite schema for D1 can be ported to Postgres by changing the dialect. Prisma would require a full schema rewrite.
Auth: Better Auth
Why NOT Auth.js / NextAuth
- Next.js-centric: Auth.js (formerly NextAuth) is designed for Next.js. Using it with Hono on Cloudflare Workers is possible but not natural.
- No D1 adapter: Auth.js doesn't have a native D1 adapter. Would require custom adapter development.
- No Drizzle adapter: Better Auth has a native Drizzle adapter for schema co-location. Auth.js requires Prisma or raw SQL.
- Hono middleware: Better Auth has first-class Hono middleware. Auth.js is Express/Next.js only.
Why NOT Clerk / Auth0
- Vendor lock-in: Both are hosted services with proprietary APIs. Migration off them is painful.
- Usage-based pricing: As the merchant base grows, auth costs scale linearly. Self-hosted auth has fixed infra cost.
- Privacy alignment: VelaPay is privacy-first. Sending authentication data to a third-party service contradicts the product's core value proposition.
- D1 integration: Clerk and Auth0 don't natively integrate with Cloudflare D1. Better Auth does.
Docs Framework: Starlight
Why NOT Docusaurus
- React-heavy: Docusaurus ships significant client-side JavaScript. Starlight is Astro-based and ships zero JS by default.
- Larger bundles: Docusaurus pages load React runtime. Starlight pages are pre-rendered HTML.
- Worse Cloudflare integration: Docusaurus is designed for GitHub Pages / Netlify / Vercel. Starlight + Astro has first-class Cloudflare Workers support.
- Cloudflare uses it: Cloudflare uses Starlight for their own docs. Good enough for them, good enough for us.
Why NOT VitePress
- Vue-based: VitePress uses Vue.js for interactive components. The rest of the workspace is React-based. Adding Vue creates a skill fragmentation.
- Not aligned with React ecosystem: Interactive docs components (code playgrounds, live examples) would need Vue, not React.
Why NOT Mintlify
- Hosted service: Vendor lock-in. Can't self-host on Cloudflare.
- Proprietary format: Mintlify uses its own MDX extensions. Migration off Mintlify means rewriting content.
- No deploy control: Updates go through Mintlify's build pipeline, not ours.
Why NOT GitBook
- Commercial product: Not self-hostable. Subscription pricing.
- No Cloudflare integration: GitBook is a SaaS product. Can't deploy to Cloudflare Workers.
- Limited customization: GitBook's theming is restricted. Starlight allows full CSS/component customization.
Testing: LiteSVM
Why NOT Bankrun / solana-bankrun
- Deprecated March 2025: Bankrun is no longer maintained. The maintainer recommends LiteSVM.
- Slower: LiteSVM is orders of magnitude faster with a better API.
- Anchor integration:
anchor-litesvm(0.2.1) providesLitesvmProvideras a drop-in replacement forAnchorProvider. Bankrun had no equivalent. - Active development: LiteSVM is actively maintained and used by the Anchor team for official testing documentation.
Testing: bun:test
Why NOT Jest
- Built-in: Bun has a built-in test runner. Zero config, native TypeScript.
- No dependencies:
bun:testis part of the Bun runtime. Jest requiresjest,ts-jest,@types/jest, and config files. - Speed: bun:test runs faster than Jest, especially for TypeScript files.
- Compatible API: bun:test uses a Jest-compatible API (
describe,it,expect), so test code is familiar.
Why NOT Vitest
- Unnecessary: Vitest is designed for Vite projects. Bun has a built-in test runner that covers the same use case.
- Extra dependency: Adding Vitest means adding
vitest, a config file, and Vite integration. bun:test is zero-config. - No Vite needed: Our projects use Bun's bundler, not Vite standalone. TanStack Start uses Vite internally, but that's managed by the framework.
Linting: Biome
Why NOT ESLint + Prettier
- Single tool: Biome replaces both ESLint and Prettier. One config, one CLI, one set of rules.
- 100x faster: Biome is written in Rust and is orders of magnitude faster than ESLint on large codebases.
- Consistent defaults: Biome has opinionated defaults that work well without config. ESLint requires extensive config to match.
- No plugin hell: ESLint's plugin ecosystem is fragmented and version-incompatible. Biome has a fixed rule set that covers 90% of needs.
Package Manager: Bun
Why NOT npm / yarn / pnpm
- Project constraint: The workspace is standardized on Bun.
- Faster installs: Bun's install algorithm is significantly faster than npm/yarn/pnpm.
- Native TypeScript: Bun runs TypeScript directly. No
ts-node,tsx, or build step for development. - Built-in test runner:
bun testreplaces Jest/Vitest. One fewer dependency. - Auto-loads .env: No
dotenvpackage needed. - Built-in bundler:
bun buildreplaces esbuild/webpack for simple builds.
Static Hosting: Cloudflare Workers
Why NOT Cloudflare Pages
- Deprecated investment: Cloudflare has shifted all new feature development to Workers. Pages is in maintenance mode.
- Workers gets all new features: D1 bindings, KV bindings, R2 bindings, Queues — all first-class in Workers.
- Free static assets: Workers serves static assets for free, matching Pages' original value proposition.
- Unified model: Using Workers for everything (compute + static) simplifies the deployment model.
Landing Page: Astro
Why NOT Next.js
- Overkill: Next.js is a full-stack React framework. A landing page doesn't need SSR, API routes, or React hydration.
- Larger bundles: Next.js ships the React runtime. Astro ships zero JS by default.
- Vercel-centric: Same concerns as the dashboard framework choice.
Why NOT Bun.serve()
- Great for apps, not content sites: Bun's HTTP server is excellent for applications. Astro is purpose-built for content sites.
- Better SEO: Astro generates static HTML with zero client-side JS. Search engines love it.
- Performance defaults: Astro's island architecture means interactive components (pricing toggle, wallet connect) load only where needed.
RPC Provider: Helius
Why Helius
- Best Solana RPC provider: Enhanced RPC with DAS API, enhanced transactions, and webhooks.
- Webhooks: Real-time subscription event indexing. Up to 100,000 addresses per webhook.
- DAS API: Digital Asset Standard queries for credential lookups.
- Good pricing: Competitive for our webhook and RPC usage patterns.
Why NOT QuickNode / Triton
- Webhook support: Helius has the most mature webhook system for Solana. QuickNode and Triton have limited webhook capabilities.
- Solana-native focus: Helius is Solana-only. Their tooling and support are specialized.
- DAS API: Helius provides the DAS API natively. Other providers require separate integration.
- Pricing: Helius pricing is more favorable for high-volume webhook and indexing use cases.