Overview & Architecture

EmergeFi is an institutional-grade RWA tokenization platform. Investors deposit stablecoins into pools backed by real-world assets (invoice financing, earned wage access, hotel STOs) and earn yield through LP tokens.

Tech Stack

🖥️ Frontend
Vite + React + TypeScript
React Router · Tailwind 4 · RainbowKit · shadcn/ui
⚡ Backend
AWS CDK + Lambda (Node.js)
API Gateway v2 · CloudFront · Supabase JS
📜 Smart Contracts
Foundry + Solidity
OpenZeppelin · 6 contracts · Base Chain
🗄️ Database
Supabase (PostgreSQL + RLS)
Hosted · Row-level security · supabase-js
🔗 Chain
Base Sepolia + Base Mainnet
+ KAIA (prod) · SIWE auth · wagmi + viem
👤 Auth
SIWE (Sign-In with Ethereum)
JWT via jose · RainbowKit · 30-min timeout

Chain Deployment

🧪 Dev
Base Sepolia
Testnet for all development and staging
🌐 Prod
Base Mainnet + KAIA
Dual-chain production deployment

Smart Contract Registry

Contract Standard Purpose
EmergeFiLPToken ERC-20 + AccessControl LP token — whitelist-only transfers, auto-yield settlement
EmergeFiEscrow AccessControl + ReentrancyGuard USDC escrow — 7-day auto-refund protection
EmergeFiEscrowFactory Factory pattern Deploys new escrow instances per pool
EmergeFiReceipt ERC-721 Proof of deposit, status tracking (PENDING → LP_VERIFIED / REFUNDED)
EmergeFiKYCSoulbound Non-transferable ERC-721 On-chain KYC status via SumSub — non-transferable identity credential
EmergeFiPool AccessControl Pool management — deposits, NAV, role-based access

Core Concepts

Foundational models and mechanisms that define how EmergeFi works. Cards with 🟡 BD or 🟠 PD badges have open decisions — click the badge to view options.

