Overview & Architecture
🚧 Largely Built — Deploy/QA Pending
This page describes the v3.0 architecture. Contracts, DB schema, backend Lambdas, and both frontends are largely built; remaining work is deploy/QA (see Migration Status). v2.x architecture (AS_POOL/FUND_POOL binary model) is deprecated.
Aset is an institutional-grade RWA tokenization platform. Investors deposit stablecoins into pools backed by real-world assets (invoice financing, earned wage access, private credit) and earn yield through LP tokens.
🏦 Non-Custodial Architecture
All investor funds flow through Pool Smart Contracts. Aset operates as a facilitator with an operator role on contracts — it never holds investor funds directly. This means no VASP license is required under MiCA (€150K capital threshold avoided). Industry standard: Ondo, Centrifuge, Maple all use this model.
v3.0 Model — Config-Driven Pools
In v2.x, pools came in two binary types: AS_POOL (Aset-managed) and FUND_POOL (FM-managed). As we onboard more partners (Joob, LINE BK, future originators) with different operational models, we moved to a dimension-based configuration: each pool is defined by ~24 independent settings — not by a fixed type.
Key principles:
- Non-custodial: Aset never holds funds, only the contract code can move them
- Aset always mints LP: One LP token contract is the single source of truth for investor positions across all pools (partners notified via API)
- Reserve and Tranche are separate: Reserve (liquidity buffer) and Junior tranche (loss absorber) serve different purposes. The reserve is not a loss layer — first-loss is the manager equity buffer for standalone pools, Junior for grouped ones (R8)
See Pool Models for the full dimension list.
v3.0 Highlights — What Changed from v2.x
📋 Quick reference for v3.0 design decisions
All 10 decisions are recorded in Decisions → v3.0 Decisions. Summary below.
| # | Change | Rationale | Detail |
|---|---|---|---|
| v3-01 | pool_type binary → ~24 orthogonal dimensions | Joob/LINE BK onboarding exposed binary as too rigid | Pool Models |
| v3-02 | LP always platform-issued (FUND_ISSUED deprecated) | Single source of truth for positions across all pools | Core Concepts |
| v3-03 | PlatformReceiptNFT removed; deposits atomic | LP itself is proof of deposit; no D+7 refund window | Smart Contracts |
| v3-04 | Redemption: PENDING_RESERVE replaces FM_ACCEPTED | Partner action is state, not workflow step | Status Machines |
| v3-05 | Two-level pause: is_paused (soft) + is_emergency_frozen (hard) | Soft pause blocks deposits only; freeze halts everything | Pool Models → Operations |
| v3-06 | Emergency wind-down (60+30 day timelock) | Recovery path for unresponsive partners. Only what the Pool contract holds is recoverable on-chain (typically the ~10% reserve) | Pool Models → Wind-Down |
| v3-07 | collateral_type enum → collateral_description text + collateral_ratio | Real collateral arrangements don't fit 3 buckets | Core Concepts |
| v3-08 | nav_data_source dimension | Superseded by v3-15 — single processing path, dimension removed | [v3-15 below] |
| v3-09 | tranche_structure dimension | Superseded by v3-14 — tranching modeled via linked SINGLE pools, not two-LP-per-pool | [v3-14 below] |
| v3-10 | Per-pool KYC: kyc_level_required + jurisdiction_whitelist | KYC/KYB requirements differ per pool (institutional, region-restricted) | KYC & Identity |
| v3-11 | PlatformEscrow absorbed into PlatformPool (5 → 4 contracts) | 10/90 split inlined; no separation-of-concerns benefit with atomic deposit | Smart Contracts |
| v3-12 | Wind-down unified with NAV mechanism; IMPAIRED state added | Math equivalent; Maple-pattern intermediate state lets us pause deposits without committing to terminal wind-down | Status Machines, Writedown & NAV |
| v3-13 | Automated NAV writedown via Joob DPD API (OJK schedule) | Joob already exposes per-loan DPD; OJK 5/15/50/100% schedule applied per-loan, aggregated to NAV | Writedown & NAV, Joob Pool Config |
| v3-14 | Tranche via linked SINGLE pools (supersedes v3-09) — tranche_group_id + tranche_role columns; Aset Lambda applies waterfall | Single-pool-two-LP pattern adds contract complexity without buying enforcement (Aset Lambda controls NAV anyway under v3-15). Two-pool pattern simpler, supports 3-tier (Senior/Mezz/Junior) | Pool Models → Tranche Group |
| v3-15 | Drop PARTNER_REPORTED (supersedes v3-08) — Aset always processes NAV | Required to enforce tranche waterfall (v3-14) and write-off schedule (v3-13). Pure mirroring would break those guarantees | Writedown & NAV |
| v3-16 | Reserve consumption integrated into updateNAV(newNav, reserveConsumed) — atomic | Single Lambda call; race-free; cleaner audit trail vs. separate consumeReserveForLoss(). ⚠️ The "loss absorption" half is retired by R8 — the reserve is redemption liquidity, and reserveConsumed is now always 0. The atomicity rationale and the ABI stand | Smart Contracts, Writedown & NAV |
| v3-17 | Cross-VASP KYC: SumSub Reusable KYC if partner uses SumSub, else fast re-KYC with returning-user flow | Tokocrypto-class secondary market partnerships need low-friction onboarding without sacrificing regulatory compliance | KYC & Identity |
| v3-18 | Minimum on-chain config + KYC compliance enforced on-chain | Permissioned platform — investors trust Aset operationally. Only KYC level/jurisdiction must be on-chain (regulatory hard line). All other config in DB | Smart Contracts |
🔄 Phase 2.5 — Universal Holder Verification (5-State Model)
On top of v3.0, the 5-State Holder Model (A: Verified Investor, B: Verified No-History, C: Unverified Holder, D: Identity Only, E: Unregistered) unifies read-only / actual-investment / secondary-market scenarios into one state machine. Investors transition between states via SBT minting, RPS transfers, or platform deposits. → Holder Verification
Also unified in v3.0
- Yield model: All pools use claim-based, manual-trigger (
yield_trigger = MANUAL) — auto-distribution removed - Escrow: Always
SMART_CONTRACT(legacyMULTI_SIG/NO_ESCROWremoved) - Per-pool reserve:
reserve_bps(default 1000 bps = 10%) — distinct from Junior tranche - Risk transparency:
risk_tierremoved; replaced by data points (collateral_*,apy_disclosure,tranche_structure, etc.)
Tech Stack
| Layer | Stack | Details |
|---|---|---|
| 🖥️ 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 · 4 contracts (PlatformReceiptNFT + PlatformEscrow removed) · Multi-chain |
| 🗄️ Database | Supabase (PostgreSQL + RLS) | Hosted · Row-level security · supabase-js |
| 🔗 Chains | Ethereum + Base + Kaia | SIWE auth · wagmi + viem |
| 👤 Auth (Investor) | SIWE (Sign-In with Ethereum) | JWT via jose · RainbowKit · 30-min timeout |
| 🔑 Auth (Admin) | Google OAuth + Multi-sig | admin_users.email from Google · multi-sig wallet for critical admin actions |
Chain Deployment
v3.0 uses multi-chain deployment to support partners on different networks.
| Environment | Chains | Note |
|---|---|---|
| 🧪 Dev | Base Sepolia, Ethereum Sepolia | Testnets for development and staging |
| 🌐 Prod | Ethereum Mainnet (Joob partner), Base Mainnet (default), Kaia (existing Joob holdings) | Per-pool chain_id configuration |
Each pool is deployed on the chain specified by its chain_id dimension. Stablecoin addresses are resolved per chain via STABLECOIN_ADDRESSES[chainId][symbol].
Smart Contract Registry (v3.0)
4 active contracts (down from 6 in v2.x — Receipt NFT removed v3-03, Escrow absorbed v3-11).
| Contract | Standard | Purpose |
|---|---|---|
| PlatformPool | AccessControl + ReentrancyGuard | Core contract — deposits, reserve management, LP mint, yield distribution, redemption, NAV updates, wind-down. Same flow for all pools (config-driven). |
| PlatformLPToken | ERC-20 + AccessControl | LP token — permissionless secondary transfers (pause-gated only; verification enforced at deposit/redemption, not on transfer — v3-58), auto yield settlement. One per pool always (v3-14: tranched products use multiple linked pools, each with their own LP, not two-LP-per-pool). |
| PlatformPoolFactory | Factory + Registry | Unified factory + on-chain registry. createPool(config) deploys Pool + LPToken(s). Single poolCounter. Validates dimensions at deploy. |
| PlatformKYCSoulbound | Non-transferable ERC-721 | On-chain KYC/KYB status via SumSub. Global contract (not per-pool). Supports INDIVIDUAL/INSTITUTION levels, US person attestation, expiry, revocation. |
| — | Removed in v3.0 (v3-11). Absorbed into PlatformPool — USDC held directly by the Pool; the 10/90 reserve split is inlined into the atomic deposit. | |
| — | Removed in v3.0. LP token itself is proof of deposit. |
See Smart Contracts for detailed function lists, roles, and architecture diagrams.
Migration Status
v3.0 design is complete; implementation is largely built. Remaining work is primarily deploy + QA, not spec.
| Component | Status |
|---|---|
| Spec (docs site) | ✅ Current (verified 2026-08-06) |
| DB schema (new dim columns) | ✅ Built — through migration 0126 (applied to dev; prod deploy pending). Counts and per-migration detail: 11-db-schema |
| Smart contracts (PlatformReceiptNFT removal, new functions) | ✅ Built — matches this spec |
| Backend Lambdas (dimension-based dispatching) | ✅ Built — ~128 handlers / 129 routes / 15 schedulers |
| Frontend (pool creation UI uses dimensions) | ✅ Built — investor + admin apps (a few admin create-form inputs still API-only, QA pending) |
Known deferred: automated DPD→OJK NAV writedown (v3-13) — see Writedown & NAV and Joob Pool Config.
Track implementation in the Phase 1.5 plan (internal).