Skip to content

Dashboard & Admin Stack

VelaPay has two operational app surfaces: the merchant dashboard and the admin dashboard. They share a common architectural pattern but serve distinct user roles and have separate deployment targets.


Merchant Dashboard (vela-dashboard)

The primary product surface for merchants. Plan creation, subscriber management, revenue analytics, and Arcium-encrypted billing insights.

AreaCurrent Choice
UI FrameworkReact 19
RoutingTanStack Router (file-based, fully type-safe)
Data FetchingTanStack Query (server state management)
API LayerHono (mounted as /api/* routes)
AuthenticationBetter Auth (email-first, SIWS for wallet linking)
DatabaseDrizzle ORM + Cloudflare D1
Build ToolingVite
DeploymentCloudflare Worker (Worker-compatible backend)
Infra TouchpointsHelius webhooks, D1, KV (sessions), R2 (exports/assets), Queues

Key Features

  • Plan builder: create subscription plans with pricing, frequency, trial periods, billing mode (fixed/streaming/usage)
  • Subscriber management: active, churned, past-due status with real-time updates via Helius webhooks
  • Revenue analytics: MRR, churn, ARPU computed inside Arcium MXE, decrypted client-side for the merchant only
  • Invoice management: auto-generated invoices, PDF delivery, failed payment handling with grace periods
  • Payment links: shareable URLs (pay.velapay.com/link/{slug}) with conversion analytics
  • Webhook integration: idempotent event processing for pull_executed, subscription_created, subscription_cancelled

Admin Dashboard (vela-admin)

The protocol operator surface. Protocol configuration, audit logging, and cross-merchant visibility.

AreaCurrent Choice
UI FrameworkReact 19
Routing + DataTanStack Router + TanStack Query
API/RuntimeHono on Cloudflare Worker
Signing ModelBrowser wallet signing (SIWS)
Audit ModelServer-side audit logging for all admin actions
Cross-Service PatternService binding into the dashboard backend

Key Features

  • Protocol config management (pause/unpause, fee structure, keeper settings)
  • Admin-level mandate inspection
  • Audit trail for all operator actions
  • Cross-merchant analytics (aggregate, not per-merchant detail)

Shared Architectural Pattern

Both apps use the same stack and differ only in deployment target and data access patterns:

┌─────────────────────────────────────────┐
│  React 19 + TanStack Router/Query       │
│  (type-safe routing, server state)      │
├─────────────────────────────────────────┤
│  Hono API Layer                         │
│  (middleware, routes, validation)        │
├─────────────────────────────────────────┤
│  Drizzle ORM → D1                       │
│  (type-safe queries, batch API)         │
├─────────────────────────────────────────┤
│  Cloudflare Worker                      │
│  (edge deployment, bindings)            │
└─────────────────────────────────────────┘

The dashboard owns the D1 database. The admin does not have its own database — it accesses the dashboard's data through a service binding.


Auth Stack

Better Auth: Email-First with Wallet Linking

The auth system is built on Better Auth, which provides:

  • Email + password as the primary authentication method
  • Magic link fallback (email-based, no password required)
  • SIWS (Sign-In With Solana) for wallet-based authentication
  • Session management via Cloudflare KV (session tokens stored in KV, not D1)

Why Better Auth

Better AuthAlternatives
Native D1 + Drizzle adapterPrisma — no D1 support
Hono middlewareNextAuth — Next.js-centric
Self-hosted, no vendor lock-inClerk/Auth0 — vendor lock-in, usage pricing
batch() API for D1 atomicityD1 doesn't support interactive transactions

Auth Flow

  1. Merchant signs up via email → Better Auth creates user in D1
  2. Merchant links wallet via SIWS → wallet address stored in D1, associated with user
  3. Session created in KV → API middleware validates on every request
  4. Wallet operations (signing transactions) happen client-side via Wallet Standard

Portal Auth (vela-portal)

The customer portal uses a different auth pattern:

  • SIWS standard — domain-bound signing (portal.velapay.com), phishing-resistant
  • Magic link fallback — subscriber can auth via email if they provided one at checkout
  • Multi-wallet linking — subscriber can link multiple wallets to see all mandates
  • Session tiers — 4-hour browsing session, fresh SIWS for destructive actions (cancel, switch plan)

Data Layer

Drizzle ORM + Cloudflare D1

AspectDetail
ORMDrizzle 0.45.x, first-class D1 support
DialectSQLite (D1 is SQLite-compatible)
Migrationsdrizzle-kit generate for SQL migrations, drizzle-kit push for dev
AtomicityD1 batch() API — no interactive transactions
Migration pathDrizzle's dialect abstraction enables Postgres migration at PMF without schema rewrite

D1 Batch API for Atomicity

D1 does not support interactive transactions (BEGIN/COMMIT). Instead, Drizzle uses D1's batch() API, which groups multiple statements into a single atomic operation. This is the correct pattern for:

  • Invoice creation + line item insertion
  • Session creation + user metadata update
  • Payment event processing + subscriber state update

Key Database Tables

TablePurpose
usersBetter Auth user records
sessionsAuth sessions
plansSubscription plan definitions
checkout_sessionsHosted checkout session state
payment_linksShareable payment link metadata
invoicesInvoice records with PDF references
billing_eventsHelius webhook event log (idempotent)
subscriber_snapshotsDenormalized subscriber state for dashboard queries

Service Binding: Admin → Dashboard

The admin dashboard does not duplicate the backend. Instead, it uses Cloudflare service bindings to call the dashboard's API:

toml
# vela-admin/wrangler.toml
[[services]]
binding = "DASHBOARD"
service = "vela-dashboard-api"

This means:

  • Admin's Hono routes proxy to the dashboard backend via env.DASHBOARD.fetch()
  • Admin authenticates with its own auth token (DASHBOARD_PROXY_AUTH_TOKEN)
  • No public API exposure between admin and dashboard — internal Worker-to-Worker communication
  • Admin can access all dashboard data without exposing a separate database connection

This pattern also applies to vela-checkout and vela-portal, which both bind to the dashboard for session and invoice data.


Why They're Separate

The split is a real product boundary, not just a code organization choice:

AspectDashboardAdmin
UsersMerchantsProtocol operators
Data ownershipPer-merchant isolationCross-merchant aggregate
Auth modelEmail + walletWallet-only (admin keypair)
Deployment couplingOwns D1 databaseService binding, no own DB
Audit requirementsStandardEnhanced (all actions logged)
ScalingPer-merchant trafficLow traffic, high privilege

The v1.2 planning and audit trail show this split is now a real operational boundary, not a future design note. The admin surface handles protocol-level operations that should never be exposed to merchant traffic, and the service-binding pattern ensures the admin never needs direct database access.

Internal knowledge base for the Vela Labs workspace.