Database Provider
Supabase
Version:
@supabase/supabase-js v2.95.3PostgreSQL database with authentication, real-time subscriptions, and automatic API generation.Connection Setup
The Supabase client is initialized insrc/lib/supabaseClient.ts:
Data Sources
Deriverse pulls data from three sources:Live Blockchain Data
Fetched via
HeliusService and DeriverseTradeService from Solana devnet/mainnet.Table: user_wallets
Purpose: Track Solana wallet addresses that have been looked up, with metadata about connection method, network, and sync status.
Schema Definition
Field Descriptions
| Field | Type | Nullable | Default | Description |
|---|---|---|---|---|
id | UUID | No | gen_random_uuid() | Primary key, auto-generated |
wallet_address | TEXT | No | - | Full Solana public key (44 characters) |
network | TEXT | No | - | Solana network: 'devnet', 'mainnet-beta', or 'mock' |
wallet_provider | TEXT | No | 'manual' | Wallet app: 'Phantom', 'Solflare', or 'manual' |
connection_method | TEXT | No | 'manual' | How address was obtained: 'manual' or 'wallet_connect' |
last_synced_at | TIMESTAMPTZ | Yes | NULL | When trades were last fetched from blockchain |
created_at | TIMESTAMPTZ | No | NOW() | When this wallet was first added |
updated_at | TIMESTAMPTZ | No | NOW() | When any field was last modified |
Design Decisions
Why defaults for wallet_provider and connection_method?
Why defaults for wallet_provider and connection_method?
- Avoids NULL complexity in queries and application code
'manual'is a sensible default for manually entered addresses- Overridden to actual wallet name (e.g.,
'Phantom') when using wallet connect - Reduces need for null checks in TypeScript
Why is last_synced_at nullable?
Why is last_synced_at nullable?
NULLexplicitly means “never synced” (wallet saved but trades not fetched yet)- Allows saving wallet address before running blockchain lookup
- Used to show “Last updated X hours ago” or “Never synced” in UI
Why separate updated_at and last_synced_at?
Why separate updated_at and last_synced_at?
updated_at: Database metadata (when ANY field changed)last_synced_at: Business logic (when blockchain data was fetched)- Serves different purposes: audit trail vs data freshness
Example Records
Table: trades
Purpose: Cache blockchain trade data from Deriverse for fast loading, offline analytics, and historical preservation.
Schema Definition
Key Fields
Trade Identity
id: Unique trade identifiertx_signature: Blockchain transaction hashTrade Details
symbol, side, order_typequantity, price, notionalPerformance
pnl: Profit and lossis_win: Boolean success flagfee, fee_breakdownStorage Estimates
Per Trade: ~420 bytes (350 bytes data + 70 bytes indexes) Supabase Free Tier (500 MB):| Trades | Storage | % Used |
|---|---|---|
| 1,000 | 420 KB | 0.08% |
| 10,000 | 4.2 MB | 0.84% |
| 100,000 | 42 MB | 8.4% |
| 1,000,000 | 420 MB | 84% |
Index overhead is ~20% but provides 100x faster queries. Trade-off: 2-3ms slower inserts for sub-millisecond lookups.
NUMERIC vs FLOAT
- Why NUMERIC?
- FLOAT Issues
Financial Precision
- Exact decimal representation (0.1 + 0.2 = 0.3) ✅
- No floating-point rounding errors
- Industry standard for financial data
- Handles crypto decimals (e.g., 0.000000000003)
Table: trade_annotations
Purpose: Store user journal entries, tags, and lessons learned for each trade. Migrated from localStorage for cross-device sync.
Schema (Planned)
Annotations currently use localStorage as a fallback, synced to Supabase when online. Full migration planned for future release.
Data Flow
Wallet Lookup Flow
The application follows this sequence when loading trade data:Caching Strategy
Deriverse implements a time-based caching strategy to balance data freshness with RPC efficiency:Row Level Security (RLS)
Policy Configuration
Supabase RLS ensures users can only access their own data:Service Layer
SupabaseWalletService
Core methods for wallet management:src/services/SupabaseWalletService.ts
SupabaseTradeService
Manages trade data persistence:src/services/SupabaseTradeService.ts
All save operations use
upsert to ensure idempotency. Calling saveTrades() multiple times with the same data is safe.Client-Side Storage
localStorage Fallback
Annotations use localStorage as an offline-first fallback:src/lib/annotationStorage.ts
Migration Notes
Current State (February 2026)
Implemented
user_walletstable with RLStradescaching with 24-hour strategy- Wallet lookup and sync
- Service layer abstractions
Planned
trade_annotationsfull migration- Real-time subscriptions
- Pre-calculated analytics tables
- SWR/TanStack Query integration
Backward Compatibility
- Mock Mode: Works without database connection
- Lookup Feature: Functions without Supabase (slower, no caching)
- Annotations: localStorage fallback prevents data loss
The database is additive-only. No breaking changes have been introduced since initial launch.
Performance Optimizations
Current Optimizations
- Indexes:
wallet_address,last_synced_at,opened_at,symbol - Caching: 24-hour window prevents redundant blockchain queries
- Upsert Logic: Prevents duplicate entries and race conditions
- JSONB Fields: Efficient storage for
fee_breakdownwith GIN indexing
Future Enhancements
Real-time Subscriptions
Real-time Subscriptions
Use Supabase real-time to push live trade updates to connected clients without polling.
Materialized Views
Materialized Views
Pre-calculate win rate, total PnL, and average trade duration for instant analytics.
SWR Integration
SWR Integration
Client-side caching with
useSWR or TanStack Query for optimistic updates.Partial Updates
Partial Updates
Fetch only new trades since
last_synced_at instead of full wallet history.Last Updated: February 2026 For architecture details, see Architecture. For deployment setup, see Deployment.
