Skip to content

Database Schema Overview

Aset database — Supabase (PostgreSQL 15+) with RLS (Row-Level Security). v2.19 · 30 tables · 26 enums · 2 views

🔐 Authentication (3 tables)

ℹ️ Investor-Side Schema

Authentication tables (users, auth_nonces, wallets) are Investor-side schema. Admin backend schema starts from Admin Management section below.

👤 users

ColumnType
idUUID PK
emailTEXT NOT NULL UNIQUE
roleuser_role DEFAULT 'GUEST'
investor_tierinvestor_tier
sumsub_applicant_idTEXT UNIQUE
kyc_levelkyc_level
kyc_statuskyc_status DEFAULT 'NOT_STARTED'
sbt_statussbt_status DEFAULT 'NOT_MINTED'
sbt_tx_hashTEXT
sbt_token_idINTEGER
sbt_errorTEXT
countryTEXT
risk_levelTEXT
company_nameTEXT
company_registration_numberTEXT
company_countryTEXT
created_atTIMESTAMPTZ DEFAULT now()
updated_atTIMESTAMPTZ DEFAULT now()

🔑 auth_nonces

ColumnType
idUUID PK
wallet_addressTEXT NOT NULL UNIQUE
nonceTEXT NOT NULL UNIQUE
expires_atTIMESTAMPTZ NOT NULL
used_atTIMESTAMPTZ
created_atTIMESTAMPTZ DEFAULT now()

💳 wallets

ColumnType
idUUID PK
user_idFK → users (CASCADE)
addressTEXT NOT NULL
chain_idchain_id NOT NULL
labelTEXT
created_atTIMESTAMPTZ DEFAULT now()

UNIQUE(address, chain_id)


🛡️ Admin Management (3 tables)

👤 admin_users

ColumnType
idUUID PK
emailTEXT NOT NULL UNIQUE
nameTEXT
avatar_urlTEXT
wallet_addressTEXT
roleadmin_role DEFAULT 'OPERATOR'
invite_codeTEXT UNIQUE
invited_byFK → admin_users.id
last_active_atTIMESTAMPTZ
deleted_atTIMESTAMPTZ (soft delete)
created_atTIMESTAMPTZ DEFAULT now()
updated_atTIMESTAMPTZ DEFAULT now()
password_hashTEXT
auth_methodTEXT DEFAULT 'GOOGLE_OAUTH'

Auth

Google OAuth or password. First admin seeded via env/DB.

🔒 admin_user_permissions

ColumnType
admin_user_idFK → admin_users (CASCADE)
page_keyTEXT NOT NULL
grantedBOOLEAN DEFAULT false
granted_byFK → admin_users.id
granted_atTIMESTAMPTZ DEFAULT now()

PK(admin_user_id, page_key). Operators only; Admins have full access.

🖥️ admin_sessions

ColumnType
idUUID PK
admin_user_idFK → admin_users (CASCADE)
device_infoTEXT
ip_addressINET
is_currentBOOLEAN DEFAULT false
last_seen_atTIMESTAMPTZ DEFAULT now()
created_atTIMESTAMPTZ DEFAULT now()

🏢 Fund Management (3 tables)

🏦 funds

ColumnType
idUUID PK
nameTEXT NOT NULL
verifiedBOOLEAN DEFAULT false
descriptionTEXT
establishedSMALLINT
total_originatedNUMERIC DEFAULT 0
website_urlTEXT
logo_urlTEXT
primary_contact_nameTEXT
primary_contact_emailTEXT
statusfund_status DEFAULT 'ACTIVE'
notification_healthTEXT DEFAULT 'HEALTHY'
created_atTIMESTAMPTZ DEFAULT now()
updated_atTIMESTAMPTZ DEFAULT now()

👥 fund_members

ColumnType
idUUID PK
fund_idFK → funds (CASCADE)
wallet_addressTEXT
emailTEXT NOT NULL
is_primaryBOOLEAN DEFAULT false
nameTEXT
statusTEXT DEFAULT 'ACTIVE'
joined_atTIMESTAMPTZ DEFAULT now()
created_atTIMESTAMPTZ DEFAULT now()

