Database Schema Configuration
Overview
The Uniswap V2 adapter uses a well-structured database schema to manage pool configurations, state, and user balances. This schema ensures data integrity and supports efficient querying.
GraphQL Schema Definition
type Token @entity {
id: ID!
address: String! @index
decimals: Int!
coingeckoId: String
}
type PoolConfig @entity {
id: ID!
token0: Token!
token1: Token!
lpToken: Token!
}
type PoolState @entity {
id: ID!
pool: PoolConfig! @index
reserve0: BigInt!
reserve1: BigInt!
totalSupply: BigInt!
lastBlock: Int!
lastTsMs: BigInt!
lastInterpolatedTs: BigInt
updatedAt: DateTime!
}
type ActiveBalances @entity {
id: ID!
activeBalancesMap: JSON!
}
type PoolProcessState @entity {
id: ID!
pool: PoolConfig! @index
lastInterpolatedTs: BigInt
}
Entity Relationships
erDiagram
PoolConfig ||--|| Token : "token0"
PoolConfig ||--|| Token : "token1"
PoolConfig ||--|| Token : "lpToken"
PoolState ||--|| PoolConfig : "pool"
PoolProcessState ||--|| PoolConfig : "pool"
PoolConfig {
string id PK
Token token0 FK
Token token1 FK
Token lpToken FK
}
Token {
string id PK
string address
int decimals
string coingeckoId
}
PoolState {
string id PK
PoolConfig pool FK
bigint reserve0
bigint reserve1
bigint totalSupply
int lastBlock
bigint lastTsMs
datetime updatedAt
}
PoolProcessState {
string id PK
PoolConfig pool FK
bigint lastInterpolatedTs
}
ActiveBalances {
string id PK
json activeBalancesMap
}
Entity Details
Token Entity
Purpose: Represents any ERC-20 token (underlying tokens or LP tokens)
interface Token {
id: string; // Unique identifier: "{address}-{type}"
address: string; // Contract address (indexed for fast lookups)
decimals: number; // Token decimal places (6 for USDC, 18 for WETH)
coingeckoId?: string; // CoinGecko ID for price fetching (null for LP tokens)
}
Examples:
// USDC Token
{
id: "0xA0b86a33E6441E8bD80E4F3a0124E53461a0f4b5-token0",
address: "0xA0b86a33E6441E8bD80E4F3a0124E53461a0f4b5",
decimals: 6,
coingeckoId: "usd-coin"
}
// LP Token (no CoinGecko ID)
{
id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc-lp",
address: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc",
decimals: 18,
coingeckoId: null
}
PoolConfig Entity
Purpose: Immutable configuration linking a pool to its three tokens
interface PoolConfig {
id: string; // "{poolAddress}-config"
token0: Token; // First underlying token (lower address)
token1: Token; // Second underlying token (higher address)
lpToken: Token; // LP token representing pool shares
}
Relationships:
- One PoolConfig has exactly 3 Tokens (token0, token1, lpToken)
- Created once during pool initialization
- Never modified after creation
PoolState Entity
Purpose: Current blockchain state of the pool (updated frequently)
interface PoolState {
id: string; // "{poolAddress}-state"
pool: PoolConfig; // Reference to pool configuration (indexed)
reserve0: bigint; // Current token0 reserves
reserve1: bigint; // Current token1 reserves
totalSupply: bigint; // Current LP token total supply
lastBlock: number; // Last processed block number
lastTsMs: bigint; // Last processed timestamp (milliseconds)
lastInterpolatedTs?: bigint; // Unused field (legacy)
updatedAt: Date; // Database update timestamp
}
Update Pattern:
- Updated every time LP token price is calculated
- Synchronized with blockchain via
updatePoolStateFromOnChain()
- Critical for accurate pricing calculations
PoolProcessState Entity
Purpose: Tracks processing state for periodic operations
interface PoolProcessState {
id: string; // "{poolAddress}-process-state"
pool: PoolConfig; // Reference to pool configuration (indexed)
lastInterpolatedTs?: bigint; // Last time balance flush was performed
}
Usage:
- Controls timing for
EXHAUSTED
balance window creation - Ensures proper time boundaries across processor restarts
- Updated during periodic balance flush operations
ActiveBalances Entity
Purpose: Serialized storage of in-memory user balance state
interface ActiveBalances {
id: string; // "{poolAddress}-active-balances"
activeBalancesMap: JSON; // Serialized Map<string, ActiveBalance>
}
JSON Structure:
{
"0x742d35Cc1F54834567...": {
"balance": "1500000000000000000",
"updatedBlockTs": 1640995200,
"updatedBlockHeight": 13916166
},
"0x8ba1f109551bD432...": {
"balance": "50000000000000000000",
"updatedBlockTs": 1640998800,
"updatedBlockHeight": 13916200
}
}
Database Operations
Initialization Flow
// 1. Load existing entities
const poolConfig = await ctx.store.findOne(PoolConfig, {
where: { id: `${contractAddress}-config` },
relations: { token0: true, token1: true, lpToken: true }
});
// 2. Create if missing
if (!poolConfig) {
const newConfig = new PoolConfig({
id: `${contractAddress}-config`,
token0: new Token({...}),
token1: new Token({...}),
lpToken: new Token({...})
});
}
State Updates
// Update pool state with fresh blockchain data
poolState.reserve0 = newReserve0;
poolState.reserve1 = newReserve1;
poolState.totalSupply = newTotalSupply;
poolState.updatedAt = new Date();
// Persist to database
await ctx.store.upsert(poolState);
Active Balance Persistence
// Convert Map to JSON for storage
const activeBalancesEntity = new ActiveBalances({
id: `${contractAddress}-active-balances`,
activeBalancesMap: mapToJson(activeBalancesMap),
});
await ctx.store.upsert(activeBalancesEntity);
Schema Design Principles
1. Separation of Concerns
- Config: Immutable pool/token metadata
- State: Mutable blockchain state
- Process: Processing timing state
- Balances: User balance snapshots
2. Efficient Indexing
@index
on frequently queried fields (addresses, pool references)- Composite IDs for fast lookups
- Minimal relations to reduce query complexity
3. Data Integrity
- Required fields marked with
!
- Foreign key relationships ensure referential integrity
- BigInt for precise financial amounts
4. Performance Optimization
- JSON storage for dynamic balance maps
- Separate entities to minimize update scope
- Indexed pool references for fast filtering
This schema provides a robust foundation for tracking Uniswap V2 pool state, user balances, and processing metadata while maintaining high performance and data integrity.