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
Chain Deployment
Testnet for all development and staging
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.
tokens_minted = deposit / nav_per_token.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.
updateNAV() on EmergeFiPool → NAV decreases enter 24h timelock,
increases apply immediately. No safety bounds (PD6 removed).
timelock. NAV increases: immediate.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.
nominal_amount × (APY / 365) × days_held. NAV fluctuation does not affect yield calculation.
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.MINTED → LP_PENDING → LP_VERIFIED or → REFUNDED. Not the LP token — just a
receipt. Burned when investment fully settles.
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.
Phase 1 — Deposit (Both Models)
kyc_status = APPROVED
is_paused = false,
kyc_status = APPROVED. NAV model keeps investment open during writedowns — no auto-block.
escrow_tx_hash recorded in investments
tablereceipt_token_id stored → status = PENDINGrefund_eligible_at = deposit time + 7 days. If LP not issued by then,
auto-refund triggers.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
lp_verified_at setfund_treasury_address → usdc_release_tx_hash recorded
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
fund_treasury_addresslp_mint_tx_hash recorded →
lp_token_events log (MINT)
Phase 2 — Yield Distribution
$1 fixed. Yield distributed as additional USDC or LP tokens — not through
NAV appreciation. Clear, predictable returns for institutional investors.
Phase 3 — Redemption
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
reserve_percentage (default 10%) of each pool's deposits. Covers first-loss events before
touching investor principal. Configurable per pool.
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
updateNAV() called on EmergeFiPool. Row created in nav_history with
status PENDING.
effective_at = now() + 24h. Notification sent to operators + fund
issuers. NAV increases apply immediately — no timelock.pools.nav_per_token updated, nav_history row marked
APPLIED. Investment stays open — new investors pay the updated fair price.
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 1 — Check the reserve fund first
The pool has a 10% reserve =
$1,000,000 × 0.10 = $100,000The 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 = $1.00 × (1 − $20,000 ÷ $1,000,000) =
$1.00 × 0.98
= $0.98Step 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,800They'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.
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
Use neutral colors (grey/blue info card)
Link to NAV history timeline for that pool
Show NAV governance bounds as reassurance
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
tokens_minted × nav_at_requestnominal × (APY / 365) × days_heldtoken_value + accrued_yield
Total: $9,384.93
Redemption by NAV Scenario
Category A: Standard Entry (invested at $1.00)
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")
Got 12,500 tokens instead of 10,000 — the "fair entry" principle with upside.
Higher token count but token value × NAV = original deposit. Pure yield play.
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.
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.
Payout Example
Redemption Flow (Two-Step Admin Queue)
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.
recommended_by recorded.
payout_tx_hash logged.Key Rules
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.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.
Panel:
/adminScope: 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
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
Panel:
/fund-adminScope: 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
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).
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.
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.
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.
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.
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.
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.
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.
Relationship Map
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
users, pools, investments, RLS
OTC, campaigns, yield types
escrow, receipt tokens, NAV
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
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_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.
Business Rules
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.
investment_id. Investor must wait for resolution before requesting again.
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.
nominal_amount × (APY / 365) × days_held. This
incentivizes investors to stay through NAV dips — they still earn full yield on their original deposit.
Pool Visibility Rules
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.
/admin/oracle.
Decisions
Open items for business and product alignment. Click any card to see the full options panel.
These shape the product's financial and operational model. Each has a recommendation but is open for discussion.
Technical architecture choices. Some are blocked until a related Business Decision is resolved.
API Reference
All backend API endpoints grouped by domain. Status: ● Built ● Planned NAV-dependent endpoints. 15 built, 10+ planned.
Investments
/api/investments/initiate
/api/investments/{id}
/api/investments?userId={id}
/api/investments/{id}/verify-lp
Pools
/api/pools
/api/pools/{id}
/api/admin/pools
/api/admin/pools/{id}/nav
Redemptions
/api/redemptions
/api/redemptions?investmentId={id}
/api/redemptions/{id}/recommend
/api/redemptions/{id}/approve
Auth & KYC
/api/auth/siwe
/api/kyc/status
/api/kyc/hook
/api/auth/role
Yield
/api/admin/pools/{id}/yield
/api/pools/{id}/yield-history
Issuers (Admin)
/api/admin/issuers
/api/admin/issuers/{id}
/api/admin/issuers
NAV & Oracle
/api/pools/{id}/nav-history
/api/admin/oracle
Admin Management
/api/admin/roles
/api/admin/roles/{id}
Product Timeline
Development phases and status. Based on product spec as of Feb 2026.
Changelog
Architecture decisions and doc updates over time. Most recent first.