UNIQUE(fund_id, wallet_address), UNIQUE(fund_id, email)

📨 fund_invites

ColumnType
idUUID PK
fund_idFK → funds (CASCADE)
emailTEXT NOT NULL
invite_codeTEXT NOT NULL UNIQUE
invited_byFK → admin_users.id
statusTEXT DEFAULT 'PENDING'
accepted_atTIMESTAMPTZ
expires_atTIMESTAMPTZ DEFAULT now() + 7 days
created_atTIMESTAMPTZ DEFAULT now()

UNIQUE(fund_id, email)


🏊 Pools (8 tables)

🏊 pools

ColumnType
idUUID PK
fund_idFK → funds.id
nameTEXT NOT NULL
descriptionTEXT
pool_typepool_type
lifecycle_statuslifecycle_status DEFAULT 'DRAFT'
is_pausedBOOLEAN DEFAULT false
investment_blockedBOOLEAN DEFAULT false
categoryTEXT NOT NULL → FK pool_categories(name)
issuerTEXT
capacityNUMERIC DEFAULT 0
min_investmentNUMERIC NOT NULL
apy_rateNUMERIC NOT NULL
termTEXT DEFAULT ''
investor_tierinvestor_tier
asset_countINTEGER DEFAULT 0
chain_idchain_id NOT NULL
accepted_currenciescurrency[] DEFAULT '{USDC}'
lp_issuance_modellp_issuance_model DEFAULT 'PLATFORM_ISSUED'
reserve_percentageNUMERIC DEFAULT 10.00
lockup_daysINTEGER DEFAULT 0
lockup_labelTEXT DEFAULT ''
maturity_daysINTEGER
penalty_typepenalty_type DEFAULT 'NO_EARLY'
penalty_fee_amountNUMERIC
penalty_rateNUMERIC
standard_redemption_daysINTEGER DEFAULT 7
redemption_notesTEXT
collateral_typecollateral_type DEFAULT 'FULLY_COLLATERALIZED'
collateral_ratioNUMERIC
yield_frequencyTEXT DEFAULT 'MONTHLY'
yield_triggerTEXT DEFAULT 'AUTO'
allow_rolloverBOOLEAN DEFAULT false
min_reinvest_amountNUMERIC DEFAULT 50
next_yield_dueTIMESTAMPTZ
yield_overdueBOOLEAN DEFAULT false
start_dateDATE
end_dateDATE
display_after_closeBOOLEAN DEFAULT true
published_atTIMESTAMPTZ
tvlNUMERIC DEFAULT 0
nav_per_tokenNUMERIC NOT NULL
has_pending_nav_updateBOOLEAN DEFAULT false
total_yield_distributedNUMERIC DEFAULT 0
investor_countINTEGER DEFAULT 0
last_nav_updateTIMESTAMPTZ
avg_asset_sizeNUMERIC
avg_tenorTEXT
historical_default_rateNUMERIC
pool_addressTEXT
escrow_addressTEXT
receipt_addressTEXT
lp_token_addressTEXT
pool_walletTEXT
signing_methodTEXT DEFAULT 'SINGLE_SIG'
deploy_statusTEXT DEFAULT 'NOT_DEPLOYED'
deploy_errorTEXT
deploy_tx_hashTEXT
operating_currencyTEXT
fx_rateNUMERIC
supported_chainsINTEGER[] DEFAULT '{8453}'
primary_chain_idINTEGER DEFAULT 8453
deleted_atTIMESTAMPTZ (soft delete)
created_atTIMESTAMPTZ DEFAULT now()
updated_atTIMESTAMPTZ DEFAULT now()

Constraints

  • nav_per_token_range: nav_per_token > 0 AND nav_per_token <= 1.0
  • chk_start_before_end: end_date IS NULL OR start_date IS NULL OR start_date < end_date

Accepted Currencies

Stored as pools.accepted_currencies (a currency[] array), not a junction table. Default: '{USDC}'. Valid values: USDC, USDT, DAI.