🔀 Dual LP Issuance Model
Two modes: Model A (Fund-Issued) — the fund mints its own LP token; EmergeFi verifies receipt via RPC. Model B (EmergeFi-Issued) — EmergeFi deploys and mints the LP token on the fund's behalf. Same investor UX either way.
Model A: Fund mints on its own contract → Operator verifies LP via RPC → USDC released from escrow. Model B: EmergeFi deploys LP contract on fund’s behalf → mints LP to investor → USDC released simultaneously.
💰 NAV Token Pricing
LP token price floats between $0.00–$1.00 (no floor — PD6 removed Feb 19). Token price IS the loss indicator — no separate PF multiplier. New investors pay the current NAV (fair entry). Investment always stays open — never auto-blocked. tokens_minted = deposit / nav_per_token.
Deposit $10,000 when NAV = $0.92 → you receive 10,000 / 0.92 = 10,869 tokens. At writedown to $0.92 your tokens are worth $10,000 but you get more tokens than original investors. On recovery to $1.00 your tokens are worth $10,869.
🔮 Oracle-Driven NAV Updates
Fund operator calculates NAV off-chain → reports to oracle endpoint → oracle validates and posts updateNAV() on EmergeFiPool → NAV decreases enter 24h timelock, increases apply immediately. No safety bounds (PD6 removed). timelock. NAV increases: immediate.
Operator submits NAV off-chain → oracle validates and calls updateNAV() on EmergeFiPool → if decrease: enters 24h pending queue → after timelock: nav_per_token updated on-chain. NAV increases skip the queue and apply immediately.
🛡️ Reserve Fund
Proposed: 10% of pool deposits allocated as first-loss reserve. Covers losses before NAV drops below $1.00. Open questions: per-pool vs platform-wide %, how reserve is funded (upfront vs accrued), what triggers payout vs writedown.
Fund operator sets a reserve % at pool creation → reserve absorbs loss first → NAV only drops once reserve is exhausted. Proposed: 10% of pool deposits allocated to reserve at creation time (not accrued over time). Reserve payout vs writedown trigger is an open question (BD4).
📈 Yield Model
Recommended: Distributing — LP tokens at $1.00 fixed, yield paid separately as USDC or LP tokens. Yield accrues on nominal balance (original deposit), not NAV-adjusted value. Auto-settled before any LP transfer.
Admin records a yield distribution event → LP price stays fixed at $1.00 → USDC (or LP tokens) distributed separately to investor wallets. Yield formula: nominal_amount × (APY / 365) × days_held. NAV fluctuation does not affect yield calculation.
🔒 Escrow Architecture
USDC goes to an EmergeFi escrow contract (on the fund's chain), not directly to the fund. Operator releases escrowed USDC only after LP issuance confirmed. 7-day auto-refund if LP not issued — no manual intervention needed.
Investor deposits USDC → EmergeFiEscrow holds funds → LP issued to investor → Operator calls releaseToFund() → USDC transferred to fund treasury. If LP not issued within 7 days: autoRefund() executes, USDC returned to investor.
🎫 Receipt Token (ERC-721)
Proof-of-deposit NFT minted when investor deposits to escrow. Tracks status: MINTED → LP_PENDING → LP_VERIFIED or → REFUNDED. Not the LP token — just a receipt. Burned when investment fully settles.
🪪 KYC / Soulbound Token
SumSub integration for KYC/KYB. On approval, a non-transferable ERC-721 (Soulbound Token) is minted on-chain. kyc_status = APPROVED required before any investment. ❓ Open: what happens if KYC is revoked — can investor still redeem?

Investment Lifecycle

End-to-end flow from investor deposit to yield distribution across both LP issuance models.

Investor
System (Auto)
Operator
Admin
Fund Issuer

Phase 1 — Deposit (Both Models)

① Investor connects wallet & passes KYC Investor
SumSub KYC → SBTSoulbound Token — a non-transferable on-chain credential that proves identity verification minted on-chain → kyc_status = APPROVED
② Investor selects pool & enters stablecoin amount Investor
System checks: is_paused = false, kyc_status = APPROVED. NAV model keeps investment open during writedowns — no auto-block.
③ Stablecoin transferred to escrow contract System
On fund's chain → escrow_tx_hash recorded in investments table
Receipt TokenA proof-of-deposit NFT issued to the investor while their investment is being processed and LP tokens haven't arrived yet (ERC-721) minted to investor System
receipt_token_id stored → status = PENDING
⑤ Refund timer starts System
refund_eligible_at = deposit time + 7 days. If LP not issued by then, auto-refund triggers.
⑥ NAV snapshot stored System
tokens_minted = stablecoin_amount / nav_per_token — LP tokens issued at fair current NAV price. nav_at_investment snapshot stored. Model A/B LP issuance follows (steps ⑦–⑩).

Model A Fund-Issued LP

The fund mints and manages its own LP token on its own smart contract. EmergeFi does not control the LP — it only verifies that the investor received LP by reading the fund's contract via RPC.

Joob, Credix, Maple, Centrifuge — any fund with its own LP token contract

⑥ Fund mints LP tokens to investor wallet Fund
Fund handles LP issuance on their own contract, outside of EmergeFi. EmergeFi has no control over this step.
⑦ Operator verifies LP arrived in investor wallet Operator
EmergeFi reads the fund's LP contract via RPC to confirm the investor's balance → lp_verified_at set
⑧ Release escrowed USDC to fund treasury Operator
Only after LP is verified → USDC released from escrow to fund_treasury_addressusdc_release_tx_hash recorded
⑨ Receipt Token → LP_VERIFIED System
Investment fully settled. Investor now holds fund's LP token + receipt token as proof.

Model B EmergeFi-Issued LP

For partners that don't have their own LP token infrastructure. EmergeFi deploys and manages the LP contract on the partner's behalf.

Traditional SPVs, new partners without on-chain presence

⑥ Release escrowed USDC to fund treasury Operator
USDC sent from escrow to fund_treasury_address
⑦ EmergeFi mints LP tokens to investor Operator
Minted on EmergeFi-deployed contract → lp_mint_tx_hash recorded → lp_token_events log (MINT)
⑧ Receipt Token → LP_VERIFIED System

Phase 2 — Yield Distribution

💰 DISTRIBUTING Mechanism
LP tokens priced at $1 fixed. Yield distributed as additional USDC or LP tokens — not through NAV appreciation. Clear, predictable returns for institutional investors.
📐 Yield Calculation
Yield accrues on nominal balanceThe original investment amount before any NAV-adjusted token value is applied (not NAV-adjusted). Auto-settled before any LP transfer. Admin records distributions in database.

Phase 3 — Redemption

🔄 Two-Step Admin Approval Queue
Investor submits a redemption request. NAV is snapshot-locked at request time and never recalculated — protecting against further drops. Requests processed FIFO.
PENDING RECOMMENDED APPROVED PROCESSING COMPLETED

Operator reviews payout + pool liquidity → recommends. Admin gives final approval → USDC payout sent → payout_tx_hash logged.

Writedown & NAV

How NAV reflects real-world asset value changes. Covers writedown mechanics, oracle update flow, timelock enforcement, and portfolio display guidelines. For redemption payout calculations, see the Redemption section.

Dual Loss Protection

🛡 Layer 1: Reserve Fund
reserve_percentage (default 10%) of each pool's deposits. Covers first-loss events before touching investor principal. Configurable per pool.
📈 Layer 2: NAV Token PriceNet Asset Value per token — floats between $0.00 and $1.00 (no floor; PD6 removed). Capped at $1.00 max. New investors always buy at the current fair price.
When losses exceed reserve: nav_per_token drops below $1.00.
nav_per_token = $0.92 → 8% writedown reflected in token price.

Consequences when NAV < $1.00:
• New investments stay open — new investors buy at the lower fair price, getting more tokens
• Existing yield continues on nominal deposit amount
• Redemptions pay tokens × nav_at_request + yield
• NAV decreases require 24-hour timelockA forced waiting period before a NAV decrease takes effect. Increases apply immediately.

NAV Update Timeline

Oracle posts NAV update Oracle
Fund operator reports NAV off-chain → EmergeFi oracle validates → updateNAV() called on EmergeFiPool. Row created in nav_history with status PENDING.
24-hour timelock (decreases only) System
effective_at = now() + 24h. Notification sent to operators + fund issuers. NAV increases apply immediately — no timelock.
NAV takes effect System
pools.nav_per_token updated, nav_history row marked APPLIED. Investment stays open — new investors pay the updated fair price.
Offline: Last NAV maintained until oracle resumes Admin
PD4 decided: No fallback. NAV stays at last known value. Admin can manually pause the pool for serious cases via the admin panel. source = oracle for normal updates.

How Admin Calculates a NAV Writedown

When a loss event occurs (e.g. a borrower defaults), the Oracle posts a new nav_per_token value on-chain via updateNAV(). Here's the step-by-step logic:

📖 Step-by-Step Example
Scenario: Joob EWA Fund pool has $1,000,000 in total deposits. A borrower defaults on a $120,000 loan.

Step 1 — Check the reserve fund first
The pool has a 10% reserve = $1,000,000 × 0.10 = $100,000
The reserve covers $100,000 of the $120,000 loss. Remaining uncovered loss = $20,000

Step 2 — Calculate the new NAV
The uncovered loss reduces nav_per_token. The formula is:
New NAV = Current NAV × (1 − Uncovered Loss ÷ Total Deposits)
New NAV = $1.00 × (1 − $20,000 ÷ $1,000,000) = $1.00 × 0.98 = $0.98

Step 3 — Apply the 24-hour timelock
NAV decreases require a 24-hour waiting period before taking effect. NAV increases (recovery) apply immediately.
✓ Decrease is 2% → submitted as a pending NAV update
✓ 24-hour timelock applies — stakeholders can review or prepare
✓ NAV capped at $1.00 max — cannot increase above initial value

Step 4 — What this means for existing investors
An investor who deposited $10,000 now has:
Tokens still = 10,000 (from original deposit at $1.00 NAV)
Token value = 10,000 × $0.98 = $9,800
They've lost $200 of token value (2%), but their yield still accrues on the full $10,000.

Step 5 — New investor gets fair entry
A new investor depositing $10,000 at $0.98 NAV gets:
tokens_minted = $10,000 ÷ $0.98 = 10,204 tokens → proportionally more tokens than original investors at $1.00. Fair entry.
🔢 Quick Reference Formula
1. Uncovered Loss = Total Loss − Reserve Fund
2. NAV Decrease % = Uncovered Loss ÷ Total Pool Deposits
3. New NAV = Current NAV × (1 − NAV Decrease %)
4. Existing investor value = tokens_minted × New NAV
5. New investor tokens = deposit_amount ÷ New NAV

If Current NAV is already below $1.00 (a previous writedown happened), use the current NAV in step 3. Writedowns are cumulative.

Portfolio NAV Display — Design Spec DECIDED

When a pool's NAV is below $1.00, the portfolio page must show the writedown transparently but without alarming design patterns. Investors are sophisticated — hiding information erodes trust faster than showing it.
✓ DO
Show: "Invested: $1,000 → Current Value: $880 (NAV: $0.88)"
Use neutral colors (grey/blue info card)
Link to NAV history timeline for that pool
Show NAV governance bounds as reassurance
✗ DON'T
Red warning banners or alarm iconography
Words like "loss", "default", "danger"
Hide the writedown behind a click/expand
Show NAV change without context or governance info

→ See Redemption for payout calculations and NAV scenario examples.

Redemption

How investors exit positions. Payout is locked at NAV-at-request time. Two-step admin approval queue — Operator recommends, Admin approves. For how NAV changes are calculated and applied, see Writedown & NAV.

Payout Formula

Token valuetokens_minted × nav_at_request
Accrued yieldnominal × (APY / 365) × days_held
Total payouttoken_value + accrued_yield
Example: Joob EWA Fund, NAV $0.92, 10,000 tokens, 15% APY, 45 days
Total: $9,384.93

Redemption by NAV Scenario

Category A: Standard Entry (invested at $1.00)

A1 — Normal Redemption
NAV at investment: $1.00 · NAV at redemption: $1.00
Deposit $10,000 at NAV $1.00 → 10,000 tokens
Token Value10,000 × $1.00 = $10,000.00
Accrued Yield$10,000 × (15%/365) × 90 days = $369.86
Total Payout$10,369.86
Full principal returned + yield. Simplest case.
⚠️ A2 — Redemption During Writedown
NAV at investment: $1.00 · NAV at redemption: $0.85
Deposit $10,000 at NAV $1.00 → 10,000 tokens
Token Value10,000 × $0.85 = $8,500.00
Accrued Yield$10,000 × (15%/365) × 90 days = $369.86
Total Payout$8,869.86
Investor absorbs 15% NAV loss on token value.
Yield still calculated on nominal $10,000 (not NAV-adjusted).
NAV is locked at request time — if NAV drops further after request, investor keeps $0.85.

Category B: Discounted Entry (invested below $1.00 — "Fair Entry")

📈 B1 — Invested Low, NAV Recovered
NAV at investment: $0.80 · NAV at redemption: $0.95
Deposit $10,000 at NAV $0.80 → 12,500 tokens
Token Value12,500 × $0.95 = $11,875.00
Accrued Yield$10,000 × (15%/365) × 90 days = $369.86
Total Payout$12,244.86
Investor gained from NAV recovery ($0.80 → $0.95).
Got 12,500 tokens instead of 10,000 — the "fair entry" principle with upside.
➡️ B2 — NAV Unchanged, Yield Only
NAV at investment: $0.85 · NAV at redemption: $0.85
Deposit $10,000 at NAV $0.85 → 11,765 tokens
Token Value11,765 × $0.85 = $10,000.25
Accrued Yield$10,000 × (15%/365) × 90 days = $369.86
Total Payout$10,370.11
No NAV movement — investor gets principal back + yield.
Higher token count but token value × NAV = original deposit. Pure yield play.
🔻 B3 — Invested Low, NAV Dropped Further
NAV at investment: $0.85 · NAV at redemption: $0.70
Deposit $10,000 at NAV $0.85 → 11,765 tokens
Token Value11,765 × $0.70 = $8,235.50
Accrued Yield$10,000 × (15%/365) × 90 days = $369.86
Total Payout$8,605.36
Discounted entry does NOT guarantee safety.
NAV dropped further $0.85 → $0.70. Investor loses ~14% on token value.
Entering during a writedown carries risk if the underlying asset continues to deteriorate.
⚡ Early Redemption Penalty (applies to all cases)
If the investor redeems BEFORE the pool's lock-up period ends, an early exit penalty is applied to the accrued yield.
penalty_amount = accrued_yield × early_redemption_penalty_pct
Example applied to Case A2:
Accrued yield: $369.86 · Pool penalty: 50% of accrued yield
Penalty amount: $369.86 × 0.50 = $184.93
Adjusted yield: $369.86 − $184.93 = $184.93

Token value: $8,500.00 (unchanged — penalty only affects yield)
Total payout: $8,500.00 + $184.93 = $8,684.93

Note: Penalty applies to yield only, never to token value / principal.
This is the worst-case scenario: NAV loss + early penalty combined.
BD2 (penalty policy) and BD6 (lock-up structure) are still open decisions.
In all 5 scenarios: Yield always accrues on the nominal deposit amount ($10,000), never on the NAV-adjusted token value. NAV is always locked at the moment of redemption request and cannot be recalculated after submission.

Payout Example

Joob EWA Fund · NAV $0.92 · 10,000 tokens held · 15% APY · 45 days
Token Value 10,000 tokens × $0.92 = $9,200.00
Accrued Yield $10,000 × (0.15÷365) × 45 = $184.93
Total Payout $9,384.93
total = (tokens × nav_at_request) + (nominal × APY/365 × days)

Redemption Flow (Two-Step Admin Queue)

Investor
Operator
Admin
System
Investor requests redemption Investor
Sees confirmation modal: token count, NAV price, token value, accrued yield, total payout + warning: "You will NOT benefit if the NAV recovers after your request is submitted."
NAV snapshot locked → status = PENDING System
nav_at_request captured from current pools.nav_per_token. Payout = tokens_minted × nav_at_request + yield. One active request per investment (no duplicates). FIFOFirst In, First Out — requests are processed in the order they were received; earliest first queue.
Operator reviews → "Recommend Approval" Operator
Verifies payout amount + checks pool liquidity → status = RECOMMENDED. recommended_by recorded.
Admin reviews → "Approve & Process" Admin
Final check → status = APPROVED → PROCESSING. USDC sent from pool → status = COMPLETED. payout_tx_hash logged.
OR: Admin rejects → REJECTED
Rejection reason sent to investor. Investor may request again.

Key Rules

📸 NAV Snapshot
nav_at_request is locked at request time from pools.nav_per_token. If NAV drops further after request, investor keeps the higher NAV. If NAV recovers, payout stays at the lower request-time NAV. Payout = tokens × nav_at_request + yield.
📊 Yield Basis
Accrued yield calculated on nominal balance (not NAV-adjusted token value). Full yield paid to incentivize patience even in writedown pools.
🔒 Early Redemption
If redemption during lockup period → per-pool penalty applies (e.g., 50% of accrued yield forfeited). Configurable per pool.
📋 FIFO Processing
Oldest requests processed first. Admin checks pool liquidity before approval. Insufficient liquidity → request stays in queue (not rejected).

Smart Contracts

Deployed contracts, their roles, and on-chain access control. Oracle architecture is an open product decision.

Deployed Contracts

Contract Standard Purpose
EmergeFiLPToken ERC-20 + AccessControl LP token — whitelist-only transfers, auto-yield settlement before any transfer
EmergeFiEscrow AccessControl + ReentrancyGuard USDC escrow with 7-day auto-refund, reentrancy protection
EmergeFiEscrowFactory Factory Deploys new escrow instances per pool — keeps escrow funds isolated per pool
EmergeFiReceipt ERC-721 Proof of deposit NFT with status tracking. Burned on full settlement
EmergeFiKYCSoulbound Non-transferable ERC-721 On-chain KYC status via SumSub — non-transferable identity credential
EmergeFiPool AccessControl Pool management — deposits, NAV, role-based admin actions
updateNAV() (integrated into EmergeFiPool — PD1 decided) ✅ PD1 updateNAV() + ORACLE_ROLE added directly to EmergeFiPool.sol. No separate contract needed.

Contract Roles

Role Capabilities
DEFAULT_ADMIN_ROLE Grant/revoke all roles, emergency functions, pause contracts
MINTER_ROLE Mint LP tokens (Model B), mint Receipt Tokens
ESCROW_MANAGER_ROLE Release USDC from escrow, process auto-refunds
VERIFIER_ROLE Update LP verification status (Model A)
ORACLE_ROLE Post NAV updates via updateNAV() on EmergeFiPool (PD1/PD2 — decided)

RBACRole-Based Access Control — defines who can do what based on their assigned role & Permissions

Three-tier role hierarchy across platform admin and fund-scoped access.

👑 Admin
Full platform control

Panel: /admin
Scope: All pools, all funds

Exclusive powers:
• Create/delete pools & issuers
• Approve redemptions (final)
• Monitor NAV / Pool Pause
• Pause/unpause pools
• Manage roles (add/remove operators & fund managers)
• Configure notifications & settings
⚙️ Operator
Day-to-day operations

Panel: /admin (limited)
Scope: All pools (restricted actions)

Can do:
• Process investments (verify LP, release USDC, mint LP)
• Record yield distributions
• Edit pool & issuer details
Propose redemptions (not approve)
• View audit log, export CSV
📁 Fund Manager
Read-only, fund-scoped

Panel: /fund-admin
Scope: Own fund(s) only

Can do:
• View fund dashboard & metrics
• View investor list & history
• View redemption requests (own fund)
• View yield records & LP ledger
• Export CSV (own fund data)

Full Permission Matrix

Action 👑 Admin ⚙️ Operator 📁 Fund Mgr
Platform Administration
View admin dashboard
View audit log
Configure notifications
Add/remove Operators
Add/remove Fund Managers
Export CSV (all data)
Pool Management
Create new pool
Edit pool details
Set risk rating (1-5)
Pause / unpause pool
Archive / delete pool
View NAV / Pool Pause
View NAV / Oracle Status ✓ own
Issuer Management
Create new issuer
Edit issuer details
Set issuer status
Add/remove fund manager wallets
Investment Processing
View investment queue
Verify LP (Model A)
Release USDC from escrow
Mint LP tokens (Model B)
Redemption Processing (Two-Step)
View redemption queue Own fund
Propose / recommend redemption
Approve redemption (final)
Process USDC payout
Reject redemption
Fund Admin Panel
View fund dashboard Own fund
View investor list Own fund
Export fund CSV Own fund

Auth & Route Protection

🔐 Authentication
Same wallet-based auth as investors (SIWESign-In With Ethereum — an authentication standard that lets users log in by signing a message with their crypto wallet). Role check on route load via GET /api/auth/role. 30-minute session timeout. No multi-sig for now (single signer + role separation + audit log).

Session response includes wallet_address. OIDC provider cache uses LRU strategy (SHA-678/679 in Linear).

🗄 Database Tables
platform_admins — Admin + Operator roles (wallet, role, is_active)

issuer_admins — Fund Manager roles (wallet, role, issuer_id scope, is_active)

Status State Machines

All status fields and their valid transitions across the system.

Receipt Token Status investments.receipt_status
PENDING MATCHED LP_VERIFIED
PENDING REFUNDED (7-day timeout, no LP issued)

PENDING: USDC in escrow, waiting for LP issuance.
MATCHED: LP confirmed (Model A verified, Model B minted).
LP_VERIFIED: Receipt fully settled, USDC released to fund.
REFUNDED: LP not issued within 7 days, USDC returned.

KYC Status users.kyc_status
PENDING IN_PROGRESS APPROVED
IN_PROGRESS REJECTED

PENDING: User registered, hasn't started KYC.
IN_PROGRESS: SumSub verification underway.
APPROVED: KYC passed, SBT minted on-chain.
REJECTED: Failed verification. User can retry.

Redemption Request Status redemption_requests.status
PENDING RECOMMENDED APPROVED PROCESSING COMPLETED
PENDING or RECOMMENDED REJECTED (+ reason sent to investor)

PENDING: Investor submitted request. NAV snapshot (nav_at_request) locked at current price.
RECOMMENDED: Operator reviewed and recommended approval.
APPROVED: Admin gave final approval.
PROCESSING: USDC transfer initiated.
COMPLETED: USDC received by investor. payout_tx_hash recorded.
REJECTED: Admin denied with reason. Investor may re-request.

Pool Status Flags pools table

Two flags control pool behavior — investment stays open during NAV changes:

is_paused Manual admin toggle. Paused pools hidden from dashboard (unless user has position). No new deposits allowed. The only way to block investment — NAV changes never auto-block.
nav_per_token Decimal (default $1.00, capped at $1.00 max, no floor). Updated by Oracle via updateNAV() with 24h timelock for decreases; increases apply immediately. Drives token minting and redemption payout calculations.
Issuer Status issuers.status
ACTIVE PAUSED OFFBOARDED

ACTIVE: Issuer operational, pools accepting investments.
PAUSED: Temporarily suspended. Existing positions remain. Reversible.
OFFBOARDED: Permanently removed. All pools should be fully redeemed first.

Why issuer-level pause? If an entire fund issuer (e.g., Joob) needs to be suspended for compliance reasons, contract renegotiation, or regulatory hold, pausing at the issuer level cascades to all their pools — rather than pausing each pool individually.

NAV Update Status nav_history.status
PENDING APPLIED

PENDING: NAV decrease submitted. 24h timelock active. Admin can cancel during this window.
APPLIED: Timelock expired, new NAV applied to pool. NAV increases skip PENDING and apply immediately.

Database Schema Overview

Key tables, their relationships, and which module they belong to. Supabase (PostgreSQL) with RLSRow-Level Security — database-level access control that restricts which rows each user can see or modify.

👤 users
id UUID PK
wallet_address VARCHAR
email VARCHAR
kyc_status ENUM
investor_type ENUM
sbt_token_id VARCHAR
acquisition_channel ENUM
🏢 issuers
id UUID PK
name, slug VARCHAR
lp_issuance_model ENUM
status ENUM
treasury_wallet VARCHAR
website_url VARCHAR
api_config JSONB
🏊 pools
id UUID PK
issuer_id FK → issuers
lp_issuance_model ENUM
escrow_contract VARCHAR
nav_per_token DECIMAL (max $1.00, no floor)
has_pending_nav_update BOOL
is_paused BOOL
risk_rating INT 1-5
target_apy DECIMAL
yield_mechanism ENUM
total_deposits DECIMAL (synced via DB trigger)
maturity_date TIMESTAMPTZ
reserve_percentage DECIMAL
lockup_days INT
early_redemption_penalty_pct DECIMAL
💼 investments
id UUID PK
user_id FK → users
pool_id FK → pools
amount DECIMAL (nominal deposit)
tokens_minted DECIMAL
nav_at_investment DECIMAL
receipt_token_id VARCHAR
receipt_status ENUM
refund_eligible_at TIMESTAMPTZ
escrow_tx_hash VARCHAR
unclaimed_yield DECIMAL
lp_mint_tx_hash VARCHAR
usdc_release_tx_hash VARCHAR
lp_verified_at TIMESTAMPTZ
🔄 redemption_requests
id UUID PK
investment_id FK → investments
pool_id FK → pools
nominal_amount DECIMAL (original deposit)
nav_at_request DECIMAL (snapshot)
token_value DECIMAL (tokens × nav)
accrued_yield DECIMAL
total_payout DECIMAL
status ENUM
recommended_by FK → platform_admins
approved_by FK → platform_admins
payout_tx_hash VARCHAR
early_penalty_amount DECIMAL
📜 lp_token_events
id UUID PK
pool_id FK → pools
investor_wallet VARCHAR
event_type ENUM
amount DECIMAL
tx_hash VARCHAR
👑 platform_admins
id UUID PK
wallet_address VARCHAR
role ADMIN | OPERATOR
display_name TEXT
is_active BOOL
📁 issuer_admins
id UUID PK
issuer_id FK → issuers
wallet_address VARCHAR
role FUND_MANAGER
is_active BOOL
📈 nav_history
id UUID PK
pool_id FK → pools
old_nav DECIMAL
new_nav DECIMAL
effective_at TIMESTAMPTZ
status ENUM (pending/applied)
reason TEXT
source ENUM (oracle | admin_override)
💰 yield_distributions
id UUID PK
pool_id FK → pools
distribution_type ENUM (USDC | LP_TOKEN)
amount DECIMAL
apy_rate DECIMAL
period_start TIMESTAMPTZ
period_end TIMESTAMPTZ
created_by FK → platform_admins
📖 DATA TYPES REFERENCE
PK Primary Key
Unique ID for each row — no duplicates allowed
e.g. id in every table
FK Foreign Key
Links to another table's PK
e.g. pool_id → pools.id
UUID Unique Identifier
Random 128-bit ID, globally unique
e.g. 550e8400-e29b-41d4...
ENUM Fixed Options
Only allows predefined values
e.g. PENDING | APPROVED | REJECTED
VARCHAR Text String
Variable-length text field
e.g. "0x1a2B...cD3e"
DECIMAL Precise Number
Exact numeric — used for money amounts
e.g. 10000.00 (USDC amount)
BOOL True / False
Yes or No flag
e.g. is_paused = true
JSONB Structured Data
Flexible key-value storage (binary JSON)
e.g. {"api_key": "...", "url": "..."}
TIMESTAMPTZ Date + Time
Full timestamp with timezone
e.g. 2025-02-11 14:30 KST
TEXT Long Text
Unlimited length text (notes, reasons)
e.g. "Pool default due to..."
INT Whole Number
Integer — no decimals
e.g. risk_rating = 3

Relationship Map

users ←1:N→ investments ←N:1→ pools ←N:1→ issuers
investments ←1:N→ redemption_requests
pools ←1:N→ lp_token_events
pools ←1:N→ nav_history
issuers ←1:N→ issuer_admins (Fund Manager scope)
platform_admins — standalone (Admin + Operator roles)

Migrations

001
Base schema
users, pools, investments, RLS
002
Yield & marketing
OTC, campaigns, yield types
003
Dual LP & admin
escrow, receipt tokens, NAV
004
NAV migration
nav_per_token, nav_history, timelock

Triggers, Auto-Rules & Business Logic

Automated database triggers, business rules, and system behaviors that run without manual intervention.

Database Triggers

🔓
No Safety Bounds — NAV Auto-Adjusts
PD6 (NAV Safety Bounds) removed. No per-transaction limit, no floor. NAV auto-adjusts on default events — the market price reflects the actual asset value. Only constraint: max $1.00 cap. Investment stays open at all NAV levels — new investors pay fair current price.
Refund Timer (7-Day Auto-Refund)
On investment creation, refund_eligible_at = now() + 7 days. If receipt_status is still PENDING at that time, system auto-refunds USDC from escrow to investor. Receipt Token → REFUNDED.
🔒
NAV Decrease Timelock (24 Hours)
When Oracle posts a NAV decrease, it's logged in nav_history with effective_at = now() + 24h and status PENDING. The actual pools.nav_per_token only updates after the timelock expires. NAV increases (recovery) bypass the timelock and apply immediately. Gives stakeholders time to react before a lower price takes effect.
🔗
LP Transfer Auto-Settlement
Before any LP token transfer (Model B), unclaimed yield is auto-settled to the sender. Prevents yield loss on token movement.

Business Rules

🚫
Investment Pre-Checks
Before allowing deposit: kyc_status = APPROVED AND is_paused = false. Investment stays open regardless of NAV level — new investors always pay the current fair price. There is no investment_blocked auto-trigger — the NAV model never auto-blocks investment.
1️⃣
One Active Redemption Per Investment
System enforces unique constraint: only one non-terminal (not COMPLETED/REJECTED) redemption request per investment_id. Investor must wait for resolution before requesting again.
📸
NAV Snapshot on Redemption Request
nav_at_request is captured from pools.nav_per_token at request creation and never recalculated. Payout = tokens_minted × nav_at_request + accrued_yield. Protects investor from further NAV drops during processing. Also means investor doesn't benefit from NAV recovery after request.
📐
Yield on Nominal Balance
Accrued yield always calculated on nominal (original) investment amount), not NAV-adjusted token value. Formula: nominal_amount × (APY / 365) × days_held. This incentivizes investors to stay through NAV dips — they still earn full yield on their original deposit.
📋
FIFO Redemption Queue
Redemption requests processed oldest-first. Admin checks pool liquidity before approving. If insufficient liquidity, request stays in queue (not rejected). Admin can skip FIFO only if rejecting with reason.
⚖️
Early Redemption Penalty
If investor redeems during lockup period: per-pool configurable penalty applies (e.g., 50% of accrued yield forfeited). Penalty amount determined at pool creation by Admin.

