Cross-Portal Workflows
Deep technical reference for every end-to-end flow: API calls, DB operations, on-chain transactions, and service-to-service sequences.
The complete journey from business registration to secondary market trading. This is the most complex workflow spanning Issuer, Admin, and Investor portals, the platform-api, blockchain-svc, and the Polygon chain.
STATUS FLOW
draft → primary → secondary ↔ paused → matured | delisted
Issuer platform-api:8081 blockchain-svc:8082 Polygon (ERC-3643)
│ │ │ │
│─POST /tokens──────▶│ │ │
│ │ INSERT tokens │ │
│ │ (status=draft) │ │
│◀───── 201 ─────────│ │ │
│ │ │ │
Admin │ │ │
│─POST /tokens/:id │ │ │
│ /deploy──────────▶│ │ │
│ │─POST /deploy───────────▶│ │
│ │ │─── Deploy IR ─────────▶│ TX1
│ │ │─── Deploy Compliance ─▶│ TX2
│ │ │─── Deploy Token ──────▶│ TX3
│ │ │─── addAgent(IR) ──────▶│ TX4
│ │ │─── addAgent(Token) ───▶│ TX5
│ │ │◀── DeployResult ───────│
│ │◀─── addresses + hashes─│ │
│ │ UPDATE tokens │ │
│ │ (contract_address, │ │
│ │ identity_registry_addr,│ │
│ │ status=primary) │ │
│◀─── 200 ───────────│ │ │Issuer registers business
POST /api/v1/businesses → INSERT INTO businesses (status='pending'). Requires KYC level 2. Includes company name, registration number, country, industry, alef_allocation_pct.
Admin approves business
PATCH /api/v1/admin/businesses/:id → UPDATE businesses SET status='active'. Only after approval can the issuer create tokens.
Issuer creates token (draft)
POST /api/v1/tokens → INSERT INTO tokens (status='draft'). Validates primary_offering_pct + team_pct + reserve_pct = 100. Sets price_per_token, total_supply, min_investment, raise_currency.
Admin deploys to blockchain (5 on-chain txns)
POST /api/v1/admin/tokens/:id/deploy → platform-api calls blockchain-svc POST /api/v1/deploy. Deploys IdentityRegistry, Compliance(IR), SecurityToken(name, symbol, decimals, IR, Compliance), then addAgent×2. Gas limit: 3M per deploy, 300K per call. All gas recorded in gas_transactions.
Admin starts primary offering
POST /api/v1/admin/tokens/:id/start-offering → UPDATE tokens SET status='primary', primary_start, primary_end. Token enters subscription mode.
Investors subscribe
POST /api/v1/tokens/:id/subscribe → SELECT users FOR UPDATE (check kyc_status='approved'), deduct fiat_balance, INSERT token_allocations (allocation_type='primary', status='pending_payment'), INSERT wallet_transactions (type='token_buy', status='pending').
Admin confirms allocations
POST /api/v1/admin/tokens/:id/confirm-payment → UPDATE token_allocations SET status='confirmed', increment tokens_sold, total_raised. UPSERT wallets (balance += qty). Update wallet_transaction to 'completed'. Async: call ExecuteOnChainAllocation.
On-chain allocation (async)
ExecuteOnChainAllocation: registerIdentity(investor, countryCode) on IdentityRegistry (idempotent), then mint(investor, amount*10^decimals) on SecurityToken. On success: on_chain_status='minted', on_chain_tx_hash set. On failure: on_chain_status='failed', on_chain_error set.
Admin enables secondary trading
POST /api/v1/admin/tokens/:id/enable-secondary → UPDATE tokens SET status='secondary'. Triggers ProcessGoLiveAllocation (treasury gets alef_allocation_pct tokens) and ProcessSuccessFee. Full orderbook trading begins.
Key API Endpoints
Database Tables
On-Chain Operations
Deploys identity registry for this token
Deploys compliance module linked to IR
Deploys the ERC-3643 security token
Grants deployer agent role on IR
Grants deployer agent role on token
Registers investor identity (idempotent)
Mints tokens to investor wallet
Manual KYC review flow (MVP). Production will integrate Sumsub for automated ID verification.
STATUS FLOW
none → pending → in_review → approved | rejected → (resubmit) → pending
Investor platform-api:8081 Admin blockchain-svc:8082 Polygon
│ │ │ │ │
│─POST /kyc────────▶│ │ │ │
│ │ INSERT kyc_applications │ │
│ │ UPDATE users.kyc_status='pending' │ │
│◀── 201 ───────────│ │ │ │
│ │ │ │ │
│ │◀─POST /kyc/:id/review│ │ │
│ │ {decision: approve} │ │ │
│ │ UPDATE kyc_applications.status='approved' │ │
│ │ UPDATE users (kyc_status, kyc_level, │ │
│ │ accreditation_status, fatca_status) │ │
│ │──── 200 ─────────────▶│ │ │
│ │ │ │ │
│ │ (later, on token allocation confirm) │ │
│ │─POST /register-identity──────────────────▶│ │
│ │ │ │─registerIdentity▶│
│ │ │ │◀──tx_hash────────│User submits KYC application
POST /api/v1/kyc → Duplicate check (no pending/in_review apps). INSERT INTO kyc_applications (verification_provider='manual'). UPDATE users SET kyc_status='pending'. Valid doc types: passport, emirates_id, national_id, drivers_license.
Application enters admin queue
Appears in Admin/Staff → KYC Management. Sorted by submission date. Notification sent to KYC reviewers.
Reviewer examines application
GET /api/v1/admin/kyc/:id → Full applicant details with PII. Status auto-updates to in_review on first access.
Approve or reject
POST /api/v1/admin/kyc/:id/review → SELECT FOR UPDATE on kyc_applications. On approve: kyc_level set (default 2), accreditation auto-derived (level>=3→institutional, >=2→professional, else retail), FATCA auto-set (US nationality→blocked, else clear). On reject: rejection_reason stored, user.kyc_status='rejected'.
Audit log created
INSERT INTO audit_logs with reviewer ID, timestamp, action type, and decision details.
Key API Endpoints
UPDATE kyc_applications SET status='approved', reviewed_by=$1,
reviewed_at=NOW(), review_notes=$2 WHERE id=$3;
UPDATE users SET kyc_status='approved', kyc_level=$1,
accreditation_status=$2, fatca_status=$3 WHERE id=$4;
-- accreditation: level>=3→'institutional', >=2→'professional', else 'retail'
-- fatca: nationality='US'→'blocked', else 'clear'How an order moves from the investor's screen through the matching engine, anti-manipulation checks, balance locking, trade creation, and WebSocket broadcast.
ORDER STATUS FLOW
open → partially_filled → filled | cancelled
pending_stop → open (when stop price triggered) → filled | cancelled
Investor trading-server:8080 Matching Engine DB (PostgreSQL) WebSocket Hub
│ │ │ │ │
│─POST /orders───────▶│ │ │ │
│ │ Circuit breaker check │ │ │
│ │ Fat-finger check │ │ │
│ │ NAV deviation check │ │ │
│ │ BEGIN TX │ │ │
│ │─── Lock token row ────────────────────────▶│ │
│ │─── Check user eligibility ───────────────▶│ │
│ │─── Lock balance (fiat or tokens) ────────▶│ │
│ │─── INSERT orders ─────────────────────────▶│ │
│ │ │ │ │
│ │─── ProcessOrder() ──▶│ │ │
│ │◀── result.Trades ────│ │ │
│ │ │ │ │
│ │─── INSERT trades ─────────────────────────▶│ │
│ │─── UPDATE wallets (buyer +qty) ──────────▶│ │
│ │─── UPDATE wallets (seller -qty) ─────────▶│ │
│ │─── UPDATE users.fiat_balance (seller) ───▶│ │
│ │─── UPDATE users.fiat_balance (treasury) ─▶│ │
│ │ COMMIT TX │ │ │
│ │ │ │ │
│ │─── BroadcastTrade ────────────────────────────────────────────▶│
│ │─── BroadcastOrderBook ───────────────────────────────────────▶│
│◀── 200 + trades ────│ │ │ │Pre-flight validation
Check side (buy/sell), quantity > 0, order_type (limit/market/stop_loss/stop_limit). Circuit breaker: if IsTripped() → reject. Fat-finger: if price deviates too far from last trade → FatFingerBlock (reject) or FatFingerConfirm (require confirm_fat_finger=true). NAV deviation: if price > 20% from latest NAV → require confirm_nav_deviation=true.
User eligibility (in DB transaction)
Lock token row (SELECT FOR UPDATE). Check trading_mode = 'full_orderbook'. Verify user: wallet_address set, kyc_status='approved', fatca_status!='blocked', is_activated=true. Accreditation investment limits: retail=$500, professional=$5000, institutional=$50000.
Balance locking
Buy order: UPDATE users SET fiat_balance = fiat_balance - (price * qty). Sell order: UPDATE wallets SET locked_balance = locked_balance + qty. Prevents double-spending during matching.
Order insertion
INSERT INTO orders (..., status='open'). Stop orders get status='pending_stop', added to in-memory StopBook, and return early (no matching yet).
Matching engine processes order
eng.ProcessOrder(order) → price-time priority matching. Engine runs in-memory at ~760K orders/sec. Returns result.Trades (matched trade pairs).
Trade creation & instant settlement
For each match: INSERT INTO trades (settlement_status='pending'). Fee = total * effective_fee_bps / 10000. Buyer wallet: UPSERT balance += qty. Seller wallet: balance -= qty, locked_balance -= qty. Seller fiat: += (total - fee). Treasury: += fee.
Status updates & history
Taker order updated to 'filled' or 'partially_filled'. INSERT INTO order_status_history. INSERT INTO trade_settlement_history (initial 'pending').
WebSocket broadcast (async)
BroadcastTrade, BroadcastOrderBook, BroadcastOrderStatus pushed to all connected clients. If lastTradePrice changed, checkAndTriggerStops() fires for any pending stop orders.
Key API Endpoints
Trading Modes
primary_subscription
Fixed-price subscriptions. No CLOB. Orders rejected if trading_mode != full_orderbook.
full_orderbook
Full CLOB with market, limit, stop_loss, stop_limit orders. Price-time priority.
fixed_price_otc
OTC at admin-set price. Routed to placeOTCOrder() → INSERT otc_orders, requires admin approval.
Background settlement processor in trading-server that picks up pending trades and executes on-chain transfers via blockchain-svc. Runs every 5 seconds.
SETTLEMENT STATUS FLOW
pending → processing → settled | failed → (retry) → processing
SettlementProcessor (5s loop) DB blockchain-svc:8082 Polygon
│ │ │ │
│─SELECT trades WHERE │ │ │
│ settlement_status='pending' │ │ │
│ AND settlement_attempts < 5 │ │ │
│ FOR UPDATE SKIP LOCKED ─────────▶│ │ │
│◀─ trade rows (LIMIT 10) ──────────│ │ │
│ │ │ │
│ [if contract_address empty │ │
│ OR wallet_address missing] │ │ │
│─UPDATE settlement_status= │ │ │
│ 'settled', type='off_chain' ────▶│ (skip on-chain) │ │
│ │ │ │
│ [if all addresses present] │ │ │
│─UPDATE settlement_status= │ │ │
│ 'processing' ───────────────────▶│ │ │
│─POST /api/v1/transfer ───────────────────────────────────────▶│ │
│ │ │─forcedTransfer()──▶│
│ │ │◀──tx_hash─────────│
│◀─ {tx_hash} ─────────────────────────────────────────────────│ │
│─UPDATE settlement_status= │ │ │
│ 'settled', tx_hash=$1, │ │ │
│ settled_at=NOW() ───────────────▶│ │ │
│ │ │ │
│ [on error: attempts < 5] │ │ │
│─UPDATE settlement_attempts++, │ │ │
│ status='pending' ──────────────▶│ │ │
│ [on error: attempts >= 5] │ │ │
│─UPDATE status='failed' ──────────▶│ │ │Polling loop (every 5 seconds)
SettlementProcessor with ticker at 5s interval. HTTP client timeout: 3 minutes. Processes up to 10 trades per tick.
Claim pending trades
SELECT ... FROM trades WHERE settlement_status='pending' AND settlement_attempts < 5 ORDER BY created_at ASC LIMIT 10 FOR UPDATE OF t SKIP LOCKED. Joins tokens (contract_address), users (wallet_address for buyer + seller).
Off-chain fallback
If contract_address is empty OR buyer/seller wallet_address is missing → immediately settle as off_chain. No blockchain call. UPDATE settlement_status='settled', settlement_type='off_chain'.
On-chain transfer
POST to blockchain-svc /api/v1/transfer with {token_addr, from_addr (seller), to_addr (buyer), amount}. blockchain-svc calls SecurityToken.forcedTransfer(from, to, amount). Returns tx_hash.
Success → settled
UPDATE trades SET settlement_status='settled', settlement_type='on_chain', tx_hash=$1, settled_at=NOW(). INSERT INTO trade_settlement_history.
Failure → retry or fail
Increment settlement_attempts. If attempts < 5: status stays 'pending', will retry next tick. If attempts >= 5: status='failed'. Admin can manually retry via POST /admin/trades/:id/retry-settlement.
Key API Endpoints
Agent-initiated transfer bypassing approve/transferFrom. Compliance checks enforced by contract.
End-to-end dividend flow from issuer declaration through per-holder payment distribution. Supports fiat (USD), token, and stablecoin (USDC) dividend types.
STATUS FLOW
draft → pending_approval → approved → distributing → distributed | cancelled
Issuer platform-api:8081 DB
│ │ │
│─POST /dividends────▶│ │
│ │─ SELECT holders from wallets─▶│
│ │─ INSERT dividend_declarations─▶│
│ │ (status=draft, holder_count) │
│◀── 201 ─────────────│ │
│ │ │
│─POST /dividends/:id │ │
│ /submit───────────▶│ │
│ │─ UPDATE status='pending_approval'
│ │ │
Admin │ │
│─POST /dividends/:id │ │
│ /approve──────────▶│─ UPDATE status='approved' ──▶│
│ │ │
│─POST /dividends/:id │ │
│ /distribute───────▶│─ SELECT FOR UPDATE ──────────▶│
│ │─ UPDATE status='distributing'─▶│
│ │─ SELECT holders (wallets) ───▶│
│ │ │
│ │ For each holder: │
│ │─ UPDATE users.fiat_balance ──▶│ (fiat type)
│ │─ INSERT wallet_transactions ─▶│
│ │─ INSERT dividend_payments ───▶│
│ │ │
│ │─ Deduct from issuer balance ─▶│
│ │─ Credit treasury (fee) ──────▶│
│ │─ UPDATE status='distributed' ▶│
│◀── 200 ─────────────│ │Issuer declares dividend
POST /api/v1/dividends → Snapshots holders (SELECT user_id, balance FROM wallets WHERE token_id=$1 AND balance > 0). Calculates total_gross_usd = per_token_usd * total_held. Fee = total_gross * fee_pct / 100. INSERT INTO dividend_declarations (status='draft').
Submit for approval
POST /api/v1/dividends/:id/submit → UPDATE status='pending_approval'. Appears in admin queue.
Admin approves
POST /api/v1/admin/dividends/:id/approve → UPDATE status='approved'. Admin verifies amounts, dates, and issuer balance.
Admin distributes (fiat type)
POST /api/v1/admin/dividends/:id/distribute → SELECT FOR UPDATE (must be 'approved'). For each holder: gross = per_token_usd * balance, fee = gross * fee_pct / 100, net = gross - fee. UPDATE users SET fiat_balance += net. INSERT wallet_transactions (type='dividend', status='completed'). INSERT dividend_payments (status='paid').
Issuer debit & treasury credit
UPDATE users SET fiat_balance -= total_gross_usd WHERE id = issuer_user_id. UPDATE users SET fiat_balance += total_fee_usd WHERE id = TreasuryUserID. Deadlock prevention: treasury locked BEFORE issuer in tx.
Mark distributed
UPDATE dividend_declarations SET status='distributed', distributed_at=NOW(), holder_count=$1.
Dividend Types
Fiat (USD)
Credits users.fiat_balance. Wallet tx type: 'dividend'. Most common.
Stablecoin (USDC)
Credits user_balances table (currency='USDC'). UPSERT on conflict.
Token
Mints additional tokens. UPSERT wallets (balance += qty). Deducts from issuer token wallet.