🏷️ pool_categories

ColumnType
idUUID PK
nameTEXT NOT NULL UNIQUE
created_atTIMESTAMPTZ DEFAULT now()

🔗 fund_pool_assignments

ColumnType
fund_idFK → funds (CASCADE)
pool_idFK → pools (CASCADE)
assigned_atTIMESTAMPTZ DEFAULT now()

PK(fund_id, pool_id)

🌐 pool_chain_deployments

ColumnType
idUUID PK
pool_idFK → pools (CASCADE)
chain_idINTEGER NOT NULL
pool_contract_addressTEXT
lp_token_addressTEXT
operator_walletTEXT
reserve_balanceNUMERIC DEFAULT 0
total_depositedNUMERIC DEFAULT 0
is_activeBOOLEAN DEFAULT true
deployed_atTIMESTAMPTZ DEFAULT now()
created_atTIMESTAMPTZ DEFAULT now()
updated_atTIMESTAMPTZ DEFAULT now()

UNIQUE(pool_id, chain_id)

📈 pool_tvl_history

ColumnType
idUUID PK
pool_idFK → pools (CASCADE)
tvlNUMERIC NOT NULL
recorded_atTIMESTAMPTZ DEFAULT now()

IDX: pool_id, recorded_at DESC

🧩 pool_asset_compositions

ColumnType
idUUID PK
pool_idFK → pools (CASCADE)
labelTEXT NOT NULL
percentageNUMERIC NOT NULL

Constraints

CHK: percentage >= 0 AND percentage <= 100

📄 pool_documents

ColumnType
idUUID PK
pool_idFK → pools (CASCADE)
nameTEXT NOT NULL
typedocument_type NOT NULL
sizeTEXT
storage_urlTEXT
docusign_view_urlTEXT
created_atTIMESTAMPTZ DEFAULT now()

📦 underlying_assets

ColumnType
idUUID PK
pool_idFK → pools (CASCADE)
categoryTEXT
external_asset_idTEXT
borrower_nameTEXT
principal_valueNUMERIC
maturity_dateTIMESTAMPTZ
statusasset_status DEFAULT 'ACTIVE'
last_synced_atTIMESTAMPTZ

💰 Deposits (1 table)

💰 deposits

ColumnType
idUUID PK
receipt_codeTEXT NOT NULL UNIQUE
user_idFK → users.id
wallet_idFK → wallets.id
pool_idFK → pools.id
amountNUMERIC NOT NULL
is_reinvestmentBOOLEAN DEFAULT false
refund_eligible_atTIMESTAMPTZ
matched_atTIMESTAMPTZ
investor_addressTEXT
currencyTEXT DEFAULT 'USDC'
statusdeposit_status DEFAULT 'PENDING'
received_atTIMESTAMPTZ
receipt_issued_atTIMESTAMPTZ
receipt_tx_hashTEXT
fm_notified_atTIMESTAMPTZ
lp_minted_atTIMESTAMPTZ
lp_mint_tx_hashTEXT
receipt_burned_atTIMESTAMPTZ
receipt_burn_tx_hashTEXT
escrow_statusTEXT DEFAULT 'HELD'
receipt_token_idTEXT
tx_hashTEXT
chain_idINTEGER DEFAULT 8453
failure_typeTEXT
error_messageTEXT
created_atTIMESTAMPTZ DEFAULT now()
updated_atTIMESTAMPTZ

IDX: pool_id, user_id, wallet_id, status, investor_address, refund_eligible_at (partial)


💼 Portfolio (1 table)

💼 portfolio_positions

ColumnType
idUUID PK
user_idFK → users.id
pool_idFK → pools.id
tokensNUMERIC NOT NULL
effective_valueNUMERIC NOT NULL
accrued_yieldNUMERIC DEFAULT 0
invested_atTIMESTAMPTZ NOT NULL
created_atTIMESTAMPTZ DEFAULT now()
updated_atTIMESTAMPTZ DEFAULT now()