Pool Visibility Rules

🏠 Dashboard (Public)
Shows only pools where is_paused = false. NAV below par does not hide a pool — investment stays open and price reflects fair value. Exception: user has an active position → pool always visible regardless of status.
📂 Portfolio (Investor)
Shows ALL user investments regardless of pool status. Paused, writedown, even offboarded pools still display with appropriate status badges.
⚙️ Admin Panel
Admin/Operator see all pools (including paused). Fund Manager only sees pools under their assigned issuer(s). Oracle health status visible to Admin + Operator via /admin/oracle.

Decisions

Open items for business and product alignment. Click any card to see the full options panel.

🟡 6 Business Decisions — Open
🟠 5 Product Decisions — Open
✅ 0 Resolved
🟡 Business Decisions

These shape the product's financial and operational model. Each has a recommendation but is open for discussion.

BD1 Open
Yield Distribution Model
How should yield be delivered to investors?
Distributing — yield paid separately as USDC, LP price stays fixed at $1.
Blocks: BD5 Ref: Lifecycle, Triggers
BD2 Open
Early Redemption Policy
What happens when an investor exits before the lock-up ends?
Per-pool configurable penalty — e.g. 50% of accrued yield.
Blocks: BD6 Ref: Redemption
BD3 Open
Token Issuance / Loss Model
How should the platform reflect asset losses to investors?
NAV Token Pricing — price floats $0.00–$1.00 (no floor), no blocking.
Blocks: BD4, PD1–PD5 Ref: Writedown, Redemption, Schema
BD4 Open
Reserve Fund Mechanics
How should the first-loss reserve be structured?
10% first-loss reserve per pool, configurable at creation.
Depends on: BD3 Ref: Core Concepts
BD5 Open
Reinvestment Mechanism
What happens when an investment matures?
Auto-roll with opt-out window before maturity.
Depends on: BD1 Ref: Lifecycle
BD6 Open
Lock-Up Period Structure
How should investment lock-up periods work?
Per-pool configurable — matches asset liquidity profile.
Depends on: BD2 Ref: Redemption, Triggers
🟠 Product Decisions