UNIQUE(user_id, pool_id)


🔄 Redemptions (1 table)

🔄 redemption_requests

ColumnType
idUUID PK
user_idFK → users.id
pool_idFK → pools.id
position_idFK → portfolio_positions.id
amountNUMERIC NOT NULL
payout_amountNUMERIC
penalty_amountNUMERIC DEFAULT 0
nav_at_requestNUMERIC
investor_addressTEXT
lp_tokens_amountNUMERIC
statusredemption_status DEFAULT 'REQUESTED'
transfer_sourcetransfer_source DEFAULT 'PLATFORM'
tx_hashTEXT
payout_tx_hashTEXT
on_chain_request_idTEXT
requested_atTIMESTAMPTZ DEFAULT now()
fm_notified_atTIMESTAMPTZ
fm_accepted_atTIMESTAMPTZ
contract_validated_atTIMESTAMPTZ
auto_released_atTIMESTAMPTZ
admin_idFK → admin_users.id
admin_decided_atTIMESTAMPTZ
rejection_reasonTEXT
funding_statusTEXT DEFAULT 'FUNDED'
funding_shortfallNUMERIC
funding_deadlineTIMESTAMPTZ
lp_burned_atTIMESTAMPTZ
completed_atTIMESTAMPTZ
failure_typeTEXT
error_messageTEXT
created_atTIMESTAMPTZ DEFAULT now()
updated_atTIMESTAMPTZ

IDX: pool_id, user_id, position_id, status, investor_address


📊 Yield (3 tables)

📊 yield_distributions

ColumnType
idUUID PK
pool_idFK → pools.id
total_amountNUMERIC NOT NULL
distributed_byFK → admin_users.id
distributed_atTIMESTAMPTZ DEFAULT now()
distribution_typeTEXT DEFAULT 'MANUAL'
apy_rateNUMERIC
period_startTIMESTAMPTZ
period_endTIMESTAMPTZ
statusyield_status DEFAULT 'PENDING'
periodTEXT
investor_countINTEGER DEFAULT 0
tx_hashTEXT
fm_notified_atTIMESTAMPTZ
tx_submitted_atTIMESTAMPTZ
claimed_atTIMESTAMPTZ
failure_typeTEXT
error_messageTEXT
created_atTIMESTAMPTZ DEFAULT now()
updated_atTIMESTAMPTZ

IDX: pool_id, status

📊 yield_distribution_investors

ColumnType
idUUID PK
distribution_idFK → yield_distributions (CASCADE)
user_idUUID
investor_addressTEXT
investor_nameTEXT
share_percentageNUMERIC
amountNUMERIC NOT NULL
tx_hashTEXT
statusyield_status DEFAULT 'PENDING'
claimed_atTIMESTAMPTZ
claim_tx_hashTEXT
claim_typeTEXT
reinvest_lp_amountNUMERIC
created_atTIMESTAMPTZ DEFAULT now()

IDX: distribution_id, user_id

💎 yield_claims

ColumnType
idUUID PK
user_idFK → users.id
pool_idFK → pools.id
position_idFK → portfolio_positions.id
amountNUMERIC NOT NULL
statusTEXT DEFAULT 'PENDING'
tx_hashTEXT
failure_typeTEXT
error_messageTEXT
created_atTIMESTAMPTZ DEFAULT now()
completed_atTIMESTAMPTZ
updated_atTIMESTAMPTZ DEFAULT now()

IDX: user_id, pool_id


📊 NAV History (1 table)

📊 nav_history

ColumnType
idUUID PK
pool_idFK → pools.id
old_navNUMERIC NOT NULL
new_navNUMERIC NOT NULL
sourceTEXT DEFAULT 'admin_override'
proposed_byFK → admin_users.id
proposed_atTIMESTAMPTZ DEFAULT now()
effective_atTIMESTAMPTZ NOT NULL
statusnav_update_status DEFAULT 'PENDING'
reasonTEXT

Constraints

  • nav_positive: new_nav > 0
  • nav_max: new_nav <= 1.0

IDX: pool_id, status


🪪 KYC / Identity (1 table)

🪪 kyc_logs

ColumnType
idUUID PK
user_idFK → users (CASCADE)
provider_idTEXT
status_resultTEXT NOT NULL
risk_scoreNUMERIC
reviewed_atTIMESTAMPTZ DEFAULT now()
reject_typereject_type
reject_reasonTEXT

IDX: user_id


🔔 Notifications (2 tables)

🔔 notification_logs

ColumnType
idUUID PK
recipient_typerecipient_type NOT NULL
recipient_idTEXT NOT NULL
recipient_nameTEXT
event_typeTEXT NOT NULL
channelnotification_channel DEFAULT 'EMAIL'
statusnotification_status DEFAULT 'DELIVERED'
failure_typenotification_failure_type
error_messageTEXT
retry_countINTEGER DEFAULT 0
notification_contentTEXT
related_entity_typeTEXT
related_entity_idUUID
delivered_atTIMESTAMPTZ
created_atTIMESTAMPTZ DEFAULT now()

IDX: status (partial on FAILED), recipient_type + recipient_id, related_entity_type + related_entity_id

⚙️ notification_preferences

ColumnType
user_typeTEXT NOT NULL
user_idUUID NOT NULL
categoryTEXT NOT NULL
email_enabledBOOLEAN DEFAULT true

PK(user_type, user_id, category)


⚡ Platform Config & Activity (3 tables)

⚙️ platform_config

ColumnType
keyTEXT PK
valueTEXT NOT NULL
updated_byFK → admin_users.id
updated_atTIMESTAMPTZ DEFAULT now()

📋 activity_events

ColumnType
idUUID PK
event_typeTEXT NOT NULL
descriptionTEXT NOT NULL
status_badgeTEXT NOT NULL
actor_idUUID
metadataJSONB
related_entity_typeTEXT
related_entity_idUUID
created_atTIMESTAMPTZ DEFAULT now()

IDX: created_at DESC, related_entity_type + related_entity_id

🪙 stablecoin_registry

ColumnType
idUUID PK
chain_idINTEGER NOT NULL
symbolTEXT NOT NULL
contract_addressTEXT NOT NULL
decimalsINTEGER DEFAULT 6
is_activeBOOLEAN DEFAULT true

UNIQUE(chain_id, symbol)


Enums Reference

v2.19 schema — 26 enum types.

EnumValuesNotes
admin_roleOPERATOR, ADMIN, SUPER_ADMIN, FUND_MANAGER
asset_statusACTIVE, SETTLED, OVERDUE
chain_id8217, 8453, 11155111, 84532Kaia, Base, Sepolia, Base Sepolia
collateral_typeFULLY_COLLATERALIZED, PARTIALLY_COLLATERALIZED, UNSECURED
currencyUSDC, USDT, DAI
deposit_statusPENDING, PROCESSING, COMPLETED, REFUNDED, FAILED
document_typePPM, SUBSCRIPTION, AUDIT, RISK_DISCLOSURE
fund_statusACTIVE, INACTIVE
investor_tierACCREDITED, QP
kyc_levelINDIVIDUAL, INSTITUTION
kyc_statusNOT_STARTED, IN_REVIEW, APPROVED, REJECTED
lifecycle_statusDRAFT, UPCOMING, ACTIVE, CLOSED, MATURED
lp_issuance_modelPLATFORM_ISSUED, FUND_ISSUED
nav_update_statusPENDING, APPLIED
notification_channelEMAIL
notification_failure_typeBOUNCED, SPAM_FILTERED, SERVER_ERROR, TIMEOUT, INVALID_RECIPIENT, RATE_LIMITED
notification_statusSENDING, DELIVERED, FAILED
penalty_typeYIELD_BASED, PRINCIPAL_BASED, NO_EARLY, FLAT_FEE
pool_typeAS_POOL, FUND_POOL
recipient_typeADMIN, FM, INVESTOR
redemption_statusREQUESTED, FM_ACCEPTED, PROCESSING, COMPLETED, FAILED
reject_typeRETRY, FINAL
sbt_statusNOT_MINTED, MINTED, FAILED
transfer_sourcePLATFORM, FUND
user_roleGUEST, INVESTOR
yield_statusPENDING, PROCESSING, DISTRIBUTED, FAILED

Views

dashboard_alert_counts

sql
SELECT
  COUNT(*) FROM notification_logs WHERE status = 'FAILED'   AS failed_notifications,
  COUNT(*) FROM users WHERE sbt_status = 'FAILED'           AS sbt_mint_failed,
  COUNT(*) FROM deposits WHERE status = 'FAILED'            AS failed_deposits,
  COUNT(*) FROM redemption_requests WHERE status = 'FAILED'  AS failed_redemptions,
  COUNT(*) FROM yield_distributions WHERE status = 'FAILED'  AS failed_yield,
  COUNT(*) FROM users WHERE kyc_status IN ('NOT_STARTED', 'IN_REVIEW') AS kyc_pending,
  COUNT(*) FROM redemption_requests WHERE status = 'REQUESTED' AS pending_redemptions,
  COUNT(*) FROM yield_distributions WHERE status = 'PENDING' AS pending_yield;

platform_stats

sql
SELECT
  COALESCE(SUM(tvl), 0)                    AS total_tvl,
  COALESCE(SUM(total_yield_distributed), 0) AS total_yield,
  COALESCE(SUM(investor_count), 0)          AS total_investors
FROM pools
WHERE lifecycle_status = 'ACTIVE';

Data Types Reference

TypeDescription
PKPrimary Key — Unique ID, no duplicates
FKForeign Key — Links to another table's PK
UUIDUnique ID — Random 128-bit, globally unique
ENUMFixed Options — Only predefined values
TEXTVariable-length string
NUMERICPrecise Number — Exact, used for money
BOOLEANTrue / False flag
TIMESTAMPTZDate + Time with timezone
INTEGERWhole Number — No decimals
SMALLINTSmall Whole Number — 2 bytes
JSONBJSON Binary — Structured data, queryable
INETIP Address — IPv4 or IPv6
DATEDate Only — No time component

Relationship Map

  • users <-1:N-> wallets
  • users <-1:N-> kyc_logs
  • users <-1:N-> deposits
  • users <-1:N-> portfolio_positions
  • users <-1:N-> redemption_requests
  • users <-1:N-> yield_claims
  • admin_users <-1:N-> admin_user_permissions
  • admin_users <-1:N-> admin_sessions
  • admin_users <-1:N-> fund_invites (invited_by)
  • funds <-1:N-> fund_members
  • funds <-1:N-> fund_invites
  • funds <-M:N-> pools (via fund_pool_assignments)
  • pools <-FK-> funds (fund_id)
  • pools <-FK-> pool_categories (category → name)
  • pools <-1:N-> pool_chain_deployments
  • pools <-1:N-> pool_tvl_history
  • pools <-1:N-> pool_asset_compositions
  • pools <-1:N-> pool_documents
  • pools <-1:N-> underlying_assets
  • pools <-1:N-> nav_history
  • pools <-1:N-> deposits
  • pools <-1:N-> portfolio_positions
  • pools <-1:N-> redemption_requests
  • pools <-1:N-> yield_distributions <-1:N-> yield_distribution_investors
  • pools <-1:N-> yield_claims
  • portfolio_positions <-1:N-> redemption_requests (position_id)
  • portfolio_positions <-1:N-> yield_claims (position_id)
  • yield_distributions <-N:1-> admin_users (distributed_by)
  • redemption_requests <-N:1-> admin_users (admin_id)
  • admin_users <-1:N-> activity_events (actor_id)
  • auth_nonces — standalone (SIWE authentication)
  • notification_logs — standalone (polymorphic recipient)
  • notification_preferences — standalone (polymorphic user)
  • platform_config <-N:1-> admin_users (updated_by)
  • stablecoin_registry — standalone

Current Version

v2.19 — 30 tables · 26 enums · 2 views