Technical architecture choices. Some are blocked until a related Business Decision is resolved.

PD5 Open
NAV Timelock Duration
How long should NAV decreases be delayed before taking effect?
24 hours for decreases, immediate for increases.
Depends on: BD3 Ref: Writedown & NAV
✅ Resolved Decisions 4 resolved
PD1Decided
NAV Oracle Contract
→ Integrated into EmergeFiPool Feb 20, 2026
PD2Decided
Oracle Role Mechanism
→ ORACLE_ROLE on EmergeFiPool Feb 20, 2026
PD3Decided
Oracle Operator
→ EmergeFi-operated Feb 20, 2026
PD4Decided
Oracle Offline Fallback
→ No fallback — Admin pool pause for serious cases. Feb 20, 2026

API Reference

All backend API endpoints grouped by domain. Status: Built   Planned   NAV-dependent endpoints. 15 built, 10+ planned.

Investments

POST/api/investments/initiate
Create investment record, mint receipt token, lock USDC in escrow
GET/api/investments/{id}
Get investment details including LP status and receipt token
GET/api/investments?userId={id}
List all investments for a user
PATCH/api/investments/{id}/verify-lp
Admin: mark LP as verified, release USDC from escrow (Model A)

Pools

GET/api/pools
List all active pools (with status, NAV, APY, capacity)
GET/api/pools/{id}
Get pool details including NAV history
POST/api/admin/pools
Admin: create new pool with config (APY, lock-up, reserve %, etc.)
PATCH/api/admin/pools/{id}/nav
Admin: update NAV (triggers 24h timelock if decrease). Planned.

Redemptions

POST/api/redemptions
Investor: submit redemption request. Locks NAV snapshot.
GET/api/redemptions?investmentId={id}
List redemption requests for an investment
PATCH/api/redemptions/{id}/recommend
Operator: recommend approval (checks liquidity)
PATCH/api/redemptions/{id}/approve
Admin: final approval + USDC payout

Auth & KYC

POST/api/auth/siwe
Sign-In With Ethereum — returns JWT on valid signature
GET/api/kyc/status
Check KYC status for current wallet address
POST/api/kyc/hook
SumSub webhook — updates kyc_status and mints SBT on approval
GET/api/auth/role
Role check on route load — returns user’s platform role (Admin/Operator/Fund Manager)

Yield

POST/api/admin/pools/{id}/yield
Operator: record yield distribution for a pool. Planned — depends on BD1.
GET/api/pools/{id}/yield-history
Get yield distribution history for a pool. Planned — depends on BD1.

Issuers (Admin)

POST/api/admin/issuers
Admin: create new issuer. Planned.
PATCH/api/admin/issuers/{id}
Admin/Operator: edit issuer details. Planned.
GET/api/admin/issuers
Admin: list all issuers. Planned.

NAV & Oracle

GET/api/pools/{id}/nav-history
Investor: NAV change history for a pool. Planned.
GET/api/admin/oracle
Admin: oracle health status and last update times. Planned.

Admin Management

POST/api/admin/roles
Admin: assign role (Operator or Fund Manager) to a wallet. Planned.
DELETE/api/admin/roles/{id}
Admin: remove role assignment. Planned.

Product Timeline

Development phases and status. Based on product spec as of Feb 2026.

✓ Done
Core smart contracts (6 contracts deployed on Base Sepolia)
SIWE authentication + JWT
SumSub KYC integration + SBT minting
Pool creation & management (admin)
Investment initiation + escrow
Investor portfolio view
Redemption request flow
Two-step admin approval queue
RBAC (Admin / Operator / Fund Manager)
▶ In Progress
NAV update flow (admin panel)
24h timelock enforcement
Portfolio NAV display (investor UI)
Writedown UX (tone + transparency)
Admin panel V2 cleanup
○ Planned
DocuSign term sheet integration
NAV oracle contract
Email notification system
Reserve fund mechanics
KAIA mainnet dual-deploy
Multi-sig admin controls
❓ Open / TBD
Secondary market / LP transfers
Multi-asset support (USDT?) — decision removed Feb 19
Investor onboarding (KYB flow)
Analytics dashboard
Mobile app

Changelog

Architecture decisions and doc updates over time. Most recent first.

📅 February 2026 5 updates
Feb 20, 2026 — PD1–PD4 decided + Gap report fixes (v6)
Oracle decisions finalized: PD1 integrated into EmergeFiPool, PD2 ORACLE_ROLE, PD3 EmergeFi-operated, PD4 no fallback (admin pool pause for emergencies). Added ORACLE_ROLE to contracts. 13 missing DB fields added. yield_distributions placeholder table added. NAV Update status machine added. 10+ planned API endpoints. Removed NAV Emergency Override. Phase 3 Redemption added to Investment Lifecycle.
🔄
Feb 19, 2026 — Product spec sync + safety bounds simplified
PD6 (NAV Safety Bounds) removed — no floor, no per-tx limit, NAV capped at $1.0 max only. PD1 recommendation updated: Integrated into EmergeFiPool is now recommended over separate oracle contract. Tech stack verified from GitHub repo. API endpoints: 15 built, rest planned.
🔄
Feb 19, 2026 — Decisions System Overhaul (Batch 5)
Upgraded DECISIONS data model to BD1–BD6 + PD1–PD5 with dependsOn, blocks, referencedIn schema. Merged Business Decisions + Product Decisions into unified Decisions page. Side panel now shows dependency graph.
🗂️
Feb 11, 2026 — Docs Site Restructure (Batch 4)
Converted horizontal tab navigation to sidebar navigation docs site. Added: Overview & Architecture, Core Concepts, Smart Contracts, API Reference, Decisions, Timeline, and Changelog sections. Split Writedown & Redemption into two separate sections. Added decision badge system with slide-out panels.
📐
Feb 15, 2026 — NAV Model Migration (v5)
Replaced Principal Factor (PF) multiplier with NAV (nav_per_token) as the single loss indicator. Investment is no longer auto-blocked when NAV < $1.00. New investors pay fair current price. Writedown formula updated throughout. Removed investment_blocked from pools entity.
📅 January 2026 2 updates
👥
Jan 2026 — RBAC Update: Fund Manager Role Added (v4)
Added Fund Manager as a third role (read-only, pool-scoped). Clarified Admin vs Operator split. Updated permission matrix. — Batch 1
Jan 2026 — Two-Step Redemption Queue Confirmed (v3)
Redemption approval changed from single-admin to Operator-recommends + Admin-approves. FIFO queue enforced. NAV snapshot locked at request time.
📅 December 2025 1 update
🚀
Dec 2025 — Initial Backend Logic Map Published (v1)
First version with Investment Lifecycle, Status Machines, Database Schema, and Business Decisions. Deployed to Vercel via GitHub